diff --git a/.github/ISSUE_TEMPLATE/bw-unified.yml b/.github/ISSUE_TEMPLATE/bw-lite.yml similarity index 96% rename from .github/ISSUE_TEMPLATE/bw-unified.yml rename to .github/ISSUE_TEMPLATE/bw-lite.yml index 240b1faa72..f46f4b3e37 100644 --- a/.github/ISSUE_TEMPLATE/bw-unified.yml +++ b/.github/ISSUE_TEMPLATE/bw-lite.yml @@ -1,6 +1,6 @@ -name: Bitwarden Unified Deployment Bug Report +name: Bitwarden Lite Deployment Bug Report description: File a bug report -labels: [bug, bw-unified-deploy] +labels: [bug, bw-lite-deploy] body: - type: markdown attributes: @@ -74,7 +74,7 @@ body: id: epic-label attributes: label: Issue-Link - description: Link to our pinned issue, tracking all Bitwarden Unified + description: Link to our pinned issue, tracking all Bitwarden Lite value: | https://github.com/bitwarden/server/issues/2480 validations: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d92c68b93..58e9ffa360 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ env: jobs: lint: name: Lint - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -38,7 +38,7 @@ jobs: build-artifacts: name: Build Docker images - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 needs: - lint outputs: @@ -49,7 +49,6 @@ jobs: timeout-minutes: 45 strategy: fail-fast: false - max-parallel: 5 matrix: include: - project_name: Admin @@ -292,7 +291,7 @@ jobs: upload: name: Upload - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 needs: build-artifacts permissions: id-token: write @@ -410,7 +409,7 @@ jobs: build-mssqlmigratorutility: name: Build MSSQL migrator utility - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 needs: - lint defaults: @@ -467,7 +466,7 @@ jobs: if: | github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc') - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 needs: - build-artifacts permissions: @@ -490,7 +489,7 @@ jobs: - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - - name: Trigger self-host build + - name: Trigger Bitwarden Lite build uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} @@ -498,7 +497,7 @@ jobs: await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', - workflow_id: 'build-unified.yml', + workflow_id: 'build-bitwarden-lite.yml', ref: 'main', inputs: { server_branch: process.env.GITHUB_REF diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index fb1c18b158..20bc67bc6b 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -62,7 +62,7 @@ jobs: docker compose --profile mssql --profile postgres --profile mysql up -d shell: pwsh - - name: Add MariaDB for unified + - name: Add MariaDB for Bitwarden Lite # Use a different port than MySQL run: | docker run --detach --name mariadb --env MARIADB_ROOT_PASSWORD=mariadb-password -p 4306:3306 mariadb:10 @@ -133,7 +133,7 @@ jobs: # Default Sqlite BW_TEST_DATABASES__3__TYPE: "Sqlite" BW_TEST_DATABASES__3__CONNECTIONSTRING: "Data Source=${{ runner.temp }}/test.db" - # Unified MariaDB + # Bitwarden Lite MariaDB BW_TEST_DATABASES__4__TYPE: "MySql" BW_TEST_DATABASES__4__CONNECTIONSTRING: "server=localhost;port=4306;uid=root;pwd=mariadb-password;database=vault_dev;Allow User Variables=true" run: dotnet test --logger "trx;LogFileName=infrastructure-test-results.trx" /p:CoverletOutputFormatter="cobertura" --collect:"XPlat Code Coverage" diff --git a/Directory.Build.props b/Directory.Build.props index 4511202024..3e55b8a8cc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net8.0 - 2025.11.0 + 2025.11.1 Bit.$(MSBuildProjectName) enable diff --git a/bitwarden_license/src/Scim/appsettings.Development.json b/bitwarden_license/src/Scim/appsettings.Development.json index 32253a93c1..496d0c075f 100644 --- a/bitwarden_license/src/Scim/appsettings.Development.json +++ b/bitwarden_license/src/Scim/appsettings.Development.json @@ -30,6 +30,7 @@ }, "storage": { "connectionString": "UseDevelopmentStorage=true" - } + }, + "pricingUri": "https://billingpricing.qa.bitwarden.pw" } } diff --git a/bitwarden_license/src/Sso/appsettings.Development.json b/bitwarden_license/src/Sso/appsettings.Development.json index 8aae281068..6d9ec77815 100644 --- a/bitwarden_license/src/Sso/appsettings.Development.json +++ b/bitwarden_license/src/Sso/appsettings.Development.json @@ -24,6 +24,7 @@ "storage": { "connectionString": "UseDevelopmentStorage=true" }, - "developmentDirectory": "../../../dev" + "developmentDirectory": "../../../dev", + "pricingUri": "https://billingpricing.qa.bitwarden.pw" } } 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 2bb02c3cee..b367b17c73 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs @@ -13,7 +13,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -207,7 +207,7 @@ public class RemoveOrganizationFromProviderCommandTests organization.PlanType = PlanType.TeamsMonthly; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetPlanOrThrow(PlanType.TeamsMonthly).Returns(teamsMonthlyPlan); @@ -296,7 +296,7 @@ public class RemoveOrganizationFromProviderCommandTests organization.PlanType = PlanType.TeamsMonthly; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetPlanOrThrow(PlanType.TeamsMonthly).Returns(teamsMonthlyPlan); @@ -416,7 +416,7 @@ public class RemoveOrganizationFromProviderCommandTests organization.PlanType = PlanType.TeamsMonthly; organization.Enabled = false; // Start with a disabled organization - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetPlanOrThrow(PlanType.TeamsMonthly).Returns(teamsMonthlyPlan); diff --git a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs index e61cf5f97e..78376f6d98 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs @@ -20,6 +20,7 @@ using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.Billing.Mocks; using Bit.Core.Tokens; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -811,12 +812,12 @@ public class ProviderServiceTests organization.Plan = "Enterprise (Monthly)"; sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var expectedPlanType = PlanType.EnterpriseMonthly2020; sutProvider.GetDependency().GetPlanOrThrow(expectedPlanType) - .Returns(StaticStore.GetPlan(expectedPlanType)); + .Returns(MockPlans.Get(expectedPlanType)); var expectedPlanId = "2020-enterprise-org-seat-monthly"; diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/BusinessUnitConverterTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/BusinessUnitConverterTests.cs index ec52650097..c893886083 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/BusinessUnitConverterTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/BusinessUnitConverterTests.cs @@ -18,6 +18,7 @@ using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Test.Billing.Mocks; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.DataProtection; @@ -72,7 +73,7 @@ public class BusinessUnitConverterTests { organization.PlanType = PlanType.EnterpriseAnnually2020; - var enterpriseAnnually2020 = StaticStore.GetPlan(PlanType.EnterpriseAnnually2020); + var enterpriseAnnually2020 = MockPlans.Get(PlanType.EnterpriseAnnually2020); var subscription = new Subscription { @@ -134,7 +135,7 @@ public class BusinessUnitConverterTests _pricingClient.GetPlanOrThrow(PlanType.EnterpriseAnnually2020) .Returns(enterpriseAnnually2020); - var enterpriseAnnually = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + var enterpriseAnnually = MockPlans.Get(PlanType.EnterpriseAnnually); _pricingClient.GetPlanOrThrow(PlanType.EnterpriseAnnually) .Returns(enterpriseAnnually); @@ -242,7 +243,7 @@ public class BusinessUnitConverterTests argument.Status == ProviderStatusType.Pending && argument.Type == ProviderType.BusinessUnit)).Returns(provider); - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); _pricingClient.GetPlanOrThrow(organization.PlanType).Returns(plan); diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/ProviderBillingServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/ProviderBillingServiceTests.cs index 18c71364e6..daf35e7ae9 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/ProviderBillingServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/Providers/Services/ProviderBillingServiceTests.cs @@ -22,7 +22,7 @@ using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Braintree; @@ -140,7 +140,7 @@ public class ProviderBillingServiceTests .Returns(existingPlan); sutProvider.GetDependency().GetPlanOrThrow(existingPlan.PlanType) - .Returns(StaticStore.GetPlan(existingPlan.PlanType)); + .Returns(MockPlans.Get(existingPlan.PlanType)); sutProvider.GetDependency().GetSubscriptionOrThrow(provider) .Returns(new Subscription @@ -155,7 +155,7 @@ public class ProviderBillingServiceTests Id = "si_ent_annual", Price = new Price { - Id = StaticStore.GetPlan(PlanType.EnterpriseAnnually).PasswordManager + Id = MockPlans.Get(PlanType.EnterpriseAnnually).PasswordManager .StripeProviderPortalSeatPlanId }, Quantity = 10 @@ -168,7 +168,7 @@ public class ProviderBillingServiceTests new ChangeProviderPlanCommand(provider, providerPlanId, PlanType.EnterpriseMonthly); sutProvider.GetDependency().GetPlanOrThrow(command.NewPlan) - .Returns(StaticStore.GetPlan(command.NewPlan)); + .Returns(MockPlans.Get(command.NewPlan)); // Act await sutProvider.Sut.ChangePlan(command); @@ -185,7 +185,7 @@ public class ProviderBillingServiceTests Arg.Is(p => p.Items.Count(si => si.Id == "si_ent_annual" && si.Deleted == true) == 1)); - var newPlanCfg = StaticStore.GetPlan(command.NewPlan); + var newPlanCfg = MockPlans.Get(command.NewPlan); await stripeAdapter.Received(1) .SubscriptionUpdateAsync( Arg.Is(provider.GatewaySubscriptionId), @@ -491,7 +491,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id).Returns(providerPlans); @@ -514,7 +514,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetSubscriptionOrThrow(provider).Returns(subscription); // 50 seats currently assigned with a seat minimum of 100 - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ @@ -573,7 +573,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } var providerPlan = providerPlans.First(); @@ -598,7 +598,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetSubscriptionOrThrow(provider).Returns(subscription); // 95 seats currently assigned with a seat minimum of 100 - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ @@ -661,7 +661,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } var providerPlan = providerPlans.First(); @@ -686,7 +686,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetSubscriptionOrThrow(provider).Returns(subscription); // 110 seats currently assigned with a seat minimum of 100 - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ @@ -749,7 +749,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } var providerPlan = providerPlans.First(); @@ -774,7 +774,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetSubscriptionOrThrow(provider).Returns(subscription); // 110 seats currently assigned with a seat minimum of 100 - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ @@ -827,13 +827,13 @@ public class ProviderBillingServiceTests } ]); - sutProvider.GetDependency().GetPlanOrThrow(planType).Returns(StaticStore.GetPlan(planType)); + sutProvider.GetDependency().GetPlanOrThrow(planType).Returns(MockPlans.Get(planType)); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ new ProviderOrganizationOrganizationDetails { - Plan = StaticStore.GetPlan(planType).Name, + Plan = MockPlans.Get(planType).Name, Status = OrganizationStatusType.Managed, Seats = 5 } @@ -865,13 +865,13 @@ public class ProviderBillingServiceTests } ]); - sutProvider.GetDependency().GetPlanOrThrow(planType).Returns(StaticStore.GetPlan(planType)); + sutProvider.GetDependency().GetPlanOrThrow(planType).Returns(MockPlans.Get(planType)); sutProvider.GetDependency().GetManyDetailsByProviderAsync(provider.Id).Returns( [ new ProviderOrganizationOrganizationDetails { - Plan = StaticStore.GetPlan(planType).Name, + Plan = MockPlans.Get(planType).Name, Status = OrganizationStatusType.Managed, Seats = 15 } @@ -1238,7 +1238,7 @@ public class ProviderBillingServiceTests .Returns(providerPlans); sutProvider.GetDependency().GetPlanOrThrow(PlanType.EnterpriseMonthly) - .Returns(StaticStore.GetPlan(PlanType.EnterpriseMonthly)); + .Returns(MockPlans.Get(PlanType.EnterpriseMonthly)); await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); @@ -1266,7 +1266,7 @@ public class ProviderBillingServiceTests .Returns(providerPlans); sutProvider.GetDependency().GetPlanOrThrow(PlanType.TeamsMonthly) - .Returns(StaticStore.GetPlan(PlanType.TeamsMonthly)); + .Returns(MockPlans.Get(PlanType.TeamsMonthly)); await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); @@ -1317,7 +1317,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1373,7 +1373,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1449,7 +1449,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1525,7 +1525,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1626,7 +1626,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1704,7 +1704,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } sutProvider.GetDependency().GetByProviderId(provider.Id) @@ -1772,8 +1772,8 @@ public class ProviderBillingServiceTests const string enterpriseLineItemId = "enterprise_line_item_id"; const string teamsLineItemId = "teams_line_item_id"; - var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; - var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var enterprisePriceId = MockPlans.Get(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = MockPlans.Get(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; var subscription = new Subscription { @@ -1806,7 +1806,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); @@ -1852,8 +1852,8 @@ public class ProviderBillingServiceTests const string enterpriseLineItemId = "enterprise_line_item_id"; const string teamsLineItemId = "teams_line_item_id"; - var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; - var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var enterprisePriceId = MockPlans.Get(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = MockPlans.Get(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; var subscription = new Subscription { @@ -1886,7 +1886,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); @@ -1932,8 +1932,8 @@ public class ProviderBillingServiceTests const string enterpriseLineItemId = "enterprise_line_item_id"; const string teamsLineItemId = "teams_line_item_id"; - var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; - var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var enterprisePriceId = MockPlans.Get(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = MockPlans.Get(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; var subscription = new Subscription { @@ -1966,7 +1966,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); @@ -2006,8 +2006,8 @@ public class ProviderBillingServiceTests const string enterpriseLineItemId = "enterprise_line_item_id"; const string teamsLineItemId = "teams_line_item_id"; - var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; - var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var enterprisePriceId = MockPlans.Get(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = MockPlans.Get(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; var subscription = new Subscription { @@ -2040,7 +2040,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); @@ -2086,8 +2086,8 @@ public class ProviderBillingServiceTests const string enterpriseLineItemId = "enterprise_line_item_id"; const string teamsLineItemId = "teams_line_item_id"; - var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; - var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var enterprisePriceId = MockPlans.Get(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = MockPlans.Get(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; var subscription = new Subscription { @@ -2120,7 +2120,7 @@ public class ProviderBillingServiceTests foreach (var plan in providerPlans) { sutProvider.GetDependency().GetPlanOrThrow(plan.PlanType) - .Returns(StaticStore.GetPlan(plan.PlanType)); + .Returns(MockPlans.Get(plan.PlanType)); } providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs index 16ae8f7f2c..776403fdd5 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs @@ -6,7 +6,7 @@ using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Settings; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -69,7 +69,7 @@ public class MaxProjectsQueryTests sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().GetPlan(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1); @@ -114,7 +114,7 @@ public class MaxProjectsQueryTests .Returns(projects); sutProvider.GetDependency().GetPlan(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd); diff --git a/bitwarden_license/test/Scim.IntegrationTest/appsettings.Development.json b/bitwarden_license/test/Scim.IntegrationTest/appsettings.Development.json new file mode 100644 index 0000000000..496d0c075f --- /dev/null +++ b/bitwarden_license/test/Scim.IntegrationTest/appsettings.Development.json @@ -0,0 +1,36 @@ +{ + "globalSettings": { + "baseServiceUri": { + "vault": "https://localhost:8080", + "api": "http://localhost:4000", + "identity": "http://localhost:33656", + "admin": "http://localhost:62911", + "notifications": "http://localhost:61840", + "sso": "http://localhost:51822", + "internalNotifications": "http://localhost:61840", + "internalAdmin": "http://localhost:62911", + "internalIdentity": "http://localhost:33656", + "internalApi": "http://localhost:4000", + "internalVault": "https://localhost:8080", + "internalSso": "http://localhost:51822", + "internalScim": "http://localhost:44559" + }, + "mail": { + "smtp": { + "host": "localhost", + "port": 10250 + } + }, + "attachment": { + "connectionString": "UseDevelopmentStorage=true", + "baseUrl": "http://localhost:4000/attachments/" + }, + "events": { + "connectionString": "UseDevelopmentStorage=true" + }, + "storage": { + "connectionString": "UseDevelopmentStorage=true" + }, + "pricingUri": "https://billingpricing.qa.bitwarden.pw" + } +} diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index c5e42cf9e3..3554306ddb 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -57,7 +57,6 @@ services: mysql: image: mysql:8.0 - container_name: bw-mysql ports: - "3306:3306" command: @@ -88,7 +87,6 @@ services: idp: image: kenchan0130/simplesamlphp:1.19.8 - container_name: idp ports: - "8090:8080" environment: @@ -102,7 +100,6 @@ services: rabbitmq: image: rabbitmq:4.1.3-management - container_name: rabbitmq ports: - "5672:5672" - "15672:15672" @@ -116,7 +113,6 @@ services: reverse-proxy: image: nginx:alpine - container_name: reverse-proxy volumes: - "./reverse-proxy.conf:/etc/nginx/conf.d/default.conf" ports: @@ -126,7 +122,6 @@ services: - proxy service-bus: - container_name: service-bus image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest pull_policy: always volumes: @@ -142,7 +137,6 @@ services: redis: image: redis:alpine - container_name: bw-redis ports: - "6379:6379" volumes: diff --git a/src/Admin/appsettings.Development.json b/src/Admin/appsettings.Development.json index 861f9be98d..15d61f493f 100644 --- a/src/Admin/appsettings.Development.json +++ b/src/Admin/appsettings.Development.json @@ -27,6 +27,7 @@ }, "storage": { "connectionString": "UseDevelopmentStorage=true" - } + }, + "pricingUri": "https://billingpricing.qa.bitwarden.pw" } } diff --git a/src/Api/AdminConsole/Controllers/BaseAdminConsoleController.cs b/src/Api/AdminConsole/Controllers/BaseAdminConsoleController.cs new file mode 100644 index 0000000000..9b147c3c54 --- /dev/null +++ b/src/Api/AdminConsole/Controllers/BaseAdminConsoleController.cs @@ -0,0 +1,26 @@ +using Bit.Core.AdminConsole.Utilities.v2; +using Bit.Core.AdminConsole.Utilities.v2.Results; +using Bit.Core.Models.Api; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.AdminConsole.Controllers; + +public abstract class BaseAdminConsoleController : Controller +{ + protected static IResult Handle(CommandResult commandResult) => + commandResult.Match( + error => error switch + { + BadRequestError badRequest => TypedResults.BadRequest(new ErrorResponseModel(badRequest.Message)), + NotFoundError notFound => TypedResults.NotFound(new ErrorResponseModel(notFound.Message)), + InternalError internalError => TypedResults.Json( + new ErrorResponseModel(internalError.Message), + statusCode: StatusCodes.Status500InternalServerError), + _ => TypedResults.Json( + new ErrorResponseModel(error.Message), + statusCode: StatusCodes.Status500InternalServerError + ) + }, + _ => TypedResults.NoContent() + ); +} diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 4b9f7e5d71..155b60ce5b 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -11,8 +11,10 @@ using Bit.Api.Models.Response; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Core; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; @@ -20,6 +22,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Utilities.v2; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Pricing; @@ -43,7 +46,7 @@ namespace Bit.Api.AdminConsole.Controllers; [Route("organizations/{orgId}/users")] [Authorize("Application")] -public class OrganizationUsersController : Controller +public class OrganizationUsersController : BaseAdminConsoleController { private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; @@ -68,6 +71,7 @@ public class OrganizationUsersController : Controller private readonly IFeatureService _featureService; private readonly IPricingClient _pricingClient; private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand; + private readonly IAutomaticallyConfirmOrganizationUserCommand _automaticallyConfirmOrganizationUserCommand; private readonly IConfirmOrganizationUserCommand _confirmOrganizationUserCommand; private readonly IRestoreOrganizationUserCommand _restoreOrganizationUserCommand; private readonly IInitPendingOrganizationCommand _initPendingOrganizationCommand; @@ -101,7 +105,8 @@ public class OrganizationUsersController : Controller IInitPendingOrganizationCommand initPendingOrganizationCommand, IRevokeOrganizationUserCommand revokeOrganizationUserCommand, IResendOrganizationInviteCommand resendOrganizationInviteCommand, - IAdminRecoverAccountCommand adminRecoverAccountCommand) + IAdminRecoverAccountCommand adminRecoverAccountCommand, + IAutomaticallyConfirmOrganizationUserCommand automaticallyConfirmOrganizationUserCommand) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -126,6 +131,7 @@ public class OrganizationUsersController : Controller _featureService = featureService; _pricingClient = pricingClient; _resendOrganizationInviteCommand = resendOrganizationInviteCommand; + _automaticallyConfirmOrganizationUserCommand = automaticallyConfirmOrganizationUserCommand; _confirmOrganizationUserCommand = confirmOrganizationUserCommand; _restoreOrganizationUserCommand = restoreOrganizationUserCommand; _initPendingOrganizationCommand = initPendingOrganizationCommand; @@ -738,6 +744,31 @@ public class OrganizationUsersController : Controller await BulkEnableSecretsManagerAsync(orgId, model); } + [HttpPost("{id}/auto-confirm")] + [Authorize] + [RequireFeature(FeatureFlagKeys.AutomaticConfirmUsers)] + public async Task AutomaticallyConfirmOrganizationUserAsync([FromRoute] Guid orgId, + [FromRoute] Guid id, + [FromBody] OrganizationUserConfirmRequestModel model) + { + var userId = _userService.GetProperUserId(User); + + if (userId is null || userId.Value == Guid.Empty) + { + return TypedResults.Unauthorized(); + } + + return Handle(await _automaticallyConfirmOrganizationUserCommand.AutomaticallyConfirmOrganizationUserAsync( + new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationId = orgId, + OrganizationUserId = id, + Key = model.Key, + DefaultUserCollectionName = model.DefaultUserCollectionName, + PerformedBy = new StandardUser(userId.Value, await _currentContext.OrganizationOwner(orgId)), + })); + } + private async Task RestoreOrRevokeUserAsync( Guid orgId, Guid id, diff --git a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs index 97a58d038a..8c52092dae 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using Bit.Core.Billing.Models; +using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Utilities; @@ -27,7 +28,7 @@ public class ProfileOrganizationResponseModel : BaseProfileOrganizationResponseM FamilySponsorshipToDelete = organizationDetails.FamilySponsorshipToDelete; FamilySponsorshipValidUntil = organizationDetails.FamilySponsorshipValidUntil; FamilySponsorshipAvailable = (organizationDetails.FamilySponsorshipFriendlyName == null || IsAdminInitiated) && - StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise) + SponsoredPlans.Get(PlanSponsorshipType.FamiliesForEnterprise) .UsersCanSponsor(organizationDetails); AccessSecretsManager = organizationDetails.AccessSecretsManager; } diff --git a/src/Api/Billing/Controllers/VNext/SelfHostedBillingController.cs b/src/Api/Billing/Controllers/VNext/SelfHostedBillingController.cs new file mode 100644 index 0000000000..bd40c777dc --- /dev/null +++ b/src/Api/Billing/Controllers/VNext/SelfHostedBillingController.cs @@ -0,0 +1,35 @@ +using Bit.Api.AdminConsole.Authorization; +using Bit.Api.AdminConsole.Authorization.Requirements; +using Bit.Api.Billing.Attributes; +using Bit.Core; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Organizations.Queries; +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Bit.Api.Billing.Controllers.VNext; + +[Authorize("Application")] +[Route("organizations/{organizationId:guid}/billing/vnext/self-host")] +[SelfHosted(SelfHostedOnly = true)] +public class SelfHostedBillingController( + IGetOrganizationMetadataQuery getOrganizationMetadataQuery) : BaseBillingController +{ + [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); + } +} diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 0983225f84..c200810156 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -1422,11 +1422,9 @@ public class CiphersController : Controller throw new NotFoundException(); } - // Extract lastKnownRevisionDate from form data if present - DateTime? lastKnownRevisionDate = GetLastKnownRevisionDateFromForm(); await Request.GetFileAsync(async (stream) => { - await _cipherService.UploadFileForExistingAttachmentAsync(stream, cipher, attachmentData, lastKnownRevisionDate); + await _cipherService.UploadFileForExistingAttachmentAsync(stream, cipher, attachmentData); }); } @@ -1525,13 +1523,10 @@ public class CiphersController : Controller throw new NotFoundException(); } - // Extract lastKnownRevisionDate from form data if present - DateTime? lastKnownRevisionDate = GetLastKnownRevisionDateFromForm(); - await Request.GetFileAsync(async (stream, fileName, key) => { await _cipherService.CreateAttachmentShareAsync(cipher, stream, fileName, key, - Request.ContentLength.GetValueOrDefault(0), attachmentId, organizationId, lastKnownRevisionDate); + Request.ContentLength.GetValueOrDefault(0), attachmentId, organizationId); }); } diff --git a/src/Api/appsettings.Development.json b/src/Api/appsettings.Development.json index 82fb951261..87e92c4516 100644 --- a/src/Api/appsettings.Development.json +++ b/src/Api/appsettings.Development.json @@ -41,6 +41,7 @@ "phishingDomain": { "updateUrl": "https://phish.co.za/latest/phishing-domains-ACTIVE.txt", "checksumUrl": "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-domains-ACTIVE.txt.sha256" - } + }, + "pricingUri": "https://billingpricing.qa.bitwarden.pw" } } diff --git a/src/Billing/Services/Implementations/StripeEventUtilityService.cs b/src/Billing/Services/Implementations/StripeEventUtilityService.cs index 49e562de56..06a5d8a890 100644 --- a/src/Billing/Services/Implementations/StripeEventUtilityService.cs +++ b/src/Billing/Services/Implementations/StripeEventUtilityService.cs @@ -3,12 +3,12 @@ using Bit.Billing.Constants; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Models; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Utilities; using Braintree; using Stripe; using Customer = Stripe.Customer; @@ -112,7 +112,7 @@ public class StripeEventUtilityService : IStripeEventUtilityService } public bool IsSponsoredSubscription(Subscription subscription) => - StaticStore.SponsoredPlans + SponsoredPlans.All .Any(p => subscription.Items .Any(i => i.Plan.Id == p.StripePlanId)); diff --git a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs index 1db469a4e2..6db0cb6373 100644 --- a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs +++ b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs @@ -195,40 +195,48 @@ public class UpcomingInvoiceHandler( Plan plan, bool milestone3) { - if (milestone3 && plan.Type == PlanType.FamiliesAnnually2019) + // currently these are the only plans that need aligned and both require the same flag and share most of the logic + if (!milestone3 || plan.Type is not (PlanType.FamiliesAnnually2019 or PlanType.FamiliesAnnually2025)) { - var passwordManagerItem = - subscription.Items.FirstOrDefault(item => item.Price.Id == plan.PasswordManager.StripePlanId); + return; + } - if (passwordManagerItem == null) - { - logger.LogWarning("Could not find Organization's ({OrganizationId}) password manager item while processing '{EventType}' event ({EventID})", - organization.Id, @event.Type, @event.Id); - return; - } + var passwordManagerItem = + subscription.Items.FirstOrDefault(item => item.Price.Id == plan.PasswordManager.StripePlanId); - var families = await pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually); + if (passwordManagerItem == null) + { + logger.LogWarning("Could not find Organization's ({OrganizationId}) password manager item while processing '{EventType}' event ({EventID})", + organization.Id, @event.Type, @event.Id); + return; + } - organization.PlanType = families.Type; - organization.Plan = families.Name; - organization.UsersGetPremium = families.UsersGetPremium; - organization.Seats = families.PasswordManager.BaseSeats; + var families = await pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually); - var options = new SubscriptionUpdateOptions - { - Items = - [ - new SubscriptionItemOptions - { - Id = passwordManagerItem.Id, Price = families.PasswordManager.StripePlanId - } - ], - Discounts = - [ - new SubscriptionDiscountOptions { Coupon = CouponIDs.Milestone3SubscriptionDiscount } - ], - ProrationBehavior = ProrationBehavior.None - }; + organization.PlanType = families.Type; + organization.Plan = families.Name; + organization.UsersGetPremium = families.UsersGetPremium; + organization.Seats = families.PasswordManager.BaseSeats; + + var options = new SubscriptionUpdateOptions + { + Items = + [ + new SubscriptionItemOptions + { + Id = passwordManagerItem.Id, + Price = families.PasswordManager.StripePlanId + } + ], + ProrationBehavior = ProrationBehavior.None + }; + + if (plan.Type == PlanType.FamiliesAnnually2019) + { + options.Discounts = + [ + new SubscriptionDiscountOptions { Coupon = CouponIDs.Milestone3SubscriptionDiscount } + ]; var premiumAccessAddOnItem = subscription.Items.FirstOrDefault(item => item.Price.Id == plan.PasswordManager.StripePremiumAccessPlanId); @@ -242,21 +250,32 @@ public class UpcomingInvoiceHandler( }); } - try + var seatAddOnItem = subscription.Items.FirstOrDefault(item => item.Price.Id == "personal-org-seat-annually"); + + if (seatAddOnItem != null) { - await organizationRepository.ReplaceAsync(organization); - await stripeFacade.UpdateSubscription(subscription.Id, options); - } - catch (Exception exception) - { - logger.LogError( - exception, - "Failed to align subscription concerns for Organization ({OrganizationID}) while processing '{EventType}' event ({EventID})", - organization.Id, - @event.Type, - @event.Id); + options.Items.Add(new SubscriptionItemOptions + { + Id = seatAddOnItem.Id, + Deleted = true + }); } } + + try + { + await organizationRepository.ReplaceAsync(organization); + await stripeFacade.UpdateSubscription(subscription.Id, options); + } + catch (Exception exception) + { + logger.LogError( + exception, + "Failed to align subscription concerns for Organization ({OrganizationID}) while processing '{EventType}' event ({EventID})", + organization.Id, + @event.Type, + @event.Id); + } } #endregion diff --git a/src/Billing/appsettings.Development.json b/src/Billing/appsettings.Development.json index 7c4889c22f..fe8e47b2f6 100644 --- a/src/Billing/appsettings.Development.json +++ b/src/Billing/appsettings.Development.json @@ -35,6 +35,7 @@ "billingSettings": { "onyx": { "personaId": 68 - } - } + } + }, + "pricingUri": "https://billingpricing.qa.bitwarden.pw" } diff --git a/src/Core/AdminConsole/Enums/EventType.cs b/src/Core/AdminConsole/Enums/EventType.cs index 8073938fc5..09cda7ca0e 100644 --- a/src/Core/AdminConsole/Enums/EventType.cs +++ b/src/Core/AdminConsole/Enums/EventType.cs @@ -60,6 +60,7 @@ public enum EventType : int OrganizationUser_RejectedAuthRequest = 1514, OrganizationUser_Deleted = 1515, // Both user and organization user data were deleted OrganizationUser_Left = 1516, // User voluntarily left the organization + OrganizationUser_AutomaticallyConfirmed = 1517, Organization_Updated = 1600, Organization_PurgedVault = 1601, diff --git a/src/Core/AdminConsole/Models/Data/OrganizationUsers/AcceptedOrganizationUserToConfirm.cs b/src/Core/AdminConsole/Models/Data/OrganizationUsers/AcceptedOrganizationUserToConfirm.cs new file mode 100644 index 0000000000..0dc6d1c352 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/OrganizationUsers/AcceptedOrganizationUserToConfirm.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.AdminConsole.Models.Data.OrganizationUsers; + +public record AcceptedOrganizationUserToConfirm +{ + public required Guid OrganizationUserId { get; init; } + public required Guid UserId { get; init; } + public required string Key { get; init; } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs new file mode 100644 index 0000000000..67b5f0da80 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs @@ -0,0 +1,186 @@ +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Platform.Push; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Microsoft.Extensions.Logging; +using OneOf.Types; +using CommandResult = Bit.Core.AdminConsole.Utilities.v2.Results.CommandResult; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +public class AutomaticallyConfirmOrganizationUserCommand(IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IAutomaticallyConfirmOrganizationUsersValidator validator, + IEventService eventService, + IMailService mailService, + IUserRepository userRepository, + IPushRegistrationService pushRegistrationService, + IDeviceRepository deviceRepository, + IPushNotificationService pushNotificationService, + IPolicyRequirementQuery policyRequirementQuery, + ICollectionRepository collectionRepository, + TimeProvider timeProvider, + ILogger logger) : IAutomaticallyConfirmOrganizationUserCommand +{ + public async Task AutomaticallyConfirmOrganizationUserAsync(AutomaticallyConfirmOrganizationUserRequest request) + { + var validatorRequest = await RetrieveDataAsync(request); + + var validatedData = await validator.ValidateAsync(validatorRequest); + + return await validatedData.Match>( + error => Task.FromResult(new CommandResult(error)), + async _ => + { + var userToConfirm = new AcceptedOrganizationUserToConfirm + { + OrganizationUserId = validatedData.Request.OrganizationUser!.Id, + UserId = validatedData.Request.OrganizationUser.UserId!.Value, + Key = validatedData.Request.Key + }; + + // This operation is idempotent. If false, the user is already confirmed and no additional side effects are required. + if (!await organizationUserRepository.ConfirmOrganizationUserAsync(userToConfirm)) + { + return new None(); + } + + await CreateDefaultCollectionsAsync(validatedData.Request); + + await Task.WhenAll( + LogOrganizationUserConfirmedEventAsync(validatedData.Request), + SendConfirmedOrganizationUserEmailAsync(validatedData.Request), + SyncOrganizationKeysAsync(validatedData.Request) + ); + + return new None(); + } + ); + } + + private async Task SyncOrganizationKeysAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + await DeleteDeviceRegistrationAsync(request); + await PushSyncOrganizationKeysAsync(request); + } + + private async Task CreateDefaultCollectionsAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + try + { + if (!await ShouldCreateDefaultCollectionAsync(request)) + { + return; + } + + await collectionRepository.CreateAsync( + new Collection + { + OrganizationId = request.Organization!.Id, + Name = request.DefaultUserCollectionName, + Type = CollectionType.DefaultUserCollection + }, + groups: null, + [new CollectionAccessSelection + { + Id = request.OrganizationUser!.Id, + Manage = true + }]); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to create default collection for user."); + } + } + + /// + /// Determines whether a default collection should be created for an organization user during the confirmation process. + /// + /// + /// The validation request containing information about the user, organization, and collection settings. + /// + /// The result is a boolean value indicating whether a default collection should be created. + private async Task ShouldCreateDefaultCollectionAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) => + !string.IsNullOrWhiteSpace(request.DefaultUserCollectionName) + && (await policyRequirementQuery.GetAsync(request.OrganizationUser!.UserId!.Value)) + .RequiresDefaultCollectionOnConfirm(request.Organization!.Id); + + private async Task PushSyncOrganizationKeysAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + try + { + await pushNotificationService.PushSyncOrgKeysAsync(request.OrganizationUser!.UserId!.Value); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to push organization keys."); + } + } + + private async Task LogOrganizationUserConfirmedEventAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + try + { + await eventService.LogOrganizationUserEventAsync(request.OrganizationUser, + EventType.OrganizationUser_AutomaticallyConfirmed, + timeProvider.GetUtcNow().UtcDateTime); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to log OrganizationUser_AutomaticallyConfirmed event."); + } + } + + private async Task SendConfirmedOrganizationUserEmailAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + try + { + var user = await userRepository.GetByIdAsync(request.OrganizationUser!.UserId!.Value); + + await mailService.SendOrganizationConfirmedEmailAsync(request.Organization!.Name, + user!.Email, + request.OrganizationUser.AccessSecretsManager); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to send OrganizationUserConfirmed."); + } + } + + private async Task DeleteDeviceRegistrationAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + try + { + var devices = (await deviceRepository.GetManyByUserIdAsync(request.OrganizationUser!.UserId!.Value)) + .Where(d => !string.IsNullOrWhiteSpace(d.PushToken)) + .Select(d => d.Id.ToString()); + + await pushRegistrationService.DeleteUserRegistrationOrganizationAsync(devices, request.Organization!.Id.ToString()); + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to delete device registration."); + } + } + + private async Task RetrieveDataAsync( + AutomaticallyConfirmOrganizationUserRequest request) + { + return new AutomaticallyConfirmOrganizationUserValidationRequest + { + OrganizationUserId = request.OrganizationUserId, + OrganizationId = request.OrganizationId, + Key = request.Key, + DefaultUserCollectionName = request.DefaultUserCollectionName, + PerformedBy = request.PerformedBy, + OrganizationUser = await organizationUserRepository.GetByIdAsync(request.OrganizationUserId), + Organization = await organizationRepository.GetByIdAsync(request.OrganizationId) + }; + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserRequest.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserRequest.cs new file mode 100644 index 0000000000..fcc8dacf66 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserRequest.cs @@ -0,0 +1,29 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Data; +using Bit.Core.Entities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +/// +/// Automatically Confirm User Command Request +/// +public record AutomaticallyConfirmOrganizationUserRequest +{ + public required Guid OrganizationUserId { get; init; } + public required Guid OrganizationId { get; init; } + public required string Key { get; init; } + public required string DefaultUserCollectionName { get; init; } + public required IActingUser PerformedBy { get; init; } +} + +/// +/// Automatically Confirm User Validation Request +/// +/// +/// This is used to hold retrieved data and pass it to the validator +/// +public record AutomaticallyConfirmOrganizationUserValidationRequest : AutomaticallyConfirmOrganizationUserRequest +{ + public OrganizationUser? OrganizationUser { get; set; } + public Organization? Organization { get; set; } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs new file mode 100644 index 0000000000..11b89de680 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUsersValidator.cs @@ -0,0 +1,116 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Utilities.v2; +using Bit.Core.AdminConsole.Utilities.v2.Validation; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +public class AutomaticallyConfirmOrganizationUsersValidator( + IOrganizationUserRepository organizationUserRepository, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IPolicyRequirementQuery policyRequirementQuery, + IPolicyRepository policyRepository) : IAutomaticallyConfirmOrganizationUsersValidator +{ + public async Task> ValidateAsync( + AutomaticallyConfirmOrganizationUserValidationRequest request) + { + // User must exist + if (request is { OrganizationUser: null } || request.OrganizationUser is { UserId: null }) + { + return Invalid(request, new UserNotFoundError()); + } + + // Organization must exist + if (request is { Organization: null }) + { + return Invalid(request, new OrganizationNotFound()); + } + + // User must belong to the organization + if (request.OrganizationUser.OrganizationId != request.Organization.Id) + { + return Invalid(request, new OrganizationUserIdIsInvalid()); + } + + // User must be accepted + if (request is { OrganizationUser.Status: not OrganizationUserStatusType.Accepted }) + { + return Invalid(request, new UserIsNotAccepted()); + } + + // User must be of type User + if (request is { OrganizationUser.Type: not OrganizationUserType.User }) + { + return Invalid(request, new UserIsNotUserType()); + } + + if (!await OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(request)) + { + return Invalid(request, new AutomaticallyConfirmUsersPolicyIsNotEnabled()); + } + + if (!await OrganizationUserConformsToTwoFactorRequiredPolicyAsync(request)) + { + return Invalid(request, new UserDoesNotHaveTwoFactorEnabled()); + } + + if (await OrganizationUserConformsToSingleOrgPolicyAsync(request) is { } error) + { + return Invalid(request, error); + } + + return Valid(request); + } + + private async Task OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync( + AutomaticallyConfirmOrganizationUserValidationRequest request) => + await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId, + PolicyType.AutomaticUserConfirmation) is { Enabled: true } + && request.Organization is { UseAutomaticUserConfirmation: true }; + + private async Task OrganizationUserConformsToTwoFactorRequiredPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) + { + if ((await twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync([request.OrganizationUser!.UserId!.Value])) + .Any(x => x.userId == request.OrganizationUser.UserId && x.twoFactorIsEnabled)) + { + return true; + } + + return !(await policyRequirementQuery.GetAsync(request.OrganizationUser.UserId!.Value)) + .IsTwoFactorRequiredForOrganization(request.Organization!.Id); + } + + private async Task OrganizationUserConformsToSingleOrgPolicyAsync( + AutomaticallyConfirmOrganizationUserValidationRequest request) + { + var allOrganizationUsersForUser = await organizationUserRepository + .GetManyByUserAsync(request.OrganizationUser!.UserId!.Value); + + if (allOrganizationUsersForUser.Count == 1) + { + return null; + } + + var policyRequirement = await policyRequirementQuery + .GetAsync(request.OrganizationUser!.UserId!.Value); + + if (policyRequirement.IsSingleOrgEnabledForThisOrganization(request.Organization!.Id)) + { + return new OrganizationEnforcesSingleOrgPolicy(); + } + + if (policyRequirement.IsSingleOrgEnabledForOrganizationsOtherThan(request.Organization.Id)) + { + return new OtherOrganizationEnforcesSingleOrgPolicy(); + } + + return null; + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/Errors.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/Errors.cs new file mode 100644 index 0000000000..1564daca6c --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/Errors.cs @@ -0,0 +1,13 @@ +using Bit.Core.AdminConsole.Utilities.v2; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +public record OrganizationNotFound() : NotFoundError("Invalid organization"); +public record FailedToWriteToEventLog() : InternalError("Failed to write to event log"); +public record UserIsNotUserType() : BadRequestError("Only organization users with the User role can be automatically confirmed"); +public record UserIsNotAccepted() : BadRequestError("Cannot confirm user that has not accepted the invitation."); +public record OrganizationUserIdIsInvalid() : BadRequestError("Invalid organization user id."); +public record UserDoesNotHaveTwoFactorEnabled() : BadRequestError("User does not have two-step login enabled."); +public record OrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization until they leave or remove all other organizations"); +public record OtherOrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization because they are in another organization which forbids it."); +public record AutomaticallyConfirmUsersPolicyIsNotEnabled() : BadRequestError("Cannot confirm this member because the Automatically Confirm Users policy is not enabled."); diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/IAutomaticallyConfirmOrganizationUsersValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/IAutomaticallyConfirmOrganizationUsersValidator.cs new file mode 100644 index 0000000000..544b65b53f --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/IAutomaticallyConfirmOrganizationUsersValidator.cs @@ -0,0 +1,9 @@ +using Bit.Core.AdminConsole.Utilities.v2.Validation; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; + +public interface IAutomaticallyConfirmOrganizationUsersValidator +{ + Task> ValidateAsync( + AutomaticallyConfirmOrganizationUserValidationRequest request); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountCommand.cs index 87c24c3ab4..c5c423f2bb 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountCommand.cs @@ -1,4 +1,6 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.Utilities.v2.Results; +using Bit.Core.AdminConsole.Utilities.v2.Validation; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountValidator.cs index 315d45ea69..71eff3ae69 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/DeleteClaimedOrganizationUserAccountValidator.cs @@ -1,8 +1,9 @@ using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Utilities.v2.Validation; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Repositories; -using static Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount.ValidationResultHelpers; +using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/Errors.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/Errors.cs index 6c8f7ee00c..a76104cc88 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/Errors.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/Errors.cs @@ -1,15 +1,6 @@ -namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +using Bit.Core.AdminConsole.Utilities.v2; -/// -/// A strongly typed error containing a reason that an action failed. -/// This is used for business logic validation and other expected errors, not exceptions. -/// -public abstract record Error(string Message); -/// -/// An type that maps to a NotFoundResult at the api layer. -/// -/// -public abstract record NotFoundError(string Message) : Error(Message); +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; public record UserNotFoundError() : NotFoundError("Invalid user."); public record UserNotClaimedError() : Error("Member is not claimed by the organization."); diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountCommand.cs index 983a3a4f21..408d3e8bcd 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountCommand.cs @@ -1,4 +1,6 @@ -namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +using Bit.Core.AdminConsole.Utilities.v2.Results; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; public interface IDeleteClaimedOrganizationUserAccountCommand { diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountValidator.cs index f1a2c71b1b..05e97e896a 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/IDeleteClaimedOrganizationUserAccountValidator.cs @@ -1,4 +1,6 @@ -namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +using Bit.Core.AdminConsole.Utilities.v2.Validation; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; public interface IDeleteClaimedOrganizationUserAccountValidator { diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IAutomaticallyConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IAutomaticallyConfirmOrganizationUserCommand.cs new file mode 100644 index 0000000000..a1776416ae --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IAutomaticallyConfirmOrganizationUserCommand.cs @@ -0,0 +1,40 @@ +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; +using Bit.Core.AdminConsole.Utilities.v2.Results; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; + +/// +/// Command to automatically confirm an organization user. +/// +/// +/// The auto-confirm feature enables eligible client apps to confirm OrganizationUsers +/// automatically via push notifications, eliminating the need for manual administrator +/// intervention. Client apps receive a push notification, perform the required key exchange, +/// and submit an auto-confirm request to the server. This command processes those +/// client-initiated requests and should only be used in that specific context. +/// +public interface IAutomaticallyConfirmOrganizationUserCommand +{ + /// + /// Automatically confirms the organization user based on the provided request data. + /// + /// The request containing necessary information to confirm the organization user. + /// + /// This action has side effects. The side effects are + ///
    + ///
  • Creating an event log entry.
  • + ///
  • Syncing organization keys with the user.
  • + ///
  • Deleting any registered user devices for the organization.
  • + ///
  • Sending an email to the confirmed user.
  • + ///
  • Creating the default collection if applicable.
  • + ///
+ /// + /// Each of these actions is performed independently of each other and not guaranteed to be performed in any order. + /// Errors will be reported back for the actions that failed in a consolidated error message. + ///
+ /// + /// The result of the command. If there was an error, the result will contain a typed error describing the problem + /// that occurred. + /// + Task AutomaticallyConfirmOrganizationUserAsync(AutomaticallyConfirmOrganizationUserRequest request); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs index 8d8ab8cdfc..0cae0fcc81 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs @@ -75,8 +75,7 @@ public class CloudOrganizationSignUpCommand( PlanType = plan!.Type, Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats), MaxCollections = plan.PasswordManager.MaxCollections, - MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ? - (short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb), + MaxStorageGb = (short)(plan.PasswordManager.BaseStorageGb + signup.AdditionalStorageGb), UsePolicies = plan.HasPolicies, UseSso = plan.HasSso, UseGroups = plan.HasGroups, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/ProviderClientOrganizationSignUpCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/ProviderClientOrganizationSignUpCommand.cs index 27e70fbe2d..4a8f08a4f7 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Organizations/ProviderClientOrganizationSignUpCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/ProviderClientOrganizationSignUpCommand.cs @@ -73,7 +73,7 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati PlanType = plan!.Type, Seats = signup.AdditionalSeats, MaxCollections = plan.PasswordManager.MaxCollections, - MaxStorageGb = 1, + MaxStorageGb = plan.PasswordManager.BaseStorageGb, UsePolicies = plan.HasPolicies, UseSso = plan.HasSso, UseOrganizationDomains = plan.HasOrganizationDomains, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SingleOrganizationPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SingleOrganizationPolicyRequirement.cs new file mode 100644 index 0000000000..d1e1efafd9 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/SingleOrganizationPolicyRequirement.cs @@ -0,0 +1,21 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +public class SingleOrganizationPolicyRequirement(IEnumerable policyDetails) : IPolicyRequirement +{ + public bool IsSingleOrgEnabledForThisOrganization(Guid organizationId) => + policyDetails.Any(p => p.OrganizationId == organizationId); + + public bool IsSingleOrgEnabledForOrganizationsOtherThan(Guid organizationId) => + policyDetails.Any(p => p.OrganizationId != organizationId); +} + +public class SingleOrganizationPolicyRequirementFactory : BasePolicyRequirementFactory +{ + public override PolicyType PolicyType => PolicyType.SingleOrg; + + public override SingleOrganizationPolicyRequirement Create(IEnumerable policyDetails) => + new(policyDetails); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 7c1987865a..e0ca8d6f90 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -65,5 +65,6 @@ public static class PolicyServiceCollectionExtensions services.AddScoped, RequireSsoPolicyRequirementFactory>(); services.AddScoped, RequireTwoFactorPolicyRequirementFactory>(); services.AddScoped, MasterPasswordPolicyRequirementFactory>(); + services.AddScoped, SingleOrganizationPolicyRequirementFactory>(); } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs index c0378bf5f9..d24c61e258 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/SingleOrgPolicyValidator.cs @@ -1,6 +1,4 @@ -#nullable enable - -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; @@ -29,8 +27,6 @@ public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent private readonly IOrganizationRepository _organizationRepository; private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ICurrentContext _currentContext; - private readonly IFeatureService _featureService; - private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery; private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand; @@ -40,8 +36,6 @@ public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent IOrganizationRepository organizationRepository, ISsoConfigRepository ssoConfigRepository, ICurrentContext currentContext, - IFeatureService featureService, - IRemoveOrganizationUserCommand removeOrganizationUserCommand, IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery, IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand) { @@ -50,8 +44,6 @@ public class SingleOrgPolicyValidator : IPolicyValidator, IPolicyValidationEvent _organizationRepository = organizationRepository; _ssoConfigRepository = ssoConfigRepository; _currentContext = currentContext; - _featureService = featureService; - _removeOrganizationUserCommand = removeOrganizationUserCommand; _organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery; _revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand; } diff --git a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs index b17de3c51d..bedb9d49ee 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.Entities; using Bit.Core.Enums; @@ -93,7 +94,7 @@ public interface IOrganizationUserRepository : IRepository - /// Accepted OrganizationUser to confirm + /// Accepted OrganizationUser to confirm /// True, if the user was updated. False, if not performed. - Task ConfirmOrganizationUserAsync(OrganizationUser organizationUser); + Task ConfirmOrganizationUserAsync(AcceptedOrganizationUserToConfirm organizationUserToConfirm); } diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 1b52ad8cff..f18ecf341b 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -148,7 +148,7 @@ public class OrganizationService : IOrganizationService } var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb, - plan.PasswordManager.StripeStoragePlanId); + plan.PasswordManager.StripeStoragePlanId, plan.PasswordManager.BaseStorageGb); await ReplaceAndUpdateCacheAsync(organization); return secret; } diff --git a/src/Core/AdminConsole/Utilities/v2/Errors.cs b/src/Core/AdminConsole/Utilities/v2/Errors.cs new file mode 100644 index 0000000000..c1c66b2630 --- /dev/null +++ b/src/Core/AdminConsole/Utilities/v2/Errors.cs @@ -0,0 +1,15 @@ +namespace Bit.Core.AdminConsole.Utilities.v2; + +/// +/// A strongly typed error containing a reason that an action failed. +/// This is used for business logic validation and other expected errors, not exceptions. +/// +public abstract record Error(string Message); +/// +/// An type that maps to a NotFoundResult at the api layer. +/// +/// +public abstract record NotFoundError(string Message) : Error(Message); + +public abstract record BadRequestError(string Message) : Error(Message); +public abstract record InternalError(string Message) : Error(Message); diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/CommandResult.cs b/src/Core/AdminConsole/Utilities/v2/Results/CommandResult.cs similarity index 94% rename from src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/CommandResult.cs rename to src/Core/AdminConsole/Utilities/v2/Results/CommandResult.cs index fbb00a908a..fb1bd16b2d 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/CommandResult.cs +++ b/src/Core/AdminConsole/Utilities/v2/Results/CommandResult.cs @@ -1,7 +1,7 @@ using OneOf; using OneOf.Types; -namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +namespace Bit.Core.AdminConsole.Utilities.v2.Results; /// /// Represents the result of a command. @@ -39,4 +39,3 @@ public record BulkCommandResult(Guid Id, CommandResult Result); /// A wrapper for with an ID, to identify the result in bulk operations. /// public record BulkCommandResult(Guid Id, CommandResult Result); - diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/ValidationResult.cs b/src/Core/AdminConsole/Utilities/v2/Validation/ValidationResult.cs similarity index 94% rename from src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/ValidationResult.cs rename to src/Core/AdminConsole/Utilities/v2/Validation/ValidationResult.cs index c84a0aeda1..e28eac9a1c 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccount/ValidationResult.cs +++ b/src/Core/AdminConsole/Utilities/v2/Validation/ValidationResult.cs @@ -1,7 +1,7 @@ using OneOf; using OneOf.Types; -namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +namespace Bit.Core.AdminConsole.Utilities.v2.Validation; /// /// Represents the result of validating a request. diff --git a/src/Core/Billing/Models/SponsoredPlans.cs b/src/Core/Billing/Models/SponsoredPlans.cs new file mode 100644 index 0000000000..851c8557d4 --- /dev/null +++ b/src/Core/Billing/Models/SponsoredPlans.cs @@ -0,0 +1,25 @@ +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Extensions; +using Bit.Core.Enums; +using Bit.Core.Models.StaticStore; + +namespace Bit.Core.Billing.Models; + +public class SponsoredPlans +{ + public static IEnumerable All { get; set; } = + [ + new() + { + PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise, + SponsoredProductTierType = ProductTierType.Families, + SponsoringProductTierType = ProductTierType.Enterprise, + StripePlanId = "2021-family-for-enterprise-annually", + UsersCanSponsor = org => + org.PlanType.GetProductTier() == ProductTierType.Enterprise, + } + ]; + + public static SponsoredPlan Get(PlanSponsorshipType planSponsorshipType) => + All.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType)!; +} diff --git a/src/Core/Billing/Models/StaticStore/Plan.cs b/src/Core/Billing/Models/StaticStore/Plan.cs index 6d8d00089c..bab64d9879 100644 --- a/src/Core/Billing/Models/StaticStore/Plan.cs +++ b/src/Core/Billing/Models/StaticStore/Plan.cs @@ -97,7 +97,7 @@ public abstract record Plan public decimal PremiumAccessOptionPrice { get; init; } public short? MaxSeats { get; init; } // Storage - public short? BaseStorageGb { get; init; } + public short BaseStorageGb { get; init; } public bool HasAdditionalStorageOption { get; init; } public decimal AdditionalStoragePricePerGb { get; init; } public string StripeStoragePlanId { get; init; } diff --git a/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs b/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs index 89d301c22a..143da0d67f 100644 --- a/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs +++ b/src/Core/Billing/Organizations/Commands/PreviewOrganizationTaxCommand.cs @@ -3,12 +3,12 @@ using Bit.Core.Billing.Commands; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Pricing; using Bit.Core.Enums; using Bit.Core.Services; -using Bit.Core.Utilities; using Microsoft.Extensions.Logging; using OneOf; using Stripe; @@ -54,7 +54,7 @@ public class PreviewOrganizationTaxCommand( switch (purchase) { case { PasswordManager.Sponsored: true }: - var sponsoredPlan = StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise); + var sponsoredPlan = SponsoredPlans.Get(PlanSponsorshipType.FamiliesForEnterprise); items.Add(new InvoiceSubscriptionDetailsItemOptions { Price = sponsoredPlan.StripePlanId, diff --git a/src/Core/Billing/Organizations/Models/SponsorOrganizationSubscriptionUpdate.cs b/src/Core/Billing/Organizations/Models/SponsorOrganizationSubscriptionUpdate.cs index ee603c67e0..6c1362d1c5 100644 --- a/src/Core/Billing/Organizations/Models/SponsorOrganizationSubscriptionUpdate.cs +++ b/src/Core/Billing/Organizations/Models/SponsorOrganizationSubscriptionUpdate.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.Models; using Bit.Core.Models.Business; using Stripe; @@ -17,7 +18,7 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate { _existingPlanStripeId = existingPlan.PasswordManager.StripePlanId; _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId - ?? Core.Utilities.StaticStore.SponsoredPlans.FirstOrDefault()?.StripePlanId; + ?? SponsoredPlans.All.FirstOrDefault()?.StripePlanId; _applySponsorship = applySponsorship; } diff --git a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs index 1f752a007b..472f31ac4b 100644 --- a/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs +++ b/src/Core/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommand.cs @@ -80,6 +80,8 @@ public class CreatePremiumCloudHostedSubscriptionCommand( return new BadRequest("Additional storage must be greater than 0."); } + var premiumPlan = await pricingClient.GetAvailablePremiumPlan(); + Customer? customer; /* @@ -107,7 +109,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand( customer = await ReconcileBillingLocationAsync(customer, billingAddress); - var subscription = await CreateSubscriptionAsync(user.Id, customer, additionalStorageGb > 0 ? additionalStorageGb : null); + var subscription = await CreateSubscriptionAsync(user.Id, customer, premiumPlan, additionalStorageGb > 0 ? additionalStorageGb : null); paymentMethod.Switch( tokenized => @@ -140,7 +142,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand( user.Gateway = GatewayType.Stripe; user.GatewayCustomerId = customer.Id; user.GatewaySubscriptionId = subscription.Id; - user.MaxStorageGb = (short)(1 + additionalStorageGb); + user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + additionalStorageGb); user.LicenseKey = CoreHelpers.SecureRandomString(20); user.RevisionDate = DateTime.UtcNow; @@ -304,9 +306,9 @@ public class CreatePremiumCloudHostedSubscriptionCommand( private async Task CreateSubscriptionAsync( Guid userId, Customer customer, + Pricing.Premium.Plan premiumPlan, int? storage) { - var premiumPlan = await pricingClient.GetAvailablePremiumPlan(); var subscriptionItemOptionsList = new List { diff --git a/src/Core/Billing/Pricing/Organizations/PlanAdapter.cs b/src/Core/Billing/Pricing/Organizations/PlanAdapter.cs index 37dc63cb47..42090a56ca 100644 --- a/src/Core/Billing/Pricing/Organizations/PlanAdapter.cs +++ b/src/Core/Billing/Pricing/Organizations/PlanAdapter.cs @@ -99,7 +99,7 @@ public record PlanAdapter : Core.Models.StaticStore.Plan _ => true); var baseSeats = GetBaseSeats(plan.Seats); var maxSeats = GetMaxSeats(plan.Seats); - var baseStorageGb = (short?)plan.Storage?.Provided; + var baseStorageGb = (short)(plan.Storage?.Provided ?? 0); var hasAdditionalStorageOption = plan.Storage != null; var additionalStoragePricePerGb = plan.Storage?.Price ?? 0; var stripeStoragePlanId = plan.Storage?.StripePriceId; diff --git a/src/Core/Billing/Pricing/Premium/Purchasable.cs b/src/Core/Billing/Pricing/Premium/Purchasable.cs index 633eb2e8aa..6bf69d9593 100644 --- a/src/Core/Billing/Pricing/Premium/Purchasable.cs +++ b/src/Core/Billing/Pricing/Premium/Purchasable.cs @@ -4,4 +4,5 @@ public class Purchasable { public string StripePriceId { get; init; } = null!; public decimal Price { get; init; } + public int Provided { get; init; } } diff --git a/src/Core/Billing/Pricing/PricingClient.cs b/src/Core/Billing/Pricing/PricingClient.cs index 1ec44c6496..ecb85ed7e8 100644 --- a/src/Core/Billing/Pricing/PricingClient.cs +++ b/src/Core/Billing/Pricing/PricingClient.cs @@ -6,7 +6,6 @@ using Bit.Core.Billing.Pricing.Organizations; using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Utilities; using Microsoft.Extensions.Logging; namespace Bit.Core.Billing.Pricing; @@ -28,13 +27,6 @@ public class PricingClient( return null; } - var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService); - - if (!usePricingService) - { - return StaticStore.GetPlan(planType); - } - var lookupKey = GetLookupKey(planType); if (lookupKey == null) @@ -77,13 +69,6 @@ public class PricingClient( return []; } - var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService); - - if (!usePricingService) - { - return StaticStore.Plans.ToList(); - } - var response = await httpClient.GetAsync("plans/organization"); if (response.IsSuccessStatusCode) @@ -114,11 +99,10 @@ public class PricingClient( return []; } - var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService); var fetchPremiumPriceFromPricingService = featureService.IsEnabled(FeatureFlagKeys.PM26793_FetchPremiumPriceFromPricingService); - if (!usePricingService || !fetchPremiumPriceFromPricingService) + if (!fetchPremiumPriceFromPricingService) { return [CurrentPremiumPlan]; } @@ -186,6 +170,6 @@ public class PricingClient( Available = true, LegacyYear = null, Seat = new Purchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually }, - Storage = new Purchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal } + Storage = new Purchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 } }; } diff --git a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs index 3170060de4..daa06b907a 100644 --- a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs +++ b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs @@ -101,7 +101,9 @@ public class PremiumUserBillingService( */ customer = await ReconcileBillingLocationAsync(customer, customerSetup.TaxInformation); - var subscription = await CreateSubscriptionAsync(user.Id, customer, storage); + var premiumPlan = await pricingClient.GetAvailablePremiumPlan(); + + var subscription = await CreateSubscriptionAsync(user.Id, customer, premiumPlan, storage); switch (customerSetup.TokenizedPaymentSource) { @@ -119,6 +121,7 @@ public class PremiumUserBillingService( user.Gateway = GatewayType.Stripe; user.GatewayCustomerId = customer.Id; user.GatewaySubscriptionId = subscription.Id; + user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + (storage ?? 0)); await userRepository.ReplaceAsync(user); } @@ -301,9 +304,9 @@ public class PremiumUserBillingService( private async Task CreateSubscriptionAsync( Guid userId, Customer customer, + Pricing.Premium.Plan premiumPlan, int? storage) { - var premiumPlan = await pricingClient.GetAvailablePremiumPlan(); var subscriptionItemOptionsList = new List { diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index d8602e2617..afabbe502f 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -163,6 +163,7 @@ public static class FeatureFlagKeys public const string RecoveryCodeSupportForSsoRequiredUsers = "pm-21153-recovery-code-support-for-sso-required"; public const string MJMLBasedEmailTemplates = "mjml-based-email-templates"; public const string MjmlWelcomeEmailTemplates = "mjml-welcome-email-templates"; + public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow"; /* Autofill Team */ public const string IdpAutoSubmitLogin = "idp-auto-submit-login"; @@ -184,7 +185,6 @@ public static class FeatureFlagKeys /* Billing Team */ public const string TrialPayment = "PM-8163-trial-payment"; - 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"; diff --git a/src/Core/Models/Business/CompleteSubscriptionUpdate.cs b/src/Core/Models/Business/CompleteSubscriptionUpdate.cs index 7473738ffc..aa49c25d36 100644 --- a/src/Core/Models/Business/CompleteSubscriptionUpdate.cs +++ b/src/Core/Models/Business/CompleteSubscriptionUpdate.cs @@ -299,7 +299,7 @@ public class CompleteSubscriptionUpdate : SubscriptionUpdate ? organization.SmServiceAccounts - plan.SecretsManager.BaseServiceAccount : 0, PurchasedAdditionalStorage = organization.MaxStorageGb.HasValue - ? organization.MaxStorageGb.Value - (plan.PasswordManager.BaseStorageGb ?? 0) : + ? organization.MaxStorageGb.Value - plan.PasswordManager.BaseStorageGb : 0 }; } diff --git a/src/Core/Models/Business/SubscriptionInfo.cs b/src/Core/Models/Business/SubscriptionInfo.cs index be514cb39f..68a060b4a8 100644 --- a/src/Core/Models/Business/SubscriptionInfo.cs +++ b/src/Core/Models/Business/SubscriptionInfo.cs @@ -1,4 +1,5 @@ using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Stripe; #nullable enable @@ -150,7 +151,7 @@ public class SubscriptionInfo } Quantity = (int)item.Quantity; - SponsoredSubscriptionItem = item.Plan != null && Utilities.StaticStore.SponsoredPlans.Any(p => p.StripePlanId == item.Plan.Id); + SponsoredSubscriptionItem = item.Plan != null && SponsoredPlans.All.Any(p => p.StripePlanId == item.Plan.Id); } public bool AddonSubscriptionItem { get; set; } diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 8cfd0a8df1..91504b0b9b 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; @@ -135,6 +136,8 @@ public static class OrganizationServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs index 2756f8930b..566c723692 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/CloudSyncSponsorshipsCommand.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -7,7 +8,6 @@ using Bit.Core.Models.Data.Organizations.OrganizationSponsorships; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; @@ -54,10 +54,9 @@ public class CloudSyncSponsorshipsCommand : ICloudSyncSponsorshipsCommand foreach (var selfHostedSponsorship in sponsorshipsData) { - var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(selfHostedSponsorship.PlanSponsorshipType)?.SponsoringProductTierType; + var requiredSponsoringProductType = SponsoredPlans.Get(selfHostedSponsorship.PlanSponsorshipType).SponsoringProductTierType; var sponsoringOrgProductTier = sponsoringOrg.PlanType.GetProductTier(); - if (requiredSponsoringProductType == null - || sponsoringOrgProductTier != requiredSponsoringProductType.Value) + if (sponsoringOrgProductTier != requiredSponsoringProductType) { continue; // prevent unsupported sponsorships } diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs index a54106481c..0aebc3fc3b 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SetUpSponsorshipCommand.cs @@ -1,11 +1,11 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; @@ -50,11 +50,10 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand } // Check org to sponsor's product type - var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductTierType; + var requiredSponsoredProductType = SponsoredPlans.Get(sponsorship.PlanSponsorshipType.Value).SponsoredProductTierType; var sponsoredOrganizationProductTier = sponsoredOrganization.PlanType.GetProductTier(); - if (requiredSponsoredProductType == null || - sponsoredOrganizationProductTier != requiredSponsoredProductType.Value) + if (sponsoredOrganizationProductTier != requiredSponsoredProductType) { throw new BadRequestException("Can only redeem sponsorship offer on families organizations."); } diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs index dcda77acea..a26d553570 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateSponsorshipCommand.cs @@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; @@ -95,7 +96,7 @@ public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSpo return false; } - var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(existingSponsorship.PlanSponsorshipType.Value); + var sponsoredPlan = SponsoredPlans.Get(existingSponsorship.PlanSponsorshipType.Value); var sponsoringOrganization = await _organizationRepository .GetByIdAsync(existingSponsorship.SponsoringOrganizationId.Value); diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs index a729937fad..ab4b17d215 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/CreateSponsorshipCommand.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Models; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -7,7 +8,6 @@ using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; @@ -34,11 +34,10 @@ public class CreateSponsorshipCommand( throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email."); } - var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductTierType; + var requiredSponsoringProductType = SponsoredPlans.Get(sponsorshipType).SponsoringProductTierType; var sponsoringOrgProductTier = sponsoringOrganization.PlanType.GetProductTier(); - if (requiredSponsoringProductType == null || - sponsoringOrgProductTier != requiredSponsoringProductType.Value) + if (sponsoringOrgProductTier != requiredSponsoringProductType) { throw new BadRequestException("Specified Organization cannot sponsor other organizations."); } diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs index 2b39e6cca6..b704cb0460 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs @@ -254,9 +254,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand organization.UseApi = newPlan.HasApi; organization.SelfHost = newPlan.HasSelfHost; organization.UsePolicies = newPlan.HasPolicies; - organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue - ? (short?)null - : (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb); + organization.MaxStorageGb = (short)(newPlan.PasswordManager.BaseStorageGb + upgrade.AdditionalStorageGb); organization.UseGroups = newPlan.HasGroups; organization.UseDirectory = newPlan.HasDirectory; organization.UseEvents = newPlan.HasEvents; diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 5dd1ff50e7..4c64abc73e 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -67,7 +67,7 @@ public class StripePaymentService : IPaymentService { var existingPlan = await _pricingClient.GetPlanOrThrow(org.PlanType); var sponsoredPlan = sponsorship?.PlanSponsorshipType != null - ? Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) + ? SponsoredPlans.Get(sponsorship.PlanSponsorshipType.Value) : null; var subscriptionUpdate = new SponsorOrganizationSubscriptionUpdate(existingPlan, sponsoredPlan, applySponsorship); @@ -1072,7 +1072,7 @@ public class StripePaymentService : IPaymentService if (isSponsored) { - var sponsoredPlan = Utilities.StaticStore.GetSponsoredPlan(parameters.PasswordManager.SponsoredPlan.Value); + var sponsoredPlan = SponsoredPlans.Get(parameters.PasswordManager.SponsoredPlan.Value); options.SubscriptionDetails.Items.Add( new InvoiceSubscriptionDetailsItemOptions { Quantity = 1, Plan = sponsoredPlan.StripePlanId } ); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index daf1b2078d..57b69deb71 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -904,7 +904,6 @@ public class UserService : UserManager, IUserService } else { - user.MaxStorageGb = (short)(1 + additionalStorageGb); user.LicenseKey = CoreHelpers.SecureRandomString(20); } @@ -977,7 +976,8 @@ public class UserService : UserManager, IUserService var premiumPlan = await _pricingClient.GetAvailablePremiumPlan(); - var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, premiumPlan.Storage.StripePriceId); + var baseStorageGb = (short)premiumPlan.Storage.Provided; + var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, premiumPlan.Storage.StripePriceId, baseStorageGb); await SaveUserAsync(user); return secret; } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index e2c2168656..4bbf80e6ba 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -21,7 +21,7 @@ public class GlobalSettings : IGlobalSettings } public bool SelfHosted { get; set; } - public bool UnifiedDeployment { get; set; } + public bool LiteDeployment { get; set; } public virtual string KnownProxies { get; set; } public virtual string SiteName { get; set; } public virtual string ProjectName { get; set; } diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index d77842373e..0fc99d63e3 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -6,7 +6,7 @@ public interface IGlobalSettings { // This interface exists for testing. Add settings here as needed for testing bool SelfHosted { get; set; } - bool UnifiedDeployment { get; set; } + bool LiteDeployment { get; set; } string KnownProxies { get; set; } string ProjectName { get; set; } bool EnableCloudCommunication { get; set; } diff --git a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs index c7f7e3aff7..fa558f5963 100644 --- a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs +++ b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs @@ -150,17 +150,34 @@ public class ImportCiphersCommand : IImportCiphersCommand foreach (var collection in collections) { - if (!organizationCollectionsIds.Contains(collection.Id)) + // If the collection already exists, skip it + if (organizationCollectionsIds.Contains(collection.Id)) { - collection.SetNewId(); - newCollections.Add(collection); - newCollectionUsers.Add(new CollectionUser - { - CollectionId = collection.Id, - OrganizationUserId = importingOrgUser.Id, - Manage = true - }); + continue; } + + // Create new collections if not already present + collection.SetNewId(); + newCollections.Add(collection); + + /* + * If the organization was created by a Provider, the organization may have zero members (users) + * In this situation importingOrgUser will be null, and accessing importingOrgUser.Id will + * result in a null reference exception. + * + * Avoid user assignment, but proceed with adding the collection. + */ + if (importingOrgUser == null) + { + continue; + } + + newCollectionUsers.Add(new CollectionUser + { + CollectionId = collection.Id, + OrganizationUserId = importingOrgUser.Id, + Manage = true + }); } // Create associations based on the newly assigned ids diff --git a/src/Core/Utilities/BillingHelpers.cs b/src/Core/Utilities/BillingHelpers.cs index e7ccfc3547..2c1dfcbbbd 100644 --- a/src/Core/Utilities/BillingHelpers.cs +++ b/src/Core/Utilities/BillingHelpers.cs @@ -7,7 +7,7 @@ namespace Bit.Core.Utilities; public static class BillingHelpers { internal static async Task AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber, - short storageAdjustmentGb, string storagePlanId) + short storageAdjustmentGb, string storagePlanId, short baseStorageGb) { if (storableSubscriber == null) { @@ -30,9 +30,9 @@ public static class BillingHelpers } var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb); - if (newStorageGb < 1) + if (newStorageGb < baseStorageGb) { - newStorageGb = 1; + newStorageGb = baseStorageGb; } if (newStorageGb > 100) @@ -48,7 +48,7 @@ public static class BillingHelpers "Delete some stored data first."); } - var additionalStorage = newStorageGb - 1; + var additionalStorage = newStorageGb - baseStorageGb; var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber, additionalStorage, storagePlanId); storableSubscriber.MaxStorageGb = newStorageGb; diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index 36c4a54ae4..f0fbd80c38 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -1,13 +1,7 @@ // FIXME: Update this file to be null safe and then delete the line below #nullable disable -using System.Collections.Immutable; -using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Extensions; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Enums; -using Bit.Core.Models.Data.Organizations.OrganizationUsers; -using Bit.Core.Models.StaticStore; namespace Bit.Core.Utilities; @@ -110,56 +104,7 @@ public static class StaticStore GlobalDomains.Add(GlobalEquivalentDomainsType.Atlassian, new List { "atlassian.com", "bitbucket.org", "trello.com", "statuspage.io", "atlassian.net", "jira.com" }); GlobalDomains.Add(GlobalEquivalentDomainsType.Pinterest, new List { "pinterest.com", "pinterest.com.au", "pinterest.cl", "pinterest.de", "pinterest.dk", "pinterest.es", "pinterest.fr", "pinterest.co.uk", "pinterest.jp", "pinterest.co.kr", "pinterest.nz", "pinterest.pt", "pinterest.se" }); #endregion - - Plans = new List - { - new EnterprisePlan(true), - new EnterprisePlan(false), - new TeamsStarterPlan(), - new TeamsPlan(true), - new TeamsPlan(false), - - new Enterprise2023Plan(true), - new Enterprise2023Plan(false), - new Enterprise2020Plan(true), - new Enterprise2020Plan(false), - new TeamsStarterPlan2023(), - new Teams2023Plan(true), - new Teams2023Plan(false), - new Teams2020Plan(true), - new Teams2020Plan(false), - new FamiliesPlan(), - new FreePlan(), - new CustomPlan(), - - new Enterprise2019Plan(true), - new Enterprise2019Plan(false), - new Teams2019Plan(true), - new Teams2019Plan(false), - new Families2019Plan(), - new Families2025Plan() - }.ToImmutableList(); } public static IDictionary> GlobalDomains { get; set; } - [Obsolete("Use PricingClient.ListPlans to retrieve all plans.")] - public static IEnumerable Plans { get; } - public static IEnumerable SponsoredPlans { get; set; } = new[] - { - new SponsoredPlan - { - PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise, - SponsoredProductTierType = ProductTierType.Families, - SponsoringProductTierType = ProductTierType.Enterprise, - StripePlanId = "2021-family-for-enterprise-annually", - UsersCanSponsor = (OrganizationUserOrganizationDetails org) => - org.PlanType.GetProductTier() == ProductTierType.Enterprise, - } - }; - - [Obsolete("Use PricingClient.GetPlan to retrieve a plan.")] - public static Plan GetPlan(PlanType planType) => Plans.SingleOrDefault(p => p.Type == planType); - - public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) => - SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType); } diff --git a/src/Core/Vault/Services/ICipherService.cs b/src/Core/Vault/Services/ICipherService.cs index 110d4b6ea4..765dae30c1 100644 --- a/src/Core/Vault/Services/ICipherService.cs +++ b/src/Core/Vault/Services/ICipherService.cs @@ -17,7 +17,7 @@ public interface ICipherService Task CreateAttachmentAsync(Cipher cipher, Stream stream, string fileName, string key, long requestLength, Guid savingUserId, bool orgAdmin = false, DateTime? lastKnownRevisionDate = null); Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, string key, long requestLength, - string attachmentId, Guid organizationShareId, DateTime? lastKnownRevisionDate = null); + string attachmentId, Guid organizationShareId); Task DeleteAsync(CipherDetails cipherDetails, Guid deletingUserId, bool orgAdmin = false); Task DeleteManyAsync(IEnumerable cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false); Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false); @@ -34,7 +34,7 @@ public interface ICipherService Task SoftDeleteManyAsync(IEnumerable cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false); Task RestoreAsync(CipherDetails cipherDetails, Guid restoringUserId, bool orgAdmin = false); Task> RestoreManyAsync(IEnumerable cipherIds, Guid restoringUserId, Guid? organizationId = null, bool orgAdmin = false); - Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentId, DateTime? lastKnownRevisionDate = null); + Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentId); Task GetAttachmentDownloadDataAsync(Cipher cipher, string attachmentId); Task ValidateCipherAttachmentFile(Cipher cipher, CipherAttachment.MetaData attachmentData); Task ValidateBulkCollectionAssignmentAsync(IEnumerable collectionIds, IEnumerable cipherIds, Guid userId); diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index 4e980f66b6..cbf4ec81e3 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -183,9 +183,8 @@ public class CipherService : ICipherService } } - public async Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachment, DateTime? lastKnownRevisionDate = null) + public async Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachment) { - ValidateCipherLastKnownRevisionDate(cipher, lastKnownRevisionDate); if (attachment == null) { throw new BadRequestException("Cipher attachment does not exist"); @@ -290,11 +289,10 @@ public class CipherService : ICipherService } public async Task CreateAttachmentShareAsync(Cipher cipher, Stream stream, string fileName, string key, - long requestLength, string attachmentId, Guid organizationId, DateTime? lastKnownRevisionDate = null) + long requestLength, string attachmentId, Guid organizationId) { try { - ValidateCipherLastKnownRevisionDate(cipher, lastKnownRevisionDate); if (requestLength < 1) { throw new BadRequestException("No data to attach."); diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs index ed5708844d..af52021ca7 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -2,6 +2,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.Utilities.DebuggingInstruments; using Bit.Core.Entities; @@ -671,7 +672,7 @@ public class OrganizationUserRepository : Repository, IO commandType: CommandType.StoredProcedure); } - public async Task ConfirmOrganizationUserAsync(OrganizationUser organizationUser) + public async Task ConfirmOrganizationUserAsync(AcceptedOrganizationUserToConfirm organizationUserToConfirm) { await using var connection = new SqlConnection(_marsConnectionString); @@ -679,10 +680,10 @@ public class OrganizationUserRepository : Repository, IO $"[{Schema}].[OrganizationUser_ConfirmById]", new { - organizationUser.Id, - organizationUser.UserId, + Id = organizationUserToConfirm.OrganizationUserId, + UserId = organizationUserToConfirm.UserId, RevisionDate = DateTime.UtcNow.Date, - Key = organizationUser.Key + Key = organizationUserToConfirm.Key }); return rowCount > 0; diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs index e5016a20d4..fd31b1f0dc 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -3,6 +3,7 @@ using AutoMapper; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -943,23 +944,24 @@ public class OrganizationUserRepository : Repository ConfirmOrganizationUserAsync(Core.Entities.OrganizationUser organizationUser) + public async Task ConfirmOrganizationUserAsync(AcceptedOrganizationUserToConfirm organizationUserToConfirm) { 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) + .Where(ou => ou.Id == organizationUserToConfirm.OrganizationUserId + && ou.Status == OrganizationUserStatusType.Accepted) .ExecuteUpdateAsync(x => x .SetProperty(y => y.Status, OrganizationUserStatusType.Confirmed) - .SetProperty(y => y.Key, organizationUser.Key)); + .SetProperty(y => y.Key, organizationUserToConfirm.Key)); if (result <= 0) { return false; } - await dbContext.UserBumpAccountRevisionDateByOrganizationUserIdAsync(organizationUser.Id); + await dbContext.UserBumpAccountRevisionDateByOrganizationUserIdAsync(organizationUserToConfirm.OrganizationUserId); return true; } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs index 1cca7a9bbb..894fb255be 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs @@ -217,7 +217,7 @@ public class PolicyRepository : Repository new OrganizationPolicyDetails { diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 78b8a61015..a0c064a2b1 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -645,7 +645,7 @@ public static class ServiceCollectionExtensions ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }; - if (!globalSettings.UnifiedDeployment) + if (!globalSettings.LiteDeployment) { // Trust the X-Forwarded-Host header of the nginx docker container try diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index e1ad6863af..c3c6597cfd 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -60,6 +60,7 @@ CREATE TABLE [dbo].[Organization] ( [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), + [MaxStorageGbIncreased] SMALLINT NULL, 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 dc772ff1a7..854fe34f4a 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -45,6 +45,7 @@ [SecurityState] VARCHAR (MAX) NULL, [SecurityVersion] INT NULL, [SignedPublicKey] VARCHAR (MAX) NULL, + [MaxStorageGbIncreased] SMALLINT 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 a7e1db6e81..564bb71ca9 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -24,7 +24,7 @@ SELECT O.[UseSecretsManager], O.[Seats], O.[MaxCollections], - O.[MaxStorageGb], + COALESCE(O.[MaxStorageGbIncreased], O.[MaxStorageGb]) AS [MaxStorageGb], O.[Identifier], OU.[Key], OU.[ResetPasswordKey], diff --git a/src/Sql/dbo/Views/OrganizationView.sql b/src/Sql/dbo/Views/OrganizationView.sql index 58989273fd..9cb4eb72a1 100644 --- a/src/Sql/dbo/Views/OrganizationView.sql +++ b/src/Sql/dbo/Views/OrganizationView.sql @@ -1,6 +1,66 @@ CREATE VIEW [dbo].[OrganizationView] AS SELECT - * + [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], + COALESCE([MaxStorageGbIncreased], [MaxStorageGb]) AS [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], + [LimitItemDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [UseOrganizationDomains], + [UseAdminSponsoredFamilies], + [SyncSeats], + [UseAutomaticUserConfirmation] FROM [dbo].[Organization] diff --git a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql index 42e877ab15..27c28f8e71 100644 --- a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql @@ -23,7 +23,7 @@ SELECT O.[UseCustomPermissions], O.[Seats], O.[MaxCollections], - O.[MaxStorageGb], + COALESCE(O.[MaxStorageGbIncreased], O.[MaxStorageGb]) AS [MaxStorageGb], O.[Identifier], PO.[Key], O.[PublicKey], diff --git a/src/Sql/dbo/Views/UserView.sql b/src/Sql/dbo/Views/UserView.sql index 82fa8a2c63..fa8dbf334b 100644 --- a/src/Sql/dbo/Views/UserView.sql +++ b/src/Sql/dbo/Views/UserView.sql @@ -1,6 +1,51 @@ CREATE VIEW [dbo].[UserView] AS SELECT - * + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + COALESCE([MaxStorageGbIncreased], [MaxStorageGb]) AS [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [ApiKey], + [Kdf], + [KdfIterations], + [KdfMemory], + [KdfParallelism], + [CreationDate], + [RevisionDate], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate], + [VerifyDevices], + [SecurityState], + [SecurityVersion], + [SignedPublicKey] FROM [dbo].[User] diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerAutoConfirmTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerAutoConfirmTests.cs new file mode 100644 index 0000000000..8df1fcaf2b --- /dev/null +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerAutoConfirmTests.cs @@ -0,0 +1,225 @@ +using System.Net; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.Helpers; +using Bit.Core; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Enums; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.Services; +using NSubstitute; +using Xunit; + +namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; + +public class OrganizationUserControllerAutoConfirmTests : IClassFixture, IAsyncLifetime +{ + private const string _mockEncryptedString = "2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk="; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + private readonly LoginHelper _loginHelper; + + private string _ownerEmail = null!; + + public OrganizationUserControllerAutoConfirmTests(ApiApplicationFactory apiFactory) + { + _factory = apiFactory; + _factory.SubstituteService(featureService => + { + featureService + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + }); + _client = _factory.CreateClient(); + _loginHelper = new LoginHelper(_factory, _client); + } + + public async Task InitializeAsync() + { + _ownerEmail = $"org-owner-{Guid.NewGuid()}@example.com"; + await _factory.LoginWithNewAccount(_ownerEmail); + } + + [Fact] + public async Task AutoConfirm_WhenUserCannotManageOtherUsers_ThenShouldReturnForbidden() + { + var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, + ownerEmail: _ownerEmail, passwordManagerSeats: 5, paymentMethod: PaymentMethodType.Card); + + organization.UseAutomaticUserConfirmation = true; + + await _factory.GetService() + .UpsertAsync(organization); + + var testKey = $"test-key-{Guid.NewGuid()}"; + + var userToConfirmEmail = $"org-user-to-confirm-{Guid.NewGuid()}@example.com"; + await _factory.LoginWithNewAccount(userToConfirmEmail); + + var (confirmingUserEmail, _) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, organization.Id, OrganizationUserType.User); + await _loginHelper.LoginAsync(confirmingUserEmail); + + var organizationUser = await OrganizationTestHelpers.CreateUserAsync( + _factory, + organization.Id, + userToConfirmEmail, + OrganizationUserType.User, + false, + new Permissions { ManageUsers = false }, + OrganizationUserStatusType.Accepted); + + var result = await _client.PostAsJsonAsync($"organizations/{organization.Id}/users/{organizationUser.Id}/auto-confirm", + new OrganizationUserConfirmRequestModel + { + Key = testKey, + DefaultUserCollectionName = _mockEncryptedString + }); + + Assert.Equal(HttpStatusCode.Forbidden, result.StatusCode); + + await _factory.GetService().DeleteAsync(organization); + } + + [Fact] + public async Task AutoConfirm_WhenOwnerConfirmsValidUser_ThenShouldReturnNoContent() + { + var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, + ownerEmail: _ownerEmail, passwordManagerSeats: 5, paymentMethod: PaymentMethodType.Card); + + organization.UseAutomaticUserConfirmation = true; + + await _factory.GetService() + .UpsertAsync(organization); + + var testKey = $"test-key-{Guid.NewGuid()}"; + + await _factory.GetService().CreateAsync(new Policy + { + OrganizationId = organization.Id, + Type = PolicyType.AutomaticUserConfirmation, + Enabled = true + }); + + await _factory.GetService().CreateAsync(new Policy + { + OrganizationId = organization.Id, + Type = PolicyType.OrganizationDataOwnership, + Enabled = true + }); + + var userToConfirmEmail = $"org-user-to-confirm-{Guid.NewGuid()}@example.com"; + await _factory.LoginWithNewAccount(userToConfirmEmail); + + await _loginHelper.LoginAsync(_ownerEmail); + var organizationUser = await OrganizationTestHelpers.CreateUserAsync( + _factory, + organization.Id, + userToConfirmEmail, + OrganizationUserType.User, + false, + new Permissions(), + OrganizationUserStatusType.Accepted); + + var result = await _client.PostAsJsonAsync($"organizations/{organization.Id}/users/{organizationUser.Id}/auto-confirm", + new OrganizationUserConfirmRequestModel + { + Key = testKey, + DefaultUserCollectionName = _mockEncryptedString + }); + + Assert.Equal(HttpStatusCode.NoContent, result.StatusCode); + + var orgUserRepository = _factory.GetService(); + var confirmedUser = await orgUserRepository.GetByIdAsync(organizationUser.Id); + Assert.NotNull(confirmedUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, confirmedUser.Status); + Assert.Equal(testKey, confirmedUser.Key); + + var collectionRepository = _factory.GetService(); + var collections = await collectionRepository.GetManyByUserIdAsync(organizationUser.UserId!.Value); + + Assert.NotEmpty(collections); + Assert.Single(collections.Where(c => c.Type == CollectionType.DefaultUserCollection)); + + await _factory.GetService().DeleteAsync(organization); + } + + [Fact] + public async Task AutoConfirm_WhenUserIsConfirmedMultipleTimes_ThenShouldSuccessAndOnlyConfirmOneUser() + { + var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, + ownerEmail: _ownerEmail, passwordManagerSeats: 5, paymentMethod: PaymentMethodType.Card); + + organization.UseAutomaticUserConfirmation = true; + + await _factory.GetService() + .UpsertAsync(organization); + + var testKey = $"test-key-{Guid.NewGuid()}"; + + var userToConfirmEmail = $"org-user-to-confirm-{Guid.NewGuid()}@example.com"; + await _factory.LoginWithNewAccount(userToConfirmEmail); + + await _factory.GetService().CreateAsync(new Policy + { + OrganizationId = organization.Id, + Type = PolicyType.AutomaticUserConfirmation, + Enabled = true + }); + + await _factory.GetService().CreateAsync(new Policy + { + OrganizationId = organization.Id, + Type = PolicyType.OrganizationDataOwnership, + Enabled = true + }); + + await _loginHelper.LoginAsync(_ownerEmail); + + var organizationUser = await OrganizationTestHelpers.CreateUserAsync( + _factory, + organization.Id, + userToConfirmEmail, + OrganizationUserType.User, + false, + new Permissions(), + OrganizationUserStatusType.Accepted); + + var tenRequests = Enumerable.Range(0, 10) + .Select(_ => _client.PostAsJsonAsync($"organizations/{organization.Id}/users/{organizationUser.Id}/auto-confirm", + new OrganizationUserConfirmRequestModel + { + Key = testKey, + DefaultUserCollectionName = _mockEncryptedString + })).ToList(); + + var results = await Task.WhenAll(tenRequests); + + Assert.Contains(results, r => r.StatusCode == HttpStatusCode.NoContent); + + var orgUserRepository = _factory.GetService(); + var confirmedUser = await orgUserRepository.GetByIdAsync(organizationUser.Id); + Assert.NotNull(confirmedUser); + Assert.Equal(OrganizationUserStatusType.Confirmed, confirmedUser.Status); + Assert.Equal(testKey, confirmedUser.Key); + + var collections = await _factory.GetService() + .GetManyByUserIdAsync(organizationUser.UserId!.Value); + Assert.NotEmpty(collections); + // validates user only received one default collection + Assert.Single(collections.Where(c => c.Type == CollectionType.DefaultUserCollection)); + + await _factory.GetService().DeleteAsync(organization); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } +} diff --git a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerTests.cs b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerTests.cs index 7c61a88bd8..0fef4a0cd0 100644 --- a/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Controllers/OrganizationUserControllerTests.cs @@ -218,7 +218,7 @@ public class OrganizationUserControllerTests : IClassFixture, IAsy await _factory.LoginWithNewAccount(_ownerEmail); // Create the organization - (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023, + (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card); // Authorize with the organization api key diff --git a/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs b/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs index 0b5ab660b9..6144d7eebb 100644 --- a/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs +++ b/test/Api.IntegrationTest/AdminConsole/Public/Controllers/PoliciesControllerTests.cs @@ -39,7 +39,7 @@ public class PoliciesControllerTests : IClassFixture, IAs await _factory.LoginWithNewAccount(_ownerEmail); // Create the organization - (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023, + (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card); // Authorize with the organization api key diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs index 5875cda05a..ae14001223 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs @@ -9,10 +9,12 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.AccountRecovery; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Utilities.v2.Results; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Repositories; using Bit.Core.Context; @@ -33,9 +35,11 @@ using Bit.Test.Common.AutoFixture.Attributes; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc.ModelBinding; using NSubstitute; +using OneOf.Types; using Xunit; namespace Bit.Api.Test.AdminConsole.Controllers; @@ -476,7 +480,7 @@ public class OrganizationUsersControllerTests var result = await sutProvider.Sut.PutResetPassword(orgId, orgUserId, model); - Assert.IsType(result); + Assert.IsType(result); } [Theory] @@ -506,7 +510,7 @@ public class OrganizationUsersControllerTests var result = await sutProvider.Sut.PutResetPassword(orgId, orgUserId, model); - Assert.IsType(result); + Assert.IsType(result); } [Theory] @@ -521,7 +525,7 @@ public class OrganizationUsersControllerTests var result = await sutProvider.Sut.PutResetPassword(orgId, orgUserId, model); - Assert.IsType(result); + Assert.IsType(result); } [Theory] @@ -594,4 +598,190 @@ public class OrganizationUsersControllerTests Assert.IsType>(result); } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_UserIdNull_ReturnsUnauthorized( + Guid orgId, + Guid orgUserId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns((Guid?)null); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + Assert.IsType(result); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_UserIdEmpty_ReturnsUnauthorized( + Guid orgId, + Guid orgUserId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(Guid.Empty); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + Assert.IsType(result); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_Success_ReturnsOk( + Guid orgId, + Guid orgUserId, + Guid userId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(userId); + + sutProvider.GetDependency() + .OrganizationOwner(orgId) + .Returns(true); + + sutProvider.GetDependency() + .AutomaticallyConfirmOrganizationUserAsync(Arg.Any()) + .Returns(new CommandResult(new None())); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + Assert.IsType(result); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_NotFoundError_ReturnsNotFound( + Guid orgId, + Guid orgUserId, + Guid userId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(userId); + + sutProvider.GetDependency() + .OrganizationOwner(orgId) + .Returns(false); + + var notFoundError = new OrganizationNotFound(); + sutProvider.GetDependency() + .AutomaticallyConfirmOrganizationUserAsync(Arg.Any()) + .Returns(new CommandResult(notFoundError)); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + var notFoundResult = Assert.IsType>(result); + Assert.Equal(notFoundError.Message, notFoundResult.Value.Message); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_BadRequestError_ReturnsBadRequest( + Guid orgId, + Guid orgUserId, + Guid userId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(userId); + + sutProvider.GetDependency() + .OrganizationOwner(orgId) + .Returns(true); + + var badRequestError = new UserIsNotAccepted(); + sutProvider.GetDependency() + .AutomaticallyConfirmOrganizationUserAsync(Arg.Any()) + .Returns(new CommandResult(badRequestError)); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + var badRequestResult = Assert.IsType>(result); + Assert.Equal(badRequestError.Message, badRequestResult.Value.Message); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_InternalError_ReturnsProblem( + Guid orgId, + Guid orgUserId, + Guid userId, + OrganizationUserConfirmRequestModel model, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers) + .Returns(true); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(userId); + + sutProvider.GetDependency() + .OrganizationOwner(orgId) + .Returns(true); + + var internalError = new FailedToWriteToEventLog(); + sutProvider.GetDependency() + .AutomaticallyConfirmOrganizationUserAsync(Arg.Any()) + .Returns(new CommandResult(internalError)); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(orgId, orgUserId, model); + + // Assert + var problemResult = Assert.IsType>(result); + Assert.Equal(StatusCodes.Status500InternalServerError, problemResult.StatusCode); + } } diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index 00fd3c3b4e..f999dd520e 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -30,8 +30,8 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks; using Bit.Core.Tokens; -using Bit.Core.Utilities; using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider; using NSubstitute; using Xunit; @@ -305,7 +305,7 @@ public class OrganizationsControllerTests : IDisposable // Arrange _currentContext.OrganizationOwner(organization.Id).Returns(true); - var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + var plan = MockPlans.Get(PlanType.EnterpriseAnnually); _pricingClient.GetPlan(Arg.Any()).Returns(plan); _organizationService diff --git a/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs index 2ad7686c30..87334dc085 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationSponsorshipsControllerTests.cs @@ -10,7 +10,7 @@ using Bit.Core.Models.Data; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -24,11 +24,11 @@ namespace Bit.Api.Test.Billing.Controllers; public class OrganizationSponsorshipsControllerTests { public static IEnumerable EnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonEnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonFamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p }); public static IEnumerable NonConfirmedOrganizationUsersStatuses => Enum.GetValues() diff --git a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs index 75bd13eae8..f59fce4011 100644 --- a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs @@ -17,7 +17,7 @@ using Bit.Core.Context; using Bit.Core.Models.Api; using Bit.Core.Models.BitStripe; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; @@ -351,7 +351,7 @@ public class ProviderBillingControllerTests foreach (var providerPlan in providerPlans) { - var plan = StaticStore.GetPlan(providerPlan.PlanType); + var plan = MockPlans.Get(providerPlan.PlanType); sutProvider.GetDependency().GetPlanOrThrow(providerPlan.PlanType).Returns(plan); var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType); sutProvider.GetDependency().PriceGetAsync(priceId) @@ -372,7 +372,7 @@ public class ProviderBillingControllerTests Assert.Equal(subscription.Customer!.Discount!.Coupon!.PercentOff, response.DiscountPercentage); Assert.Equal(subscription.CollectionMethod, response.CollectionMethod); - var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsPlan = MockPlans.Get(PlanType.TeamsMonthly); var providerTeamsPlan = response.Plans.FirstOrDefault(plan => plan.PlanName == teamsPlan.Name); Assert.NotNull(providerTeamsPlan); Assert.Equal(50, providerTeamsPlan.SeatMinimum); @@ -381,7 +381,7 @@ public class ProviderBillingControllerTests Assert.Equal(60 * teamsPlan.PasswordManager.ProviderPortalSeatPrice, providerTeamsPlan.Cost); Assert.Equal("Monthly", providerTeamsPlan.Cadence); - var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var enterprisePlan = MockPlans.Get(PlanType.EnterpriseMonthly); var providerEnterprisePlan = response.Plans.FirstOrDefault(plan => plan.PlanName == enterprisePlan.Name); Assert.NotNull(providerEnterprisePlan); Assert.Equal(100, providerEnterprisePlan.SeatMinimum); @@ -498,7 +498,7 @@ public class ProviderBillingControllerTests foreach (var providerPlan in providerPlans) { - var plan = StaticStore.GetPlan(providerPlan.PlanType); + var plan = MockPlans.Get(providerPlan.PlanType); sutProvider.GetDependency().GetPlanOrThrow(providerPlan.PlanType).Returns(plan); var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, providerPlan.PlanType); sutProvider.GetDependency().PriceGetAsync(priceId) diff --git a/test/Api.Test/SecretsManager/Controllers/ServiceAccountsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/ServiceAccountsControllerTests.cs index 78224a8bd8..5d3b7f2fa5 100644 --- a/test/Api.Test/SecretsManager/Controllers/ServiceAccountsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/ServiceAccountsControllerTests.cs @@ -16,7 +16,7 @@ using Bit.Core.SecretsManager.Models.Data; using Bit.Core.SecretsManager.Queries.ServiceAccounts.Interfaces; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -121,7 +121,7 @@ public class ServiceAccountsControllerTests { ArrangeCreateServiceAccountAutoScalingTest(newSlotsRequired, sutProvider, data, organization); - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); await sutProvider.Sut.CreateAsync(organization.Id, data); diff --git a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs index a46eba283d..e6d34592c7 100644 --- a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs @@ -18,9 +18,9 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks; using Bit.Core.Tools.Entities; using Bit.Core.Tools.Repositories; -using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Repositories; @@ -335,7 +335,7 @@ public class SyncControllerTests if (matchedProviderUserOrgDetails != null) { - var providerOrgProductType = StaticStore.GetPlan(matchedProviderUserOrgDetails.PlanType).ProductTier; + var providerOrgProductType = MockPlans.Get(matchedProviderUserOrgDetails.PlanType).ProductTier; Assert.Equal(providerOrgProductType, profProviderOrg.ProductTierType); } } diff --git a/test/Billing.Test/Billing.Test.csproj b/test/Billing.Test/Billing.Test.csproj index 4d7f887c90..84443753ce 100644 --- a/test/Billing.Test/Billing.Test.csproj +++ b/test/Billing.Test/Billing.Test.csproj @@ -24,6 +24,7 @@ + diff --git a/test/Billing.Test/Services/ProviderEventServiceTests.cs b/test/Billing.Test/Services/ProviderEventServiceTests.cs index d5f273fa65..34c69b95c2 100644 --- a/test/Billing.Test/Services/ProviderEventServiceTests.cs +++ b/test/Billing.Test/Services/ProviderEventServiceTests.cs @@ -9,7 +9,7 @@ using Bit.Core.Billing.Providers.Entities; using Bit.Core.Billing.Providers.Repositories; using Bit.Core.Enums; using Bit.Core.Repositories; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using NSubstitute; using Stripe; using Xunit; @@ -237,7 +237,7 @@ public class ProviderEventServiceTests foreach (var providerPlan in providerPlans) { - _pricingClient.GetPlanOrThrow(providerPlan.PlanType).Returns(StaticStore.GetPlan(providerPlan.PlanType)); + _pricingClient.GetPlanOrThrow(providerPlan.PlanType).Returns(MockPlans.Get(providerPlan.PlanType)); } _providerPlanRepository.GetByProviderId(providerId).Returns(providerPlans); @@ -246,8 +246,8 @@ public class ProviderEventServiceTests await _providerEventService.TryRecordInvoiceLineItems(stripeEvent); // Assert - var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); - var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var teamsPlan = MockPlans.Get(PlanType.TeamsMonthly); + var enterprisePlan = MockPlans.Get(PlanType.EnterpriseMonthly); await _providerInvoiceItemRepository.Received(1).CreateAsync(Arg.Is( options => diff --git a/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs b/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs index 16287bc5c9..83ebd4aaa7 100644 --- a/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs +++ b/test/Billing.Test/Services/SubscriptionUpdatedHandlerTests.cs @@ -8,11 +8,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Pricing; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using NSubstitute; diff --git a/test/Billing.Test/Services/UpcomingInvoiceHandlerTests.cs b/test/Billing.Test/Services/UpcomingInvoiceHandlerTests.cs index 5ac77eb42a..89c926ee31 100644 --- a/test/Billing.Test/Services/UpcomingInvoiceHandlerTests.cs +++ b/test/Billing.Test/Services/UpcomingInvoiceHandlerTests.cs @@ -5,7 +5,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Payment.Queries; using Bit.Core.Billing.Pricing; @@ -16,6 +15,7 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri using Bit.Core.Platform.Mail.Mailer; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Microsoft.Extensions.Logging; using NSubstitute; using NSubstitute.ExceptionExtensions; @@ -1141,7 +1141,7 @@ public class UpcomingInvoiceHandlerTests } [Fact] - public async Task HandleAsync_WhenMilestone3Disabled_DoesNotUpdateSubscription() + public async Task HandleAsync_WhenMilestone3Disabled_AndFamilies2019Plan_DoesNotUpdateSubscription() { // Arrange var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; @@ -1469,4 +1469,490 @@ public class UpcomingInvoiceHandlerTests email.ToEmails.Contains("org@example.com") && email.Subject == "Your Subscription Will Renew Soon")); } + + [Fact] + public async Task HandleAsync_WhenMilestone3Enabled_AndSeatAddOnExists_DeletesItem() + { + // Arrange + var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; + var customerId = "cus_123"; + var subscriptionId = "sub_123"; + var passwordManagerItemId = "si_pm_123"; + var seatAddOnItemId = "si_seat_123"; + + var invoice = new Invoice + { + CustomerId = customerId, + AmountDue = 40000, + NextPaymentAttempt = DateTime.UtcNow.AddDays(7), + Lines = new StripeList + { + Data = new List { new() { Description = "Test Item" } } + } + }; + + var families2019Plan = new Families2019Plan(); + var familiesPlan = new FamiliesPlan(); + + var subscription = new Subscription + { + Id = subscriptionId, + CustomerId = customerId, + Items = new StripeList + { + Data = new List + { + new() + { + Id = passwordManagerItemId, + Price = new Price { Id = families2019Plan.PasswordManager.StripePlanId } + }, + new() + { + Id = seatAddOnItemId, + Price = new Price { Id = "personal-org-seat-annually" }, + Quantity = 3 + } + } + }, + AutomaticTax = new SubscriptionAutomaticTax { Enabled = true }, + Metadata = new Dictionary() + }; + + var customer = new Customer + { + Id = customerId, + Subscriptions = new StripeList { Data = new List { subscription } }, + Address = new Address { Country = "US" } + }; + + var organization = new Organization + { + Id = _organizationId, + BillingEmail = "org@example.com", + PlanType = PlanType.FamiliesAnnually2019 + }; + + _stripeEventService.GetInvoice(parsedEvent).Returns(invoice); + _stripeFacade.GetCustomer(customerId, Arg.Any()).Returns(customer); + _stripeEventUtilityService + .GetIdsFromMetadata(subscription.Metadata) + .Returns(new Tuple(_organizationId, null, null)); + _organizationRepository.GetByIdAsync(_organizationId).Returns(organization); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan); + _featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); + _stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false); + + // Act + await _sut.HandleAsync(parsedEvent); + + // Assert + await _stripeFacade.Received(1).UpdateSubscription( + Arg.Is(subscriptionId), + Arg.Is(o => + o.Items.Count == 2 && + o.Items[0].Id == passwordManagerItemId && + o.Items[0].Price == familiesPlan.PasswordManager.StripePlanId && + o.Items[1].Id == seatAddOnItemId && + o.Items[1].Deleted == true && + o.Discounts.Count == 1 && + o.Discounts[0].Coupon == CouponIDs.Milestone3SubscriptionDiscount && + o.ProrationBehavior == ProrationBehavior.None)); + + await _organizationRepository.Received(1).ReplaceAsync( + Arg.Is(org => + org.Id == _organizationId && + org.PlanType == PlanType.FamiliesAnnually && + org.Plan == familiesPlan.Name && + org.UsersGetPremium == familiesPlan.UsersGetPremium && + org.Seats == familiesPlan.PasswordManager.BaseSeats)); + + await _mailer.Received(1).SendEmail( + Arg.Is(email => + email.ToEmails.Contains("org@example.com") && + email.Subject == "Your Subscription Will Renew Soon")); + } + + [Fact] + public async Task HandleAsync_WhenMilestone3Enabled_AndSeatAddOnWithQuantityOne_DeletesItem() + { + // Arrange + var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; + var customerId = "cus_123"; + var subscriptionId = "sub_123"; + var passwordManagerItemId = "si_pm_123"; + var seatAddOnItemId = "si_seat_123"; + + var invoice = new Invoice + { + CustomerId = customerId, + AmountDue = 40000, + NextPaymentAttempt = DateTime.UtcNow.AddDays(7), + Lines = new StripeList + { + Data = new List { new() { Description = "Test Item" } } + } + }; + + var families2019Plan = new Families2019Plan(); + var familiesPlan = new FamiliesPlan(); + + var subscription = new Subscription + { + Id = subscriptionId, + CustomerId = customerId, + Items = new StripeList + { + Data = new List + { + new() + { + Id = passwordManagerItemId, + Price = new Price { Id = families2019Plan.PasswordManager.StripePlanId } + }, + new() + { + Id = seatAddOnItemId, + Price = new Price { Id = "personal-org-seat-annually" }, + Quantity = 1 + } + } + }, + AutomaticTax = new SubscriptionAutomaticTax { Enabled = true }, + Metadata = new Dictionary() + }; + + var customer = new Customer + { + Id = customerId, + Subscriptions = new StripeList { Data = new List { subscription } }, + Address = new Address { Country = "US" } + }; + + var organization = new Organization + { + Id = _organizationId, + BillingEmail = "org@example.com", + PlanType = PlanType.FamiliesAnnually2019 + }; + + _stripeEventService.GetInvoice(parsedEvent).Returns(invoice); + _stripeFacade.GetCustomer(customerId, Arg.Any()).Returns(customer); + _stripeEventUtilityService + .GetIdsFromMetadata(subscription.Metadata) + .Returns(new Tuple(_organizationId, null, null)); + _organizationRepository.GetByIdAsync(_organizationId).Returns(organization); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan); + _featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); + _stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false); + + // Act + await _sut.HandleAsync(parsedEvent); + + // Assert + await _stripeFacade.Received(1).UpdateSubscription( + Arg.Is(subscriptionId), + Arg.Is(o => + o.Items.Count == 2 && + o.Items[0].Id == passwordManagerItemId && + o.Items[0].Price == familiesPlan.PasswordManager.StripePlanId && + o.Items[1].Id == seatAddOnItemId && + o.Items[1].Deleted == true && + o.Discounts.Count == 1 && + o.Discounts[0].Coupon == CouponIDs.Milestone3SubscriptionDiscount && + o.ProrationBehavior == ProrationBehavior.None)); + + await _organizationRepository.Received(1).ReplaceAsync( + Arg.Is(org => + org.Id == _organizationId && + org.PlanType == PlanType.FamiliesAnnually && + org.Plan == familiesPlan.Name && + org.UsersGetPremium == familiesPlan.UsersGetPremium && + org.Seats == familiesPlan.PasswordManager.BaseSeats)); + + await _mailer.Received(1).SendEmail( + Arg.Is(email => + email.ToEmails.Contains("org@example.com") && + email.Subject == "Your Subscription Will Renew Soon")); + } + + [Fact] + public async Task HandleAsync_WhenMilestone3Enabled_WithPremiumAccessAndSeatAddOn_UpdatesBothItems() + { + // Arrange + var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; + var customerId = "cus_123"; + var subscriptionId = "sub_123"; + var passwordManagerItemId = "si_pm_123"; + var premiumAccessItemId = "si_premium_123"; + var seatAddOnItemId = "si_seat_123"; + + var invoice = new Invoice + { + CustomerId = customerId, + AmountDue = 40000, + NextPaymentAttempt = DateTime.UtcNow.AddDays(7), + Lines = new StripeList + { + Data = new List { new() { Description = "Test Item" } } + } + }; + + var families2019Plan = new Families2019Plan(); + var familiesPlan = new FamiliesPlan(); + + var subscription = new Subscription + { + Id = subscriptionId, + CustomerId = customerId, + Items = new StripeList + { + Data = new List + { + new() + { + Id = passwordManagerItemId, + Price = new Price { Id = families2019Plan.PasswordManager.StripePlanId } + }, + new() + { + Id = premiumAccessItemId, + Price = new Price { Id = families2019Plan.PasswordManager.StripePremiumAccessPlanId } + }, + new() + { + Id = seatAddOnItemId, + Price = new Price { Id = "personal-org-seat-annually" }, + Quantity = 2 + } + } + }, + AutomaticTax = new SubscriptionAutomaticTax { Enabled = true }, + Metadata = new Dictionary() + }; + + var customer = new Customer + { + Id = customerId, + Subscriptions = new StripeList { Data = new List { subscription } }, + Address = new Address { Country = "US" } + }; + + var organization = new Organization + { + Id = _organizationId, + BillingEmail = "org@example.com", + PlanType = PlanType.FamiliesAnnually2019 + }; + + _stripeEventService.GetInvoice(parsedEvent).Returns(invoice); + _stripeFacade.GetCustomer(customerId, Arg.Any()).Returns(customer); + _stripeEventUtilityService + .GetIdsFromMetadata(subscription.Metadata) + .Returns(new Tuple(_organizationId, null, null)); + _organizationRepository.GetByIdAsync(_organizationId).Returns(organization); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2019).Returns(families2019Plan); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan); + _featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); + _stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false); + + // Act + await _sut.HandleAsync(parsedEvent); + + // Assert + await _stripeFacade.Received(1).UpdateSubscription( + Arg.Is(subscriptionId), + Arg.Is(o => + o.Items.Count == 3 && + o.Items[0].Id == passwordManagerItemId && + o.Items[0].Price == familiesPlan.PasswordManager.StripePlanId && + o.Items[1].Id == premiumAccessItemId && + o.Items[1].Deleted == true && + o.Items[2].Id == seatAddOnItemId && + o.Items[2].Deleted == true && + o.Discounts.Count == 1 && + o.Discounts[0].Coupon == CouponIDs.Milestone3SubscriptionDiscount && + o.ProrationBehavior == ProrationBehavior.None)); + + await _organizationRepository.Received(1).ReplaceAsync( + Arg.Is(org => + org.Id == _organizationId && + org.PlanType == PlanType.FamiliesAnnually && + org.Plan == familiesPlan.Name && + org.UsersGetPremium == familiesPlan.UsersGetPremium && + org.Seats == familiesPlan.PasswordManager.BaseSeats)); + + await _mailer.Received(1).SendEmail( + Arg.Is(email => + email.ToEmails.Contains("org@example.com") && + email.Subject == "Your Subscription Will Renew Soon")); + } + + [Fact] + public async Task HandleAsync_WhenMilestone3Enabled_AndFamilies2025Plan_UpdatesSubscriptionOnlyNoAddons() + { + // Arrange + var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; + var customerId = "cus_123"; + var subscriptionId = "sub_123"; + var passwordManagerItemId = "si_pm_123"; + + var invoice = new Invoice + { + CustomerId = customerId, + AmountDue = 40000, + NextPaymentAttempt = DateTime.UtcNow.AddDays(7), + Lines = new StripeList + { + Data = new List { new() { Description = "Test Item" } } + } + }; + + var families2025Plan = new Families2025Plan(); + var familiesPlan = new FamiliesPlan(); + + var subscription = new Subscription + { + Id = subscriptionId, + CustomerId = customerId, + Items = new StripeList + { + Data = new List + { + new() + { + Id = passwordManagerItemId, + Price = new Price { Id = families2025Plan.PasswordManager.StripePlanId } + } + } + }, + AutomaticTax = new SubscriptionAutomaticTax { Enabled = true }, + Metadata = new Dictionary() + }; + + var customer = new Customer + { + Id = customerId, + Subscriptions = new StripeList { Data = new List { subscription } }, + Address = new Address { Country = "US" } + }; + + var organization = new Organization + { + Id = _organizationId, + BillingEmail = "org@example.com", + PlanType = PlanType.FamiliesAnnually2025 + }; + + _stripeEventService.GetInvoice(parsedEvent).Returns(invoice); + _stripeFacade.GetCustomer(customerId, Arg.Any()).Returns(customer); + _stripeEventUtilityService + .GetIdsFromMetadata(subscription.Metadata) + .Returns(new Tuple(_organizationId, null, null)); + _organizationRepository.GetByIdAsync(_organizationId).Returns(organization); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2025).Returns(families2025Plan); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually).Returns(familiesPlan); + _featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); + _stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false); + + // Act + await _sut.HandleAsync(parsedEvent); + + // Assert + await _stripeFacade.Received(1).UpdateSubscription( + Arg.Is(subscriptionId), + Arg.Is(o => + o.Items.Count == 1 && + o.Items[0].Id == passwordManagerItemId && + o.Items[0].Price == familiesPlan.PasswordManager.StripePlanId && + o.Discounts == null && + o.ProrationBehavior == ProrationBehavior.None)); + + await _organizationRepository.Received(1).ReplaceAsync( + Arg.Is(org => + org.Id == _organizationId && + org.PlanType == PlanType.FamiliesAnnually && + org.Plan == familiesPlan.Name && + org.UsersGetPremium == familiesPlan.UsersGetPremium && + org.Seats == familiesPlan.PasswordManager.BaseSeats)); + } + + [Fact] + public async Task HandleAsync_WhenMilestone3Disabled_AndFamilies2025Plan_DoesNotUpdateSubscription() + { + // Arrange + var parsedEvent = new Event { Id = "evt_123", Type = "invoice.upcoming" }; + var customerId = "cus_123"; + var subscriptionId = "sub_123"; + var passwordManagerItemId = "si_pm_123"; + + var invoice = new Invoice + { + CustomerId = customerId, + AmountDue = 40000, + NextPaymentAttempt = DateTime.UtcNow.AddDays(7), + Lines = new StripeList + { + Data = new List { new() { Description = "Test Item" } } + } + }; + + var families2025Plan = new Families2025Plan(); + + var subscription = new Subscription + { + Id = subscriptionId, + CustomerId = customerId, + Items = new StripeList + { + Data = new List + { + new() + { + Id = passwordManagerItemId, + Price = new Price { Id = families2025Plan.PasswordManager.StripePlanId } + } + } + }, + AutomaticTax = new SubscriptionAutomaticTax { Enabled = true }, + Metadata = new Dictionary() + }; + + var customer = new Customer + { + Id = customerId, + Subscriptions = new StripeList { Data = new List { subscription } }, + Address = new Address { Country = "US" } + }; + + var organization = new Organization + { + Id = _organizationId, + BillingEmail = "org@example.com", + PlanType = PlanType.FamiliesAnnually2025 + }; + + _stripeEventService.GetInvoice(parsedEvent).Returns(invoice); + _stripeFacade.GetCustomer(customerId, Arg.Any()).Returns(customer); + _stripeEventUtilityService + .GetIdsFromMetadata(subscription.Metadata) + .Returns(new Tuple(_organizationId, null, null)); + _organizationRepository.GetByIdAsync(_organizationId).Returns(organization); + _pricingClient.GetPlanOrThrow(PlanType.FamiliesAnnually2025).Returns(families2025Plan); + _featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false); + _stripeEventUtilityService.IsSponsoredSubscription(subscription).Returns(false); + + // Act + await _sut.HandleAsync(parsedEvent); + + // Assert - should not update subscription or organization when feature flag is disabled + await _stripeFacade.DidNotReceive().UpdateSubscription( + Arg.Any(), + Arg.Any()); + + await _organizationRepository.DidNotReceive().ReplaceAsync( + Arg.Is(org => org.PlanType == PlanType.FamiliesAnnually)); + } } diff --git a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs index e906862e3f..c874fe58d8 100644 --- a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs @@ -1,6 +1,8 @@ -using System.Text.Json; +using System.Reflection; +using System.Text.Json; using AutoFixture; using AutoFixture.Kernel; +using AutoFixture.Xunit2; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; @@ -9,7 +11,7 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Business; using Bit.Core.Models.Data; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.DataProtection; @@ -20,12 +22,24 @@ public class OrganizationCustomization : ICustomization { public bool UseGroups { get; set; } public PlanType PlanType { get; set; } + public bool UseAutomaticUserConfirmation { get; set; } + + public OrganizationCustomization() + { + + } + + public OrganizationCustomization(bool useAutomaticUserConfirmation, PlanType planType) + { + UseAutomaticUserConfirmation = useAutomaticUserConfirmation; + PlanType = planType; + } public void Customize(IFixture fixture) { var organizationId = Guid.NewGuid(); var maxCollections = (short)new Random().Next(10, short.MaxValue); - var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == PlanType); + var plan = MockPlans.Plans.FirstOrDefault(p => p.Type == PlanType); var seats = (short)new Random().Next(plan.PasswordManager.BaseSeats, plan.PasswordManager.MaxSeats ?? short.MaxValue); var smSeats = plan.SupportsSecretsManager ? (short?)new Random().Next(plan.SecretsManager.BaseSeats, plan.SecretsManager.MaxSeats ?? short.MaxValue) @@ -37,7 +51,8 @@ public class OrganizationCustomization : ICustomization .With(o => o.UseGroups, UseGroups) .With(o => o.PlanType, PlanType) .With(o => o.Seats, seats) - .With(o => o.SmSeats, smSeats)); + .With(o => o.SmSeats, smSeats) + .With(o => o.UseAutomaticUserConfirmation, UseAutomaticUserConfirmation)); fixture.Customize(composer => composer @@ -77,7 +92,7 @@ internal class PaidOrganization : ICustomization public PlanType CheckedPlanType { get; set; } public void Customize(IFixture fixture) { - var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList(); + var validUpgradePlans = MockPlans.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList(); var lowestActivePaidPlan = validUpgradePlans.First(); CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType; validUpgradePlans.Remove(lowestActivePaidPlan); @@ -105,7 +120,7 @@ internal class FreeOrganizationUpgrade : ICustomization .With(o => o.PlanType, PlanType.Free)); var plansToIgnore = new List { PlanType.Free, PlanType.Custom }; - var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled); + var selectedPlan = MockPlans.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled); fixture.Customize(composer => composer .With(ou => ou.Plan, selectedPlan.Type) @@ -153,7 +168,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization .With(o => o.Id, organizationId) .With(o => o.UseSecretsManager, true) .With(o => o.PlanType, planType) - .With(o => o.Plan, StaticStore.GetPlan(planType).Name) + .With(o => o.Plan, MockPlans.Get(planType).Name) .With(o => o.MaxAutoscaleSmSeats, (int?)null) .With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null)); } @@ -277,3 +292,9 @@ internal class EphemeralDataProtectionAutoDataAttribute : CustomAutoDataAttribut public EphemeralDataProtectionAutoDataAttribute() : base(new SutProviderCustomization(), new EphemeralDataProtectionCustomization()) { } } + +internal class OrganizationAttribute(bool useAutomaticUserConfirmation = false, PlanType planType = PlanType.Free) : CustomizeAttribute +{ + public override ICustomization GetCustomization(ParameterInfo parameter) => + new OrganizationCustomization(useAutomaticUserConfirmation, planType); +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmOrganizationUsersValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmOrganizationUsersValidatorTests.cs new file mode 100644 index 0000000000..eb377a8d08 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmOrganizationUsersValidatorTests.cs @@ -0,0 +1,696 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Billing.Enums; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUsers; + +[SutProviderCustomize] +public class AutomaticallyConfirmOrganizationUsersValidatorTests +{ + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithNullOrganizationUser_ReturnsUserNotFoundError( + SutProvider sutProvider, + Organization organization) + { + // Arrange + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = null, + OrganizationUserId = Guid.NewGuid(), + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithNullUserId_ReturnsUserNotFoundError( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser) + { + // Arrange + organizationUser.UserId = null; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithNullOrganization_ReturnsOrganizationNotFoundError( + SutProvider sutProvider, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId) + { + // Arrange + organizationUser.UserId = userId; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = null, + OrganizationId = organizationUser.OrganizationId, + Key = "test-key" + }; + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithValidAcceptedUser_ReturnsValidResult( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true, planType: PlanType.EnterpriseAnnually)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsValid); + Assert.Equal(request, result.Request); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithMismatchedOrganizationId_ReturnsOrganizationUserIdIsInvalidError( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = Guid.NewGuid(); // Different from organization.Id + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData(OrganizationUserStatusType.Invited)] + [BitAutoData(OrganizationUserStatusType.Revoked)] + [BitAutoData(OrganizationUserStatusType.Confirmed)] + public async Task ValidateAsync_WithNotAcceptedStatus_ReturnsUserIsNotAcceptedError( + OrganizationUserStatusType statusType, + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + Guid userId) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + organizationUser.Status = statusType; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData(OrganizationUserType.Owner)] + [BitAutoData(OrganizationUserType.Custom)] + [BitAutoData(OrganizationUserType.Admin)] + public async Task ValidateAsync_WithNonUserType_ReturnsUserIsNotUserTypeError( + OrganizationUserType userType, + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + organizationUser.Type = userType; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserWithout2FA_And2FARequired_ReturnsError( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + var twoFactorPolicyDetails = new PolicyDetails + { + OrganizationId = organization.Id, + PolicyType = PolicyType.TwoFactorAuthentication + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, false)]); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new RequireTwoFactorPolicyRequirement([twoFactorPolicyDetails])); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserWith2FA_ReturnsValidResult( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsValid); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserWithout2FA_And2FANotRequired_ReturnsValidResult( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, false)]); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new RequireTwoFactorPolicyRequirement([])); // No 2FA policy + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsValid); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnThisOrg_ReturnsError( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + OrganizationUser otherOrgUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + var singleOrgPolicyDetails = new PolicyDetails + { + OrganizationId = organization.Id, + PolicyType = PolicyType.SingleOrg + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser, otherOrgUser]); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails])); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnOtherOrg_ReturnsError( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + OrganizationUser otherOrgUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + var otherOrgId = Guid.NewGuid(); // Different org + var singleOrgPolicyDetails = new PolicyDetails + { + OrganizationId = otherOrgId, + PolicyType = PolicyType.SingleOrg, + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser, otherOrgUser]); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails])); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserInSingleOrg_ReturnsValidResult( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); // Single org + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsValid); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_UserInMultipleOrgs_WithNoSingleOrgPolicy_ReturnsValidResult( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: true)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + OrganizationUser otherOrgUser, + Guid userId, + Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + autoConfirmPolicy.Type = PolicyType.AutomaticUserConfirmation; + autoConfirmPolicy.Enabled = true; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser, otherOrgUser]); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(new SingleOrganizationPolicyRequirement([])); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsValid); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithAutoConfirmPolicyDisabled_ReturnsAutoConfirmPolicyNotEnabledError( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns((Policy)null); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } + + [Theory] + [BitAutoData] + public async Task ValidateAsync_WithOrganizationUseAutomaticUserConfirmationDisabled_ReturnsAutoConfirmPolicyNotEnabledError( + SutProvider sutProvider, + [Organization(useAutomaticUserConfirmation: false)] Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + Guid userId, + [Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy) + { + // Arrange + organizationUser.UserId = userId; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = Substitute.For(), + DefaultUserCollectionName = "test-collection", + OrganizationUser = organizationUser, + OrganizationUserId = organizationUser.Id, + Organization = organization, + OrganizationId = organization.Id, + Key = "test-key" + }; + + sutProvider.GetDependency() + .GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation) + .Returns(autoConfirmPolicy); + + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Any>()) + .Returns([(userId, true)]); + + sutProvider.GetDependency() + .GetManyByUserAsync(userId) + .Returns([organizationUser]); + + // Act + var result = await sutProvider.Sut.ValidateAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs new file mode 100644 index 0000000000..1035d5c578 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs @@ -0,0 +1,730 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Data; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.Utilities.v2; +using Bit.Core.AdminConsole.Utilities.v2.Validation; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Platform.Push; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Extensions.Logging; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUsers; + +[SutProviderCustomize] +public class AutomaticallyConfirmUsersCommandTests +{ + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WithValidRequest_ConfirmsUserSuccessfully( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsSuccess); + + await sutProvider.GetDependency() + .Received(1) + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)); + + await AssertSuccessfulOperationsAsync(sutProvider, organizationUser, organization, user, key); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WithInvalidUserOrgId_ReturnsOrganizationUserIdIsInvalidError( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = Guid.NewGuid(); // User belongs to another organization + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, false, new OrganizationUserIdIsInvalid()); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsError); + Assert.IsType(result.AsError); + + await sutProvider.GetDependency() + .DidNotReceive() + .ConfirmOrganizationUserAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenAlreadyConfirmed_ReturnsNoneSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + // Return false to indicate the user is already confirmed + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(x => + x.OrganizationUserId == organizationUser.Id && x.Key == request.Key)) + .Returns(false); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsSuccess); + + await sutProvider.GetDependency() + .Received(1) + .ConfirmOrganizationUserAsync(Arg.Is(x => + x.OrganizationUserId == organizationUser.Id && x.Key == request.Key)); + + // Verify no side effects occurred + await sutProvider.GetDependency() + .DidNotReceive() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + + await sutProvider.GetDependency() + .DidNotReceive() + .PushSyncOrgKeysAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectionEnabled_CreatesDefaultCollection( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, // Non-empty to trigger creation + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, true); // Policy requires collection + + sutProvider.GetDependency().ConfirmOrganizationUserAsync( + Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsSuccess); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is(c => + c.OrganizationId == organization.Id && + c.Name == defaultCollectionName && + c.Type == CollectionType.DefaultUserCollection), + Arg.Is>(groups => groups == null), + Arg.Is>(access => + access.FirstOrDefault(x => x.Id == organizationUser.Id && x.Manage) != null)); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectionDisabled_DoesNotCreateCollection( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = string.Empty, // Empty, so the collection won't be created + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, false); // Policy doesn't require + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsSuccess); + + await sutProvider.GetDependency() + .DidNotReceive() + .CreateAsync(Arg.Any(), + Arg.Any>(), + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenCreateDefaultCollectionFails_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, // Non-empty to trigger creation + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + SetupPolicyRequirementMock(sutProvider, user.Id, organization.Id, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)).Returns(true); + + var collectionException = new Exception("Collection creation failed"); + sutProvider.GetDependency() + .CreateAsync(Arg.Any(), + Arg.Any>(), + Arg.Any>()) + .ThrowsAsync(collectionException); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if collection creation fails + Assert.True(result.IsSuccess); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString()!.Contains("Failed to create default collection for user")), + collectionException, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenEventLogFails_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + var eventException = new Exception("Event logging failed"); + sutProvider.GetDependency() + .LogOrganizationUserEventAsync(Arg.Any(), + EventType.OrganizationUser_AutomaticallyConfirmed, + Arg.Any()) + .ThrowsAsync(eventException); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if event log fails + Assert.True(result.IsSuccess); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString()!.Contains("Failed to log OrganizationUser_AutomaticallyConfirmed event")), + eventException, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenSendEmailFails_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + var emailException = new Exception("Email sending failed"); + sutProvider.GetDependency() + .SendOrganizationConfirmedEmailAsync(organization.Name, user.Email, organizationUser.AccessSecretsManager) + .ThrowsAsync(emailException); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if email fails + Assert.True(result.IsSuccess); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString()!.Contains("Failed to send OrganizationUserConfirmed")), + emailException, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenUserNotFoundForEmail_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + // Return null when retrieving user for email + sutProvider.GetDependency() + .GetByIdAsync(user.Id) + .Returns((User)null!); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if user not found for email + Assert.True(result.IsSuccess); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenDeleteDeviceRegistrationFails_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName, + Device device) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + device.UserId = user.Id; + device.PushToken = "test-push-token"; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(user.Id) + .Returns(new List { device }); + + var deviceException = new Exception("Device registration deletion failed"); + sutProvider.GetDependency() + .DeleteUserRegistrationOrganizationAsync(Arg.Any>(), organization.Id.ToString()) + .ThrowsAsync(deviceException); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if device registration deletion fails + Assert.True(result.IsSuccess); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString()!.Contains("Failed to delete device registration")), + deviceException, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WhenPushSyncOrgKeysFails_LogsErrorButReturnsSuccess( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + var pushException = new Exception("Push sync failed"); + sutProvider.GetDependency() + .PushSyncOrgKeysAsync(user.Id) + .ThrowsAsync(pushException); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert - side effects are fire-and-forget, so command returns success even if push sync fails + Assert.True(result.IsSuccess); + + sutProvider.GetDependency>() + .Received(1) + .Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString()!.Contains("Failed to push organization keys")), + pushException, + Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task AutomaticallyConfirmOrganizationUserAsync_WithDevicesWithoutPushToken_FiltersCorrectly( + SutProvider sutProvider, + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser, + User user, + Guid performingUserId, + string key, + string defaultCollectionName, + Device deviceWithToken, + Device deviceWithoutToken) + { + // Arrange + organizationUser.UserId = user.Id; + organizationUser.OrganizationId = organization.Id; + deviceWithToken.UserId = user.Id; + deviceWithToken.PushToken = "test-token"; + deviceWithoutToken.UserId = user.Id; + deviceWithoutToken.PushToken = null; + var request = new AutomaticallyConfirmOrganizationUserRequest + { + OrganizationUserId = organizationUser.Id, + OrganizationId = organization.Id, + Key = key, + DefaultUserCollectionName = defaultCollectionName, + PerformedBy = new StandardUser(performingUserId, true) + }; + + SetupRepositoryMocks(sutProvider, organizationUser, organization, user); + SetupValidatorMock(sutProvider, request, organizationUser, organization, true); + + sutProvider.GetDependency() + .ConfirmOrganizationUserAsync(Arg.Is(o => + o.OrganizationUserId == organizationUser.Id && o.Key == request.Key)) + .Returns(true); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(user.Id) + .Returns(new List { deviceWithToken, deviceWithoutToken }); + + // Act + var result = await sutProvider.Sut.AutomaticallyConfirmOrganizationUserAsync(request); + + // Assert + Assert.True(result.IsSuccess); + + await sutProvider.GetDependency() + .Received(1) + .DeleteUserRegistrationOrganizationAsync( + Arg.Is>(devices => + devices.Count(d => deviceWithToken.Id.ToString() == d) == 1), + organization.Id.ToString()); + } + + private static void SetupRepositoryMocks( + SutProvider sutProvider, + OrganizationUser organizationUser, + Organization organization, + User user) + { + sutProvider.GetDependency() + .GetByIdAsync(organizationUser.Id) + .Returns(organizationUser); + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + sutProvider.GetDependency() + .GetByIdAsync(user.Id) + .Returns(user); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(user.Id) + .Returns(new List()); + } + + private static void SetupValidatorMock( + SutProvider sutProvider, + AutomaticallyConfirmOrganizationUserRequest originalRequest, + OrganizationUser organizationUser, + Organization organization, + bool isValid, + Error? error = null) + { + var validationRequest = new AutomaticallyConfirmOrganizationUserValidationRequest + { + PerformedBy = originalRequest.PerformedBy, + DefaultUserCollectionName = originalRequest.DefaultUserCollectionName, + OrganizationUserId = originalRequest.OrganizationUserId, + OrganizationUser = organizationUser, + OrganizationId = originalRequest.OrganizationId, + Organization = organization, + Key = originalRequest.Key + }; + + var validationResult = isValid + ? ValidationResultHelpers.Valid(validationRequest) + : ValidationResultHelpers.Invalid(validationRequest, error ?? new UserIsNotAccepted()); + + sutProvider.GetDependency() + .ValidateAsync(Arg.Any()) + .Returns(validationResult); + } + + private static void SetupPolicyRequirementMock( + SutProvider sutProvider, + Guid userId, + Guid organizationId, + bool requiresDefaultCollection) + { + var policyDetails = requiresDefaultCollection + ? new List { new() { OrganizationId = organizationId } } + : new List(); + + var policyRequirement = new OrganizationDataOwnershipPolicyRequirement( + requiresDefaultCollection ? OrganizationDataOwnershipState.Enabled : OrganizationDataOwnershipState.Disabled, + policyDetails); + + sutProvider.GetDependency() + .GetAsync(userId) + .Returns(policyRequirement); + } + + private static async Task AssertSuccessfulOperationsAsync( + SutProvider sutProvider, + OrganizationUser organizationUser, + Organization organization, + User user, + string key) + { + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync( + Arg.Is(x => x.Id == organizationUser.Id), + EventType.OrganizationUser_AutomaticallyConfirmed, + Arg.Any()); + + await sutProvider.GetDependency() + .Received(1) + .SendOrganizationConfirmedEmailAsync( + organization.Name, + user.Email, + organizationUser.AccessSecretsManager); + + await sutProvider.GetDependency() + .Received(1) + .PushSyncOrgKeysAsync(user.Id); + + await sutProvider.GetDependency() + .Received(1) + .DeleteUserRegistrationOrganizationAsync( + Arg.Any>(), + organization.Id.ToString()); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccountvNext/DeleteClaimedOrganizationUserAccountCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccountvNext/DeleteClaimedOrganizationUserAccountCommandTests.cs index c223520a04..dfb1b35be0 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccountvNext/DeleteClaimedOrganizationUserAccountCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteClaimedAccountvNext/DeleteClaimedOrganizationUserAccountCommandTests.cs @@ -1,5 +1,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.Utilities.v2; +using Bit.Core.AdminConsole.Utilities.v2.Validation; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs index 10dcff9e2a..5d82f0717d 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/InviteOrganizationUserCommandTests.cs @@ -13,7 +13,6 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Utilities.Commands; using Bit.Core.AdminConsole.Utilities.Errors; using Bit.Core.AdminConsole.Utilities.Validation; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Business; @@ -22,6 +21,7 @@ using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Time.Testing; @@ -29,6 +29,7 @@ using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; using static Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Helpers.InviteUserOrganizationValidationRequestHelpers; +using Enterprise2019Plan = Bit.Core.Test.Billing.Mocks.Plans.Enterprise2019Plan; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUsersValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUsersValidatorTests.cs index a5b220b94a..04ef3961ca 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUsersValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteOrganizationUsersValidatorTests.cs @@ -3,12 +3,12 @@ using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation; using Bit.Core.AdminConsole.Utilities.Validation; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs index be5586f8a6..482b369780 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserOrganizationValidationTests.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Organization; using Bit.Core.AdminConsole.Utilities.Validation; -using Bit.Core.Billing.Models.StaticStore.Plans; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserPaymentValidationTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserPaymentValidationTests.cs index 738ae71298..72a146205b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserPaymentValidationTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/InviteUserPaymentValidationTests.cs @@ -5,7 +5,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V using Bit.Core.AdminConsole.Utilities.Validation; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs index 571832d675..46ca37522f 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/InviteUsers/Validation/PasswordManagerInviteUserValidatorTests.cs @@ -3,7 +3,7 @@ using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager; using Bit.Core.AdminConsole.Utilities.Validation; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/GetOrganizationSubscriptionsToUpdateQueryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/GetOrganizationSubscriptionsToUpdateQueryTests.cs index af6b5a17f7..f1c4797de8 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/GetOrganizationSubscriptionsToUpdateQueryTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/GetOrganizationSubscriptionsToUpdateQueryTests.cs @@ -1,9 +1,9 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Pricing; using Bit.Core.Repositories; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs index feb5ef2a40..c1fea1455e 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs @@ -10,7 +10,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data; using Bit.Core.Repositories; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -28,7 +28,7 @@ public class CloudICloudOrganizationSignUpCommandTests { signup.Plan = planType; - var plan = StaticStore.GetPlan(signup.Plan); + var plan = MockPlans.Get(signup.Plan); signup.AdditionalSeats = 0; signup.PaymentMethodType = PaymentMethodType.Card; @@ -37,7 +37,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.IsFromSecretsManagerTrial = false; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var result = await sutProvider.Sut.SignUpOrganizationAsync(signup); @@ -77,7 +77,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.UseSecretsManager = false; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); // Extract orgUserId when created Guid? orgUserId = null; @@ -112,7 +112,7 @@ public class CloudICloudOrganizationSignUpCommandTests { signup.Plan = planType; - var plan = StaticStore.GetPlan(signup.Plan); + var plan = MockPlans.Get(signup.Plan); signup.UseSecretsManager = true; signup.AdditionalSeats = 15; @@ -123,7 +123,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.IsFromSecretsManagerTrial = false; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var result = await sutProvider.Sut.SignUpOrganizationAsync(signup); @@ -164,7 +164,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PremiumAccessAddon = false; signup.IsFromProvider = true; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.SignUpOrganizationAsync(signup)); Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message); @@ -184,7 +184,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.AdditionalStorageGb = 0; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); @@ -204,7 +204,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.AdditionalServiceAccounts = 10; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); @@ -224,7 +224,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.AdditionalServiceAccounts = -10; signup.IsFromProvider = false; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); @@ -244,7 +244,7 @@ public class CloudICloudOrganizationSignUpCommandTests Owner = new User { Id = Guid.NewGuid() } }; - sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(StaticStore.GetPlan(signup.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(signup.Plan).Returns(MockPlans.Get(signup.Plan)); sutProvider.GetDependency() .GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ProviderClientOrganizationSignUpCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ProviderClientOrganizationSignUpCommandTests.cs index 881f134b4c..5385b4cdea 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ProviderClientOrganizationSignUpCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ProviderClientOrganizationSignUpCommandTests.cs @@ -10,7 +10,7 @@ using Bit.Core.Models.Data; using Bit.Core.Models.StaticStore; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -36,7 +36,7 @@ public class ProviderClientOrganizationSignUpCommandTests signup.AdditionalSeats = 15; signup.CollectionName = collectionName; - var plan = StaticStore.GetPlan(signup.Plan); + var plan = MockPlans.Get(signup.Plan); sutProvider.GetDependency() .GetPlanOrThrow(signup.Plan) .Returns(plan); @@ -112,7 +112,7 @@ public class ProviderClientOrganizationSignUpCommandTests signup.Plan = PlanType.TeamsMonthly; signup.AdditionalSeats = -5; - var plan = StaticStore.GetPlan(signup.Plan); + var plan = MockPlans.Get(signup.Plan); sutProvider.GetDependency() .GetPlanOrThrow(signup.Plan) .Returns(plan); @@ -132,7 +132,7 @@ public class ProviderClientOrganizationSignUpCommandTests { signup.Plan = planType; - var plan = StaticStore.GetPlan(signup.Plan); + var plan = MockPlans.Get(signup.Plan); sutProvider.GetDependency() .GetPlanOrThrow(signup.Plan) .Returns(plan); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/UpdateOrganizationSubscriptionCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/UpdateOrganizationSubscriptionCommandTests.cs index 37a5627919..f9fc086873 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/UpdateOrganizationSubscriptionCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/UpdateOrganizationSubscriptionCommandTests.cs @@ -2,10 +2,10 @@ using Bit.Core.AdminConsole.Models.Data.Organizations; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Models.StaticStore; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; diff --git a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs index 33f2e78799..821ce78074 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs @@ -21,8 +21,8 @@ using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Core.Test.AutoFixture.OrganizationUserFixtures; +using Bit.Core.Test.Billing.Mocks; using Bit.Core.Tokens; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Fakes; @@ -618,7 +618,7 @@ public class OrganizationServiceTests SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository); SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository); - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites); @@ -666,7 +666,7 @@ public class OrganizationServiceTests .SendInvitesAsync(Arg.Any()).ThrowsAsync(); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); await Assert.ThrowsAsync(async () => await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites)); @@ -732,7 +732,7 @@ public class OrganizationServiceTests sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscription(organization.Id, seatAdjustment, maxAutoscaleSeats)); @@ -757,7 +757,7 @@ public class OrganizationServiceTests organization.SmSeats = 100; sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); sutProvider.GetDependency() .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts { @@ -837,7 +837,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.EnterpriseMonthly)] public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -854,7 +854,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.Free)] public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -871,7 +871,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -890,7 +890,7 @@ public class OrganizationServiceTests [BitAutoData(PlanType.EnterpriseMonthly)] public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -912,7 +912,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -930,7 +930,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, @@ -952,7 +952,7 @@ public class OrganizationServiceTests PlanType planType, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var signup = new OrganizationUpgrade { UseSecretsManager = true, diff --git a/test/Core.Test/Billing/Mocks/MockPlans.cs b/test/Core.Test/Billing/Mocks/MockPlans.cs new file mode 100644 index 0000000000..b4737434fb --- /dev/null +++ b/test/Core.Test/Billing/Mocks/MockPlans.cs @@ -0,0 +1,37 @@ +using Bit.Core.Billing.Enums; +using Bit.Core.Models.StaticStore; +using Bit.Core.Test.Billing.Mocks.Plans; + +namespace Bit.Core.Test.Billing.Mocks; + +public class MockPlans +{ + public static List Plans => + [ + new CustomPlan(), + new Enterprise2019Plan(false), + new Enterprise2019Plan(true), + new Enterprise2020Plan(false), + new Enterprise2020Plan(true), + new Enterprise2023Plan(false), + new Enterprise2023Plan(true), + new EnterprisePlan(false), + new EnterprisePlan(true), + new Families2019Plan(), + new Families2025Plan(), + new FamiliesPlan(), + new FreePlan(), + new Teams2019Plan(false), + new Teams2019Plan(true), + new Teams2020Plan(false), + new Teams2020Plan(true), + new Teams2023Plan(false), + new Teams2023Plan(true), + new TeamsPlan(false), + new TeamsPlan(true), + new TeamsStarterPlan(), + new TeamsStarterPlan2023() + ]; + + public static Plan Get(PlanType planType) => Plans.SingleOrDefault(p => p.Type == planType)!; +} diff --git a/src/Core/Billing/Models/StaticStore/Plans/CustomPlan.cs b/test/Core.Test/Billing/Mocks/Plans/CustomPlan.cs similarity index 89% rename from src/Core/Billing/Models/StaticStore/Plans/CustomPlan.cs rename to test/Core.Test/Billing/Mocks/Plans/CustomPlan.cs index ce55cb422e..0105b7d07f 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/CustomPlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/CustomPlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record CustomPlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Enterprise2019Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Enterprise2019Plan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/Enterprise2019Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Enterprise2019Plan.cs index b584647a26..27f3710b96 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Enterprise2019Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Enterprise2019Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Enterprise2019Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Enterprise2020Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Enterprise2020Plan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/Enterprise2020Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Enterprise2020Plan.cs index a1a6113cbc..8f56125fc1 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Enterprise2020Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Enterprise2020Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Enterprise2020Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan.cs b/test/Core.Test/Billing/Mocks/Plans/EnterprisePlan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan.cs rename to test/Core.Test/Billing/Mocks/Plans/EnterprisePlan.cs index 8aeca521d1..563adc82a3 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/EnterprisePlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record EnterprisePlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan2023.cs b/test/Core.Test/Billing/Mocks/Plans/EnterprisePlan2023.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan2023.cs rename to test/Core.Test/Billing/Mocks/Plans/EnterprisePlan2023.cs index dce1719a49..f221821ed3 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/EnterprisePlan2023.cs +++ b/test/Core.Test/Billing/Mocks/Plans/EnterprisePlan2023.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Enterprise2023Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Families2019Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Families2019Plan.cs similarity index 96% rename from src/Core/Billing/Models/StaticStore/Plans/Families2019Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Families2019Plan.cs index 93ab2c39a1..a0257d88e9 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Families2019Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Families2019Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Families2019Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Families2025Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Families2025Plan.cs similarity index 95% rename from src/Core/Billing/Models/StaticStore/Plans/Families2025Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Families2025Plan.cs index 77e238e98e..5f5424bbcf 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Families2025Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Families2025Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Families2025Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/FamiliesPlan.cs b/test/Core.Test/Billing/Mocks/Plans/FamiliesPlan.cs similarity index 95% rename from src/Core/Billing/Models/StaticStore/Plans/FamiliesPlan.cs rename to test/Core.Test/Billing/Mocks/Plans/FamiliesPlan.cs index b2edc1168b..70aa613ee0 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/FamiliesPlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/FamiliesPlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record FamiliesPlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/FreePlan.cs b/test/Core.Test/Billing/Mocks/Plans/FreePlan.cs similarity index 95% rename from src/Core/Billing/Models/StaticStore/Plans/FreePlan.cs rename to test/Core.Test/Billing/Mocks/Plans/FreePlan.cs index 3b0a8b7480..307f58c803 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/FreePlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/FreePlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record FreePlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Teams2019Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Teams2019Plan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/Teams2019Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Teams2019Plan.cs index 27ed5e0bf4..f1aad7c16f 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Teams2019Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Teams2019Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Teams2019Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/Teams2020Plan.cs b/test/Core.Test/Billing/Mocks/Plans/Teams2020Plan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/Teams2020Plan.cs rename to test/Core.Test/Billing/Mocks/Plans/Teams2020Plan.cs index a760b9692e..546f1f84c5 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/Teams2020Plan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/Teams2020Plan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Teams2020Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/TeamsPlan.cs b/test/Core.Test/Billing/Mocks/Plans/TeamsPlan.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/TeamsPlan.cs rename to test/Core.Test/Billing/Mocks/Plans/TeamsPlan.cs index 654792ee0b..e0ecd35346 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/TeamsPlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/TeamsPlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record TeamsPlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/TeamsPlan2023.cs b/test/Core.Test/Billing/Mocks/Plans/TeamsPlan2023.cs similarity index 98% rename from src/Core/Billing/Models/StaticStore/Plans/TeamsPlan2023.cs rename to test/Core.Test/Billing/Mocks/Plans/TeamsPlan2023.cs index 8498af6b13..5ec2acd61c 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/TeamsPlan2023.cs +++ b/test/Core.Test/Billing/Mocks/Plans/TeamsPlan2023.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record Teams2023Plan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan.cs b/test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan.cs similarity index 97% rename from src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan.cs rename to test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan.cs index d78844e429..119f431a56 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan.cs +++ b/test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record TeamsStarterPlan : Plan { diff --git a/src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan2023.cs b/test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan2023.cs similarity index 97% rename from src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan2023.cs rename to test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan2023.cs index ea15d9eb95..40952e75fb 100644 --- a/src/Core/Billing/Models/StaticStore/Plans/TeamsStarterPlan2023.cs +++ b/test/Core.Test/Billing/Mocks/Plans/TeamsStarterPlan2023.cs @@ -1,7 +1,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.StaticStore; -namespace Bit.Core.Billing.Models.StaticStore.Plans; +namespace Bit.Core.Test.Billing.Mocks.Plans; public record TeamsStarterPlan2023 : Plan { diff --git a/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs b/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs index 8b3a044118..ef2b1512c9 100644 --- a/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs +++ b/test/Core.Test/Billing/Organizations/Commands/PreviewOrganizationTaxCommandTests.cs @@ -1,11 +1,11 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Organizations.Commands; using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Payment.Models; using Bit.Core.Billing.Pricing; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Microsoft.Extensions.Logging; using NSubstitute; using Stripe; diff --git a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs index 9f4b8474b5..e4cb0b0109 100644 --- a/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs +++ b/test/Core.Test/Billing/Organizations/Queries/GetOrganizationMetadataQueryTests.cs @@ -8,7 +8,7 @@ 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.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -163,7 +163,7 @@ public class GetOrganizationMetadataQueryTests sutProvider.GetDependency() .GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var result = await sutProvider.Sut.Run(organization); @@ -216,7 +216,7 @@ public class GetOrganizationMetadataQueryTests sutProvider.GetDependency() .GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var result = await sutProvider.Sut.Run(organization); @@ -282,7 +282,7 @@ public class GetOrganizationMetadataQueryTests sutProvider.GetDependency() .GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var result = await sutProvider.Sut.Run(organization); @@ -349,7 +349,7 @@ public class GetOrganizationMetadataQueryTests sutProvider.GetDependency() .GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var result = await sutProvider.Sut.Run(organization); diff --git a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs index 493246c578..cc9c409b4a 100644 --- a/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs +++ b/test/Core.Test/Billing/Premium/Commands/CreatePremiumCloudHostedSubscriptionCommandTests.cs @@ -53,7 +53,7 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests Available = true, LegacyYear = null, Seat = new PremiumPurchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually }, - Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal } + Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 } }; _pricingClient.GetAvailablePremiumPlan().Returns(premiumPlan); @@ -720,4 +720,63 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests await _stripeAdapter.DidNotReceive().SubscriptionCreateAsync(Arg.Any()); await _userService.DidNotReceive().SaveUserAsync(Arg.Any()); } + + [Theory, BitAutoData] + public async Task Run_WithAdditionalStorage_SetsCorrectMaxStorageGb( + User user, + TokenizedPaymentMethod paymentMethod, + BillingAddress billingAddress) + { + // Arrange + user.Premium = false; + user.GatewayCustomerId = null; + user.Email = "test@example.com"; + paymentMethod.Type = TokenizablePaymentMethodType.Card; + paymentMethod.Token = "card_token_123"; + billingAddress.Country = "US"; + billingAddress.PostalCode = "12345"; + const short additionalStorage = 2; + + // Setup premium plan with 5GB provided storage + var premiumPlan = new PremiumPlan + { + Name = "Premium", + Available = true, + LegacyYear = null, + Seat = new PremiumPurchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually }, + Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 } + }; + _pricingClient.GetAvailablePremiumPlan().Returns(premiumPlan); + + var mockCustomer = Substitute.For(); + mockCustomer.Id = "cust_123"; + mockCustomer.Address = new Address { Country = "US", PostalCode = "12345" }; + mockCustomer.Metadata = new Dictionary(); + + var mockSubscription = Substitute.For(); + mockSubscription.Id = "sub_123"; + mockSubscription.Status = "active"; + mockSubscription.Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + CurrentPeriodEnd = DateTime.UtcNow.AddDays(30) + } + ] + }; + + _stripeAdapter.CustomerCreateAsync(Arg.Any()).Returns(mockCustomer); + _stripeAdapter.SubscriptionCreateAsync(Arg.Any()).Returns(mockSubscription); + + // Act + var result = await _command.Run(user, paymentMethod, billingAddress, additionalStorage); + + // Assert + Assert.True(result.IsT0); + Assert.Equal((short)3, user.MaxStorageGb); // 1 (provided) + 2 (additional) = 3 + await _userService.Received(1).SaveUserAsync(user); + } + } diff --git a/test/Core.Test/Billing/Pricing/PricingClientTests.cs b/test/Core.Test/Billing/Pricing/PricingClientTests.cs index 189df15b9c..43329e9c2e 100644 --- a/test/Core.Test/Billing/Pricing/PricingClientTests.cs +++ b/test/Core.Test/Billing/Pricing/PricingClientTests.cs @@ -3,7 +3,6 @@ using Bit.Core.Billing; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Pricing; using Bit.Core.Services; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.Extensions.Logging; @@ -34,7 +33,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -70,7 +68,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -109,7 +106,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -144,7 +140,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -179,7 +174,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -217,7 +211,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -258,7 +251,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(false); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -297,7 +289,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -339,33 +330,12 @@ public class PricingClientTests Assert.Null(result); } - [Theory, BitAutoData] - public async Task GetPlan_WhenPricingServiceDisabled_ReturnsStaticStorePlan( - SutProvider sutProvider) - { - // Arrange - sutProvider.GetDependency().SelfHosted = false; - - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.UsePricingService) - .Returns(false); - - // Act - var result = await sutProvider.Sut.GetPlan(PlanType.FamiliesAnnually); - - // Assert - Assert.NotNull(result); - Assert.Equal(PlanType.FamiliesAnnually, result.Type); - } - [Theory, BitAutoData] public async Task GetPlan_WhenLookupKeyNotFound_ReturnsNull( SutProvider sutProvider) { // Arrange - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.UsePricingService) - .Returns(true); + sutProvider.GetDependency().SelfHosted = false; // Act - Using PlanType that doesn't have a lookup key mapping var result = await sutProvider.Sut.GetPlan(unchecked((PlanType)999)); @@ -384,7 +354,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -413,7 +382,6 @@ public class PricingClientTests var featureService = Substitute.For(); featureService.IsEnabled(FeatureFlagKeys.PM26462_Milestone_3).Returns(true); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; @@ -450,26 +418,6 @@ public class PricingClientTests Assert.Empty(result); } - [Theory, BitAutoData] - public async Task ListPlans_WhenPricingServiceDisabled_ReturnsStaticStorePlans( - SutProvider sutProvider) - { - // Arrange - sutProvider.GetDependency().SelfHosted = false; - - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.UsePricingService) - .Returns(false); - - // Act - var result = await sutProvider.Sut.ListPlans(); - - // Assert - Assert.NotNull(result); - Assert.NotEmpty(result); - Assert.Equal(StaticStore.Plans.Count(), result.Count); - } - [Fact] public async Task ListPlans_WhenPricingServiceReturnsError_ThrowsBillingException() { @@ -479,7 +427,6 @@ public class PricingClientTests .Respond(HttpStatusCode.InternalServerError); var featureService = Substitute.For(); - featureService.IsEnabled(FeatureFlagKeys.UsePricingService).Returns(true); var globalSettings = new GlobalSettings { SelfHosted = false }; diff --git a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs index 40fa4c412d..6a7e9d3190 100644 --- a/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs +++ b/test/Core.Test/Billing/Services/OrganizationBillingServiceTests.cs @@ -10,7 +10,7 @@ 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.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -31,10 +31,10 @@ public class OrganizationBillingServiceTests SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(organizationId).Returns(organization); - sutProvider.GetDependency().ListPlans().Returns(StaticStore.Plans.ToList()); + sutProvider.GetDependency().ListPlans().Returns(MockPlans.Plans.ToList()); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var subscriberService = sutProvider.GetDependency(); var organizationSeatCount = new OrganizationSeatCounts { Users = 1, Sponsored = 0 }; @@ -97,10 +97,10 @@ public class OrganizationBillingServiceTests { sutProvider.GetDependency().GetByIdAsync(organizationId).Returns(organization); - sutProvider.GetDependency().ListPlans().Returns(StaticStore.Plans.ToList()); + sutProvider.GetDependency().ListPlans().Returns(MockPlans.Plans.ToList()); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); sutProvider.GetDependency() .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id) @@ -134,7 +134,7 @@ public class OrganizationBillingServiceTests SutProvider sutProvider) { // Arrange - var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + var plan = MockPlans.Get(PlanType.TeamsAnnually); organization.PlanType = PlanType.TeamsAnnually; organization.GatewayCustomerId = "cus_test123"; organization.GatewaySubscriptionId = null; @@ -210,7 +210,7 @@ public class OrganizationBillingServiceTests SutProvider sutProvider) { // Arrange - var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + var plan = MockPlans.Get(PlanType.TeamsAnnually); organization.PlanType = PlanType.TeamsAnnually; organization.GatewayCustomerId = "cus_test123"; organization.GatewaySubscriptionId = null; @@ -284,7 +284,7 @@ public class OrganizationBillingServiceTests SutProvider sutProvider) { // Arrange - var plan = StaticStore.GetPlan(PlanType.TeamsAnnually); + var plan = MockPlans.Get(PlanType.TeamsAnnually); organization.PlanType = PlanType.TeamsAnnually; organization.GatewayCustomerId = "cus_test123"; organization.GatewaySubscriptionId = null; diff --git a/test/Core.Test/Models/Business/CompleteSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/CompleteSubscriptionUpdateTests.cs index dee805033a..39374755eb 100644 --- a/test/Core.Test/Models/Business/CompleteSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/CompleteSubscriptionUpdateTests.cs @@ -2,7 +2,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.Business; using Bit.Core.Test.AutoFixture.OrganizationFixtures; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Stripe; using Xunit; @@ -17,7 +17,7 @@ public class CompleteSubscriptionUpdateTests public void UpgradeItemOptions_TeamsStarterToTeams_ReturnsCorrectOptions( Organization organization) { - var teamsStarterPlan = StaticStore.GetPlan(PlanType.TeamsStarter); + var teamsStarterPlan = MockPlans.Get(PlanType.TeamsStarter); var subscription = new Subscription { @@ -35,7 +35,7 @@ public class CompleteSubscriptionUpdateTests } }; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); var updatedSubscriptionData = new SubscriptionData { @@ -66,7 +66,7 @@ public class CompleteSubscriptionUpdateTests // 5 purchased, 1 base organization.MaxStorageGb = 6; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); var subscription = new Subscription { @@ -102,7 +102,7 @@ public class CompleteSubscriptionUpdateTests } }; - var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly); var updatedSubscriptionData = new SubscriptionData { @@ -173,7 +173,7 @@ public class CompleteSubscriptionUpdateTests // 5 purchased, 1 base organization.MaxStorageGb = 6; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); var subscription = new Subscription { @@ -209,7 +209,7 @@ public class CompleteSubscriptionUpdateTests } }; - var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly); var updatedSubscriptionData = new SubscriptionData { @@ -277,8 +277,8 @@ public class CompleteSubscriptionUpdateTests public void RevertItemOptions_TeamsStarterToTeams_ReturnsCorrectOptions( Organization organization) { - var teamsStarterPlan = StaticStore.GetPlan(PlanType.TeamsStarter); - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); + var teamsStarterPlan = MockPlans.Get(PlanType.TeamsStarter); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); var subscription = new Subscription { @@ -325,8 +325,8 @@ public class CompleteSubscriptionUpdateTests // 5 purchased, 1 base organization.MaxStorageGb = 6; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); - var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); + var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly); var subscription = new Subscription { @@ -431,8 +431,8 @@ public class CompleteSubscriptionUpdateTests // 5 purchased, 1 base organization.MaxStorageGb = 6; - var teamsMonthlyPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); - var enterpriseMonthlyPlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var teamsMonthlyPlan = MockPlans.Get(PlanType.TeamsMonthly); + var enterpriseMonthlyPlan = MockPlans.Get(PlanType.EnterpriseMonthly); var subscription = new Subscription { diff --git a/test/Core.Test/Models/Business/SeatSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/SeatSubscriptionUpdateTests.cs index b6e9f63640..d96f9fea95 100644 --- a/test/Core.Test/Models/Business/SeatSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/SeatSubscriptionUpdateTests.cs @@ -1,7 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Models.Business; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Stripe; using Xunit; @@ -27,7 +27,7 @@ public class SeatSubscriptionUpdateTests public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var subscription = new Subscription { @@ -69,7 +69,7 @@ public class SeatSubscriptionUpdateTests [BitAutoData(PlanType.TeamsAnnually)] public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var subscription = new Subscription { diff --git a/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs index 20405b07b0..1f75b6a23a 100644 --- a/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs @@ -4,7 +4,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.StaticStore; using Bit.Core.Test.AutoFixture.OrganizationFixtures; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Xunit; @@ -16,7 +16,7 @@ public class SecretsManagerSubscriptionUpdateTests private static TheoryData ToPlanTheory(List types) { var theoryData = new TheoryData(); - var plans = types.Select(StaticStore.GetPlan).ToArray(); + var plans = types.Select(MockPlans.Get).ToArray(); theoryData.AddRange(plans); return theoryData; } diff --git a/test/Core.Test/Models/Business/ServiceAccountSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/ServiceAccountSubscriptionUpdateTests.cs index 3663277933..a1e9669c87 100644 --- a/test/Core.Test/Models/Business/ServiceAccountSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/ServiceAccountSubscriptionUpdateTests.cs @@ -1,7 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Models.Business; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Stripe; using Xunit; @@ -27,7 +27,7 @@ public class ServiceAccountSubscriptionUpdateTests public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var subscription = new Subscription { @@ -69,7 +69,7 @@ public class ServiceAccountSubscriptionUpdateTests [BitAutoData(PlanType.TeamsAnnually)] public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var quantity = 5; var subscription = new Subscription diff --git a/test/Core.Test/Models/Business/SmSeatSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/SmSeatSubscriptionUpdateTests.cs index ee9dc615b6..d9fcaf991e 100644 --- a/test/Core.Test/Models/Business/SmSeatSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/SmSeatSubscriptionUpdateTests.cs @@ -1,7 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Models.Business; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Stripe; using Xunit; @@ -27,7 +27,7 @@ public class SmSeatSubscriptionUpdateTests public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var quantity = 3; var subscription = new Subscription @@ -70,7 +70,7 @@ public class SmSeatSubscriptionUpdateTests [BitAutoData(PlanType.TeamsAnnually)] public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType, Organization organization) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); organization.PlanType = planType; var quantity = 5; var subscription = new Subscription diff --git a/test/Core.Test/Models/Business/StorageSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/StorageSubscriptionUpdateTests.cs index 79b29fcd0c..21326c5324 100644 --- a/test/Core.Test/Models/Business/StorageSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/StorageSubscriptionUpdateTests.cs @@ -1,6 +1,6 @@ using Bit.Core.Billing.Enums; using Bit.Core.Models.Business; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture.Attributes; using Stripe; using Xunit; @@ -26,7 +26,7 @@ public class StorageSubscriptionUpdateTests public void UpgradeItemsOptions_ReturnsCorrectOptions(PlanType planType) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var subscription = new Subscription { Items = new StripeList @@ -77,7 +77,7 @@ public class StorageSubscriptionUpdateTests [BitAutoData(PlanType.TeamsStarter)] public void RevertItemsOptions_ReturnsCorrectOptions(PlanType planType) { - var plan = StaticStore.GetPlan(planType); + var plan = MockPlans.Get(planType); var subscription = new Subscription { Items = new StripeList diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs index 5feee0f13a..515b4d7ba1 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/FamiliesForEnterpriseTestsBase.cs @@ -1,22 +1,22 @@ using Bit.Core.Billing.Enums; using Bit.Core.Enums; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; public abstract class FamiliesForEnterpriseTestsBase { public static IEnumerable EnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier == ProductTierType.Enterprise).Select(p => new object[] { p }); public static IEnumerable NonEnterprisePlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Enterprise).Select(p => new object[] { p }); public static IEnumerable FamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier == ProductTierType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier == ProductTierType.Families).Select(p => new object[] { p }); public static IEnumerable NonFamiliesPlanTypes => - Enum.GetValues().Where(p => StaticStore.GetPlan(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p }); + Enum.GetValues().Where(p => MockPlans.Get(p).ProductTier != ProductTierType.Families).Select(p => new object[] { p }); public static IEnumerable NonConfirmedOrganizationUsersStatuses => Enum.GetValues() diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs index 02ae40798b..fb64c11312 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs @@ -9,7 +9,7 @@ using Bit.Core.Models.Business; using Bit.Core.Models.StaticStore; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions; using Bit.Core.Services; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -42,7 +42,7 @@ public class AddSecretsManagerSubscriptionCommandTests { organization.PlanType = planType; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(plan); await sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts); @@ -88,7 +88,7 @@ public class AddSecretsManagerSubscriptionCommandTests organization.GatewayCustomerId = null; organization.PlanType = PlanType.EnterpriseAnnually; sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts)); Assert.Contains("No payment method found.", exception.Message); @@ -106,7 +106,7 @@ public class AddSecretsManagerSubscriptionCommandTests organization.GatewaySubscriptionId = null; organization.PlanType = PlanType.EnterpriseAnnually; sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts)); Assert.Contains("No subscription found.", exception.Message); @@ -139,7 +139,7 @@ public class AddSecretsManagerSubscriptionCommandTests provider.Type = ProviderType.Msp; sutProvider.GetDependency().GetByOrganizationIdAsync(organization.Id).Returns(provider); sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType) - .Returns(StaticStore.GetPlan(organization.PlanType)); + .Returns(MockPlans.Get(organization.PlanType)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(organization, 10, 10)); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs index 1e764de6d7..baa9e04c22 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs @@ -11,7 +11,7 @@ using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Test.AutoFixture.OrganizationFixtures; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -26,7 +26,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests private static TheoryData ToPlanTheory(List types) { var theoryData = new TheoryData(); - var plans = types.Select(StaticStore.GetPlan).ToArray(); + var plans = types.Select(MockPlans.Get).ToArray(); theoryData.AddRange(plans); return theoryData; } @@ -164,7 +164,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, autoscaling).AdjustSeats(2); sutProvider.GetDependency().SelfHosted.Returns(true); @@ -180,7 +180,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider, Organization organization) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); organization.UseSecretsManager = false; var update = new SecretsManagerSubscriptionUpdate(organization, plan, false); @@ -289,7 +289,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.MaxAutoscaleSmSeats = maxSeatCount; organization.PlanType = PlanType.EnterpriseAnnually; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { @@ -334,7 +334,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests var ownerDetailsList = new List { new() { Email = "owner@example.com" } }; organization.PlanType = PlanType.EnterpriseAnnually; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { @@ -372,7 +372,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { organization.SmSeats = null; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustSeats(1); var exception = await Assert.ThrowsAsync( @@ -388,7 +388,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustSeats(-2); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -404,7 +404,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { organization.PlanType = planType; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustSeats(1); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -422,7 +422,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.SmSeats = 9; organization.MaxAutoscaleSmSeats = 10; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustSeats(2); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -436,7 +436,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmSeats = organization.SmSeats + 10, @@ -455,7 +455,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmSeats = 0, @@ -475,7 +475,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { organization.SmSeats = 8; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmSeats = 7, @@ -498,7 +498,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests var smServiceAccounts = 300; var existingServiceAccountCount = 299; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmServiceAccounts = smServiceAccounts, @@ -531,7 +531,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { var smServiceAccounts = 300; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmServiceAccounts = smServiceAccounts, @@ -571,7 +571,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { organization.SmServiceAccounts = null; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustServiceAccounts(1); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -585,7 +585,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustServiceAccounts(-2); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -601,7 +601,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests SutProvider sutProvider) { organization.PlanType = planType; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false).AdjustServiceAccounts(1); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -619,7 +619,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.SmServiceAccounts = 9; organization.MaxAutoscaleSmServiceAccounts = 10; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, true).AdjustServiceAccounts(2); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); @@ -639,7 +639,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.SmServiceAccounts = smServiceAccount - 5; organization.MaxAutoscaleSmServiceAccounts = 2 * smServiceAccount; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmServiceAccounts = smServiceAccount, @@ -662,7 +662,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.SmServiceAccounts = newSmServiceAccounts - 10; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmServiceAccounts = newSmServiceAccounts, @@ -707,7 +707,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.SmSeats = smSeats - 1; organization.MaxAutoscaleSmSeats = smSeats * 2; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { SmSeats = smSeats, @@ -728,7 +728,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests { organization.PlanType = planType; organization.SmSeats = 2; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { MaxAutoscaleSmSeats = 3 @@ -748,7 +748,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests { organization.PlanType = planType; organization.SmSeats = 2; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { MaxAutoscaleSmSeats = 2 @@ -769,7 +769,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests organization.PlanType = planType; organization.SmServiceAccounts = 3; - var plan = StaticStore.GetPlan(organization.PlanType); + var plan = MockPlans.Get(organization.PlanType); var update = new SecretsManagerSubscriptionUpdate(organization, plan, false) { MaxAutoscaleSmServiceAccounts = 3 }; var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSubscriptionAsync(update)); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs index 704f89ba3f..3841f7a619 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs @@ -8,7 +8,7 @@ using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; -using Bit.Core.Utilities; +using Bit.Core.Test.Billing.Mocks; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -45,7 +45,7 @@ public class UpgradeOrganizationPlanCommandTests SutProvider sutProvider) { upgrade.Plan = organization.PlanType; - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade)); @@ -61,7 +61,7 @@ public class UpgradeOrganizationPlanCommandTests upgrade.AdditionalSmSeats = 10; upgrade.AdditionalServiceAccounts = 10; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade)); Assert.Contains("already on this plan", exception.Message); @@ -73,11 +73,11 @@ public class UpgradeOrganizationPlanCommandTests SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); upgrade.AdditionalSmSeats = 10; upgrade.AdditionalSeats = 10; upgrade.Plan = PlanType.TeamsAnnually; - sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan)); sutProvider.GetDependency() .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts { @@ -104,7 +104,7 @@ public class UpgradeOrganizationPlanCommandTests organization.PlanType = PlanType.FamiliesAnnually; - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); organizationUpgrade.AdditionalSeats = 30; organizationUpgrade.UseSecretsManager = true; @@ -113,7 +113,7 @@ public class UpgradeOrganizationPlanCommandTests organizationUpgrade.AdditionalStorageGb = 3; organizationUpgrade.Plan = planType; - sutProvider.GetDependency().GetPlanOrThrow(organizationUpgrade.Plan).Returns(StaticStore.GetPlan(organizationUpgrade.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(organizationUpgrade.Plan).Returns(MockPlans.Get(organizationUpgrade.Plan)); sutProvider.GetDependency() .GetOccupiedSeatCountByOrganizationIdAsync(organization.Id).Returns(new OrganizationSeatCounts { @@ -123,7 +123,7 @@ public class UpgradeOrganizationPlanCommandTests await sutProvider.Sut.UpgradePlanAsync(organization.Id, organizationUpgrade); await sutProvider.GetDependency().Received(1).AdjustSubscription( organization, - StaticStore.GetPlan(planType), + MockPlans.Get(planType), organizationUpgrade.AdditionalSeats, organizationUpgrade.UseSecretsManager, organizationUpgrade.AdditionalSmSeats, @@ -141,12 +141,12 @@ public class UpgradeOrganizationPlanCommandTests public async Task UpgradePlan_SM_Passes(PlanType planType, Organization organization, OrganizationUpgrade upgrade, SutProvider sutProvider) { - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); upgrade.Plan = planType; - sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan)); - var plan = StaticStore.GetPlan(upgrade.Plan); + var plan = MockPlans.Get(upgrade.Plan); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); @@ -184,10 +184,10 @@ public class UpgradeOrganizationPlanCommandTests upgrade.AdditionalSeats = 15; upgrade.AdditionalSmSeats = 1; upgrade.AdditionalServiceAccounts = 0; - sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan)); organization.SmSeats = 2; - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency() @@ -218,11 +218,11 @@ public class UpgradeOrganizationPlanCommandTests upgrade.AdditionalSeats = 15; upgrade.AdditionalSmSeats = 1; upgrade.AdditionalServiceAccounts = 0; - sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(StaticStore.GetPlan(upgrade.Plan)); + sutProvider.GetDependency().GetPlanOrThrow(upgrade.Plan).Returns(MockPlans.Get(upgrade.Plan)); organization.SmSeats = 1; organization.SmServiceAccounts = currentServiceAccounts; - sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(StaticStore.GetPlan(organization.PlanType)); + sutProvider.GetDependency().GetPlanOrThrow(organization.PlanType).Returns(MockPlans.Get(organization.PlanType)); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency() diff --git a/test/Core.Test/Services/StripePaymentServiceTests.cs b/test/Core.Test/Services/StripePaymentServiceTests.cs index 863fe716d4..dc62af0872 100644 --- a/test/Core.Test/Services/StripePaymentServiceTests.cs +++ b/test/Core.Test/Services/StripePaymentServiceTests.cs @@ -1,11 +1,11 @@ using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models.StaticStore.Plans; using Bit.Core.Billing.Pricing; using Bit.Core.Billing.Tax.Requests; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Services; +using Bit.Core.Test.Billing.Mocks.Plans; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; diff --git a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs index 8c1cb1d5ff..b92477e73d 100644 --- a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs +++ b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs @@ -238,4 +238,55 @@ public class ImportCiphersAsyncCommandTests Assert.Equal("This organization can only have a maximum of " + $"{organization.MaxCollections} collections.", exception.Message); } + + [Theory, BitAutoData] + public async Task ImportIntoOrganizationalVaultAsync_WithNullImportingOrgUser_SkipsCollectionUserCreation( + Organization organization, + Guid importingUserId, + List collections, + List ciphers, + SutProvider sutProvider) + { + organization.MaxCollections = null; + + foreach (var collection in collections) + { + collection.OrganizationId = organization.Id; + } + + foreach (var cipher in ciphers) + { + cipher.OrganizationId = organization.Id; + } + + KeyValuePair[] collectionRelationships = { + new(0, 0), + new(1, 1), + new(2, 2) + }; + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + // Simulate provider-created org with no members - importing user is NOT an org member + sutProvider.GetDependency() + .GetByOrganizationAsync(organization.Id, importingUserId) + .Returns((OrganizationUser)null); + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organization.Id) + .Returns(new List()); + + await sutProvider.Sut.ImportIntoOrganizationalVaultAsync(collections, ciphers, collectionRelationships, importingUserId); + + // Verify ciphers were created but no CollectionUser entries were created (because the organization user (importingUserId) is null) + await sutProvider.GetDependency().Received(1).CreateAsync( + ciphers, + Arg.Is>(cols => cols.Count() == collections.Count), + Arg.Is>(cc => cc.Count() == ciphers.Count), + Arg.Is>(cus => !cus.Any())); + + await sutProvider.GetDependency().Received(1).PushSyncVaultAsync(importingUserId); + } } diff --git a/test/Core.Test/Utilities/StaticStoreTests.cs b/test/Core.Test/Utilities/StaticStoreTests.cs index 01e2ab8914..5d5b03dcd6 100644 --- a/test/Core.Test/Utilities/StaticStoreTests.cs +++ b/test/Core.Test/Utilities/StaticStoreTests.cs @@ -1,5 +1,4 @@ -using Bit.Core.Billing.Enums; -using Bit.Core.Utilities; +using Bit.Core.Utilities; using Xunit; namespace Bit.Core.Test.Utilities; @@ -7,28 +6,6 @@ namespace Bit.Core.Test.Utilities; public class StaticStoreTests { - [Fact] - public void StaticStore_Initialization_Success() - { - var plans = StaticStore.Plans.ToList(); - Assert.NotNull(plans); - Assert.NotEmpty(plans); - Assert.Equal(23, plans.Count); - } - - [Theory] - [InlineData(PlanType.EnterpriseAnnually)] - [InlineData(PlanType.EnterpriseMonthly)] - [InlineData(PlanType.TeamsMonthly)] - [InlineData(PlanType.TeamsAnnually)] - [InlineData(PlanType.TeamsStarter)] - public void StaticStore_GetPlan_Success(PlanType planType) - { - var plan = StaticStore.GetPlan(planType); - Assert.NotNull(plan); - Assert.Equal(planType, plan.Type); - } - [Fact] public void StaticStore_GlobalEquivalentDomains_OnlyAsciiAllowed() { diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index fb53c41bad..c5eecb8f34 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -225,130 +225,6 @@ public class CipherServiceTests Assert.NotNull(result.uploadUrl); } - [Theory, BitAutoData] - public async Task UploadFileForExistingAttachmentAsync_WrongRevisionDate_Throws(SutProvider sutProvider, - Cipher cipher) - { - var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1); - var stream = new MemoryStream(); - var attachment = new CipherAttachment.MetaData - { - AttachmentId = "test-attachment-id", - Size = 100, - FileName = "test.txt", - Key = "test-key" - }; - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UploadFileForExistingAttachmentAsync(stream, cipher, attachment, lastKnownRevisionDate)); - Assert.Contains("out of date", exception.Message); - } - - [Theory] - [BitAutoData("")] - [BitAutoData("Correct Time")] - public async Task UploadFileForExistingAttachmentAsync_CorrectRevisionDate_DoesNotThrow(string revisionDateString, - SutProvider sutProvider, CipherDetails cipher) - { - var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate; - var stream = new MemoryStream(new byte[100]); - var attachmentId = "test-attachment-id"; - var attachment = new CipherAttachment.MetaData - { - AttachmentId = attachmentId, - Size = 100, - FileName = "test.txt", - Key = "test-key" - }; - - // Set the attachment on the cipher so ValidateCipherAttachmentFile can find it - cipher.SetAttachments(new Dictionary - { - [attachmentId] = attachment - }); - - sutProvider.GetDependency() - .UploadNewAttachmentAsync(stream, cipher, attachment) - .Returns(Task.CompletedTask); - - sutProvider.GetDependency() - .ValidateFileAsync(cipher, attachment, Arg.Any()) - .Returns((true, 100L)); - - sutProvider.GetDependency() - .UpdateAttachmentAsync(Arg.Any()) - .Returns(Task.CompletedTask); - - await sutProvider.Sut.UploadFileForExistingAttachmentAsync(stream, cipher, attachment, lastKnownRevisionDate); - - await sutProvider.GetDependency().Received(1) - .UploadNewAttachmentAsync(stream, cipher, attachment); - } - - [Theory, BitAutoData] - public async Task CreateAttachmentShareAsync_WrongRevisionDate_Throws(SutProvider sutProvider, - Cipher cipher, Guid organizationId) - { - var lastKnownRevisionDate = cipher.RevisionDate.AddDays(-1); - var stream = new MemoryStream(); - var fileName = "test.txt"; - var key = "test-key"; - var attachmentId = "attachment-id"; - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.CreateAttachmentShareAsync(cipher, stream, fileName, key, 100, attachmentId, organizationId, lastKnownRevisionDate)); - Assert.Contains("out of date", exception.Message); - } - - [Theory] - [BitAutoData("")] - [BitAutoData("Correct Time")] - public async Task CreateAttachmentShareAsync_CorrectRevisionDate_DoesNotThrow(string revisionDateString, - SutProvider sutProvider, CipherDetails cipher, Guid organizationId) - { - var lastKnownRevisionDate = string.IsNullOrEmpty(revisionDateString) ? (DateTime?)null : cipher.RevisionDate; - var stream = new MemoryStream(new byte[100]); - var fileName = "test.txt"; - var key = "test-key"; - var attachmentId = "attachment-id"; - - // Setup cipher with existing attachment (no TempMetadata) - cipher.OrganizationId = null; - cipher.SetAttachments(new Dictionary - { - [attachmentId] = new CipherAttachment.MetaData - { - AttachmentId = attachmentId, - Size = 100, - FileName = "existing.txt", - Key = "existing-key" - } - }); - - // Mock organization - var organization = new Organization - { - Id = organizationId, - MaxStorageGb = 1 - }; - sutProvider.GetDependency() - .GetByIdAsync(organizationId) - .Returns(organization); - - sutProvider.GetDependency() - .UploadShareAttachmentAsync(stream, cipher.Id, organizationId, Arg.Any()) - .Returns(Task.CompletedTask); - - sutProvider.GetDependency() - .UpdateAttachmentAsync(Arg.Any()) - .Returns(Task.CompletedTask); - - await sutProvider.Sut.CreateAttachmentShareAsync(cipher, stream, fileName, key, 100, attachmentId, organizationId, lastKnownRevisionDate); - - await sutProvider.GetDependency().Received(1) - .UploadShareAttachmentAsync(stream, cipher.Id, organizationId, Arg.Any()); - } - [Theory] [BitAutoData] public async Task SaveDetailsAsync_PersonalVault_WithOrganizationDataOwnershipPolicyEnabled_Throws( diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs index 157d6a2589..1e1e512b27 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Data.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; @@ -1487,8 +1488,15 @@ public class OrganizationUserRepositoryTests const string key = "test-key"; orgUser.Key = key; + var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm + { + OrganizationUserId = orgUser.Id, + UserId = user.Id, + Key = key + }; + // Act - var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser); // Assert Assert.True(result); @@ -1502,27 +1510,6 @@ public class OrganizationUserRepositoryTests 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, @@ -1533,8 +1520,17 @@ public class OrganizationUserRepositoryTests var user = await userRepository.CreateTestUserAsync(); var orgUser = await organizationUserRepository.CreateConfirmedTestOrganizationUserAsync(organization, user); + orgUser.Status = OrganizationUserStatusType.Accepted; // To simulate a second call to ConfirmOrganizationUserAsync + + var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm + { + OrganizationUserId = orgUser.Id, + UserId = user.Id, + Key = "test-key" + }; + // Act - var result = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + var result = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser); // Assert Assert.False(result); @@ -1547,30 +1543,6 @@ public class OrganizationUserRepositoryTests 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, @@ -1582,9 +1554,16 @@ public class OrganizationUserRepositoryTests var user = await userRepository.CreateTestUserAsync(); var orgUser = await organizationUserRepository.CreateAcceptedTestOrganizationUserAsync(organization, user); + var acceptedOrganizationUser = new AcceptedOrganizationUserToConfirm + { + OrganizationUserId = orgUser.Id, + UserId = user.Id, + Key = "test-key" + }; + // Act - First call should confirm - var firstResult = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); - var secondResult = await organizationUserRepository.ConfirmOrganizationUserAsync(orgUser); + var firstResult = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser); + var secondResult = await organizationUserRepository.ConfirmOrganizationUserAsync(acceptedOrganizationUser); // Assert Assert.True(firstResult); @@ -1603,14 +1582,11 @@ public class OrganizationUserRepositoryTests IOrganizationUserRepository organizationUserRepository) { // Arrange - var nonExistentUser = new OrganizationUser + var nonExistentUser = new AcceptedOrganizationUserToConfirm { - Id = Guid.NewGuid(), - OrganizationId = Guid.NewGuid(), + OrganizationUserId = Guid.NewGuid(), UserId = Guid.NewGuid(), - Email = "nonexistent@bitwarden.com", - Status = OrganizationUserStatusType.Accepted, - Type = OrganizationUserType.Owner + Key = "test-key" }; // Act diff --git a/util/Migrator/DbScripts/2025-11-12_00_AddMaxStorageGbIncreased.sql b/util/Migrator/DbScripts/2025-11-12_00_AddMaxStorageGbIncreased.sql new file mode 100644 index 0000000000..4e4909b826 --- /dev/null +++ b/util/Migrator/DbScripts/2025-11-12_00_AddMaxStorageGbIncreased.sql @@ -0,0 +1,350 @@ +-- Add MaxStorageGbIncreased column to User table +IF COL_LENGTH('[dbo].[User]', 'MaxStorageGbIncreased') IS NULL +BEGIN + ALTER TABLE [dbo].[User] ADD [MaxStorageGbIncreased] SMALLINT NULL; +END +GO + +-- Add MaxStorageGbIncreased column to Organization table +IF COL_LENGTH('[dbo].[Organization]', 'MaxStorageGbIncreased') IS NULL +BEGIN + ALTER TABLE [dbo].[Organization] ADD [MaxStorageGbIncreased] SMALLINT NULL; +END +GO + +-- Update UserView to use COALESCE for MaxStorageGb +CREATE OR ALTER VIEW [dbo].[UserView] +AS +SELECT + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + COALESCE([MaxStorageGbIncreased], [MaxStorageGb]) AS [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [ApiKey], + [Kdf], + [KdfIterations], + [KdfMemory], + [KdfParallelism], + [CreationDate], + [RevisionDate], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate], + [VerifyDevices], + [SecurityState], + [SecurityVersion], + [SignedPublicKey] +FROM + [dbo].[User] +GO + +-- Update OrganizationView to use COALESCE for MaxStorageGb +CREATE OR ALTER VIEW [dbo].[OrganizationView] +AS +SELECT + [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], + COALESCE([MaxStorageGbIncreased], [MaxStorageGb]) AS [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], + [LimitItemDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [UseOrganizationDomains], + [UseAdminSponsoredFamilies], + [SyncSeats], + [UseAutomaticUserConfirmation] +FROM + [dbo].[Organization] +GO + + +-- Update OrganizationUserOrganizationDetailsView +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], + COALESCE(O.[MaxStorageGbIncreased], O.[MaxStorageGb]) AS [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 + +-- Update ProviderUserProviderOrganizationDetailsView +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.[UseSecretsManager], + O.[UsePasswordManager], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[Seats], + O.[MaxCollections], + COALESCE(O.[MaxStorageGbIncreased], O.[MaxStorageGb]) AS [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], + SS.[Enabled] SsoEnabled, + SS.[Data] SsoConfig +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] +LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = O.[Id] +GO + +-- Refresh views that reference Organization table +EXEC sp_refreshview N'[dbo].[OrganizationCipherDetailsCollectionsView]'; +EXEC sp_refreshview N'[dbo].[OrganizationUserOrganizationDetailsView]'; +EXEC sp_refreshview N'[dbo].[ProviderOrganizationOrganizationDetailsView]'; +EXEC sp_refreshview N'[dbo].[ProviderUserProviderOrganizationDetailsView]'; +GO + +-- Refresh views that reference User table +EXEC sp_refreshview N'[dbo].[EmergencyAccessDetailsView]'; +EXEC sp_refreshview N'[dbo].[OrganizationUserUserDetailsView]'; +EXEC sp_refreshview N'[dbo].[ProviderUserUserDetailsView]'; +EXEC sp_refreshview N'[dbo].[UserEmailDomainView]'; +GO + +-- Refresh stored procedures that reference UserView +EXEC sp_refreshsqlmodule N'[dbo].[Notification_ReadByUserIdAndStatus]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadById]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByOrganizationIdEmail]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByOrganizationIdUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByOrganizationIdWithClaimedDomains]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByOrganizationIdWithClaimedDomains_V2]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByUserIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByUserIdWithPolicyDetails]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadOccupiedSmSeatCountByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadById]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadByIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadByOrganizationIdStatus]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadByProviderId]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadByProviderIdUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUser_ReadByUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadByEmail]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadByEmails]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadById]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadByIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadByIdsWithCalculatedPremium]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadByPremium]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_ReadBySsoUserOrganizationIdExternalId]'; +EXEC sp_refreshsqlmodule N'[dbo].[User_Search]'; +GO + +-- Refresh stored procedures that reference OrganizationView +EXEC sp_refreshsqlmodule N'[dbo].[Organization_GetOrganizationsForSubscriptionSync]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByClaimedUserEmailDomain]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByEnabled]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadById]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByIdentifier]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByLicenseKey]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByProviderId]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadByUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadManyByIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_ReadOccupiedSeatCountByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_Search]'; +EXEC sp_refreshsqlmodule N'[dbo].[Organization_UnassignedToProviderSearch]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationDomainSsoDetails_ReadByEmail]'; +EXEC sp_refreshsqlmodule N'[dbo].[PolicyDetails_ReadByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[PolicyDetails_ReadByUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[PolicyDetails_ReadByUserIdsPolicyType]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderOrganization_ReadById]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderOrganization_ReadByOrganizationId]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderOrganization_ReadCountByOrganizationIds]'; +EXEC sp_refreshsqlmodule N'[dbo].[ProviderOrganizationProviderDetails_ReadByUserId]'; +EXEC sp_refreshsqlmodule N'[dbo].[VerifiedOrganizationDomainSsoDetails_ReadByEmail]'; +GO + +-- Refresh stored procedures that reference OrganizationUserOrganizationDetailsView +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatus]'; +EXEC sp_refreshsqlmodule N'[dbo].[OrganizationUserOrganizationDetails_ReadByUserIdStatusOrganizationId]'; +GO + +-- Refresh stored procedures that reference ProviderUserProviderOrganizationDetailsView +EXEC sp_refreshsqlmodule N'[dbo].[ProviderUserProviderOrganizationDetails_ReadByUserIdStatus]'; +GO + diff --git a/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.Designer.cs b/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.Designer.cs new file mode 100644 index 0000000000..0d50ccc349 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.Designer.cs @@ -0,0 +1,3446 @@ +// +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("20251112155802_AddMaxStorageGbIncreasedColumn")] + partial class AddMaxStorageGbIncreasedColumn + { + /// + 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("MaxStorageGbIncreased") + .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("ApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("ApplicationCount") + .HasColumnType("int"); + + b.Property("ApplicationData") + .HasColumnType("longtext"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalApplicationCount") + .HasColumnType("int"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalMemberCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("CriticalPasswordCount") + .HasColumnType("int"); + + b.Property("MemberAtRiskCount") + .HasColumnType("int"); + + b.Property("MemberCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("int"); + + b.Property("PasswordCount") + .HasColumnType("int"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SummaryData") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProjectId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("MaxStorageGbIncreased") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("SecurityState") + .HasColumnType("longtext"); + + b.Property("SecurityVersion") + .HasColumnType("int"); + + b.Property("SignedPublicKey") + .HasColumnType("longtext"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SignatureAlgorithm") + .HasColumnType("tinyint unsigned"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("EditorServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VersionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ArchivedDate") + .HasColumnType("datetime(6)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.cs b/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.cs new file mode 100644 index 0000000000..acdf253901 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20251112155802_AddMaxStorageGbIncreasedColumn.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class AddMaxStorageGbIncreasedColumn : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "User", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "Organization", + type: "smallint", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "User"); + + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "Organization"); + } +} diff --git a/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.Designer.cs b/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.Designer.cs new file mode 100644 index 0000000000..99e2535164 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.Designer.cs @@ -0,0 +1,3452 @@ +// +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("20251112155845_AddMaxStorageGbIncreasedColumn")] + partial class AddMaxStorageGbIncreasedColumn + { + /// + 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("MaxStorageGbIncreased") + .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("ApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("ApplicationCount") + .HasColumnType("integer"); + + b.Property("ApplicationData") + .HasColumnType("text"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalApplicationCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalMemberCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("CriticalPasswordCount") + .HasColumnType("integer"); + + b.Property("MemberAtRiskCount") + .HasColumnType("integer"); + + b.Property("MemberCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("integer"); + + b.Property("PasswordCount") + .HasColumnType("integer"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SummaryData") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("MaxStorageGbIncreased") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SecurityState") + .HasColumnType("text"); + + b.Property("SecurityVersion") + .HasColumnType("integer"); + + b.Property("SignedPublicKey") + .HasColumnType("text"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SignatureAlgorithm") + .HasColumnType("smallint"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("EditorServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.Property("VersionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ArchivedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.cs b/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.cs new file mode 100644 index 0000000000..0b742b9029 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20251112155845_AddMaxStorageGbIncreasedColumn.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class AddMaxStorageGbIncreasedColumn : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "User", + type: "smallint", + nullable: true); + + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "Organization", + type: "smallint", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "User"); + + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "Organization"); + } +} diff --git a/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.Designer.cs b/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.Designer.cs new file mode 100644 index 0000000000..28726db203 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.Designer.cs @@ -0,0 +1,3435 @@ +// +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("20251112155857_AddMaxStorageGbIncreasedColumn")] + partial class AddMaxStorageGbIncreasedColumn + { + /// + 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("MaxStorageGbIncreased") + .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("ApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("ApplicationData") + .HasColumnType("TEXT"); + + b.Property("ContentEncryptionKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CriticalApplicationAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalApplicationCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalMemberCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("CriticalPasswordCount") + .HasColumnType("INTEGER"); + + b.Property("MemberAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("MemberCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PasswordAtRiskCount") + .HasColumnType("INTEGER"); + + b.Property("PasswordCount") + .HasColumnType("INTEGER"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SummaryData") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GrantedServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasDatabaseName("IX_Event_DateOrganizationIdUserId") + .HasAnnotation("SqlServer:Clustered", false) + .HasAnnotation("SqlServer:Include", new[] { "ServiceAccountId", "GrantedServiceAccountId" }); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGbIncreased") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("SecurityState") + .HasColumnType("TEXT"); + + b.Property("SecurityVersion") + .HasColumnType("INTEGER"); + + b.Property("SignedPublicKey") + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SignatureAlgorithm") + .HasColumnType("INTEGER"); + + b.Property("SigningKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VerifyingKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("UserSignatureKeyPair", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EditorOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("EditorServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VersionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("EditorOrganizationUserId") + .HasDatabaseName("IX_SecretVersion_EditorOrganizationUserId"); + + b.HasIndex("EditorServiceAccountId") + .HasDatabaseName("IX_SecretVersion_EditorServiceAccountId"); + + b.HasIndex("SecretId") + .HasDatabaseName("IX_SecretVersion_SecretId"); + + b.ToTable("SecretVersion"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ArchivedDate") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserSignatureKeyPair", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.SecretVersion", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "EditorOrganizationUser") + .WithMany() + .HasForeignKey("EditorOrganizationUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "EditorServiceAccount") + .WithMany() + .HasForeignKey("EditorServiceAccountId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "Secret") + .WithMany("SecretVersions") + .HasForeignKey("SecretId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EditorOrganizationUser"); + + b.Navigation("EditorServiceAccount"); + + b.Navigation("Secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("SecretVersions"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.cs b/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.cs new file mode 100644 index 0000000000..a80458fab9 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20251112155857_AddMaxStorageGbIncreasedColumn.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class AddMaxStorageGbIncreasedColumn : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "User", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "MaxStorageGbIncreased", + table: "Organization", + type: "INTEGER", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "User"); + + migrationBuilder.DropColumn( + name: "MaxStorageGbIncreased", + table: "Organization"); + } +}