diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9784e1f9ab..11e79590f2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -71,6 +71,7 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev .github/workflows/repository-management.yml @bitwarden/team-platform-dev .github/workflows/test-database.yml @bitwarden/team-platform-dev .github/workflows/test.yml @bitwarden/team-platform-dev +**/*Platform* @bitwarden/team-platform-dev # Multiple owners - DO NOT REMOVE (BRE) **/packages.lock.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0b598ea56..510ce3318b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Verify format run: dotnet format --verify-no-changes @@ -81,7 +81,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Set up Node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 @@ -120,7 +120,7 @@ jobs: ls -atlh ../../../ - name: Upload project artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: ${{ matrix.project_name }}.zip path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip @@ -278,7 +278,7 @@ jobs: - name: Build Docker image id: build-docker - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: ${{ matrix.base_path }}/${{ matrix.project_name }} file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile @@ -307,14 +307,14 @@ jobs: - name: Scan Docker image id: container-scan - uses: anchore/scan-action@5ed195cc06065322983cae4bb31e2a751feb86fd # v5.2.0 + uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 # v6.0.0 with: image: ${{ steps.image-tags.outputs.primary_tag }} fail-build: false output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} @@ -329,7 +329,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Log in to Azure - production subscription uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 @@ -393,7 +393,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc') - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docker-stub-US.zip path: docker-stub-US.zip @@ -403,7 +403,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc') - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docker-stub-EU.zip path: docker-stub-EU.zip @@ -413,7 +413,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc') - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docker-stub-US-sha256.txt path: docker-stub-US-sha256.txt @@ -423,7 +423,7 @@ jobs: if: | github.event_name != 'pull_request_target' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc') - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: docker-stub-EU-sha256.txt path: docker-stub-EU-sha256.txt @@ -447,7 +447,7 @@ jobs: GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder" - name: Upload Public API Swagger artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: swagger.json path: swagger.json @@ -481,14 +481,14 @@ jobs: GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder" - name: Upload Internal API Swagger artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: internal.json path: internal.json if-no-files-found: error - name: Upload Identity Swagger artifact - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: identity.json path: identity.json @@ -517,7 +517,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Print environment run: | @@ -533,7 +533,7 @@ jobs: - name: Upload project artifact for Windows if: ${{ contains(matrix.target, 'win') == true }} - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: MsSqlMigratorUtility-${{ matrix.target }} path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe @@ -541,7 +541,7 @@ jobs: - name: Upload project artifact if: ${{ contains(matrix.target, 'win') == false }} - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: MsSqlMigratorUtility-${{ matrix.target }} path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility diff --git a/.github/workflows/code-references.yml b/.github/workflows/code-references.yml index eeb84f745b..7fcf864866 100644 --- a/.github/workflows/code-references.yml +++ b/.github/workflows/code-references.yml @@ -37,7 +37,7 @@ jobs: - name: Collect id: collect - uses: launchdarkly/find-code-references-in-pull-request@d008aa4f321d8cd35314d9cb095388dcfde84439 # v2.0.0 + uses: launchdarkly/find-code-references-in-pull-request@b2d44bb453e13c11fd1a6ada7b1e5f9fb0ace629 # v2.0.1 with: project-key: default environment-key: dev diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index d41ce91ec9..3d9806a267 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -98,7 +98,7 @@ jobs: version: ${{ inputs.version_number_override }} - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -197,7 +197,7 @@ jobs: - setup steps: - name: Generate GH App token - uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0 + uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index f071cb4ec3..e40ff07148 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -31,7 +31,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36 + uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 with: sarif_file: cx_result.sarif @@ -60,7 +60,7 @@ jobs: steps: - name: Set up JDK 17 - uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0 + uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0 with: java-version: 17 distribution: "zulu" @@ -72,7 +72,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Install SonarCloud scanner run: dotnet tool install dotnet-sonarscanner -g diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index 134e96b339..0d6361eca8 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -57,7 +57,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Restore tools run: dotnet tool restore @@ -107,7 +107,7 @@ jobs: run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"' env: CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true" - + - name: Migrate MariaDB working-directory: "util/MySqlMigrations" run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"' @@ -186,7 +186,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Print environment run: | @@ -200,7 +200,7 @@ jobs: shell: pwsh - name: Upload DACPAC - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: sql.dacpac path: Sql.dacpac @@ -226,7 +226,7 @@ jobs: shell: pwsh - name: Report validation results - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: report.xml path: | @@ -237,7 +237,7 @@ jobs: run: | if grep -q "<Operations>" "report.xml"; then echo - echo "Migrations are out of sync with sqlproj!" + echo "Migration files are not in sync with the files in the Sql project. Review to make sure that any stored procedures / other db changes match with the stored procedures in the Sql project." exit 1 else echo "Report looks good" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5f3b9871bc..5cc31f5c2f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up .NET - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0 + uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 - name: Print environment run: | @@ -77,7 +77,7 @@ jobs: fail-on-error: true - name: Upload to codecov.io - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 + uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 if: ${{ needs.check-test-secrets.outputs.available == 'true' }} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/Directory.Build.props b/Directory.Build.props index a27c4874f8..11478a436b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ <PropertyGroup> <TargetFramework>net8.0</TargetFramework> - <Version>2025.1.0</Version> + <Version>2025.1.1</Version> <RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace> <ImplicitUsings>enable</ImplicitUsings> @@ -64,4 +64,4 @@ </ItemGroup> </Target> -</Project> +</Project> \ No newline at end of file diff --git a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs index a6bf62871f..57349042d1 100644 --- a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs +++ b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs @@ -32,7 +32,8 @@ public class ProviderBillingService( IProviderOrganizationRepository providerOrganizationRepository, IProviderPlanRepository providerPlanRepository, IStripeAdapter stripeAdapter, - ISubscriberService subscriberService) : IProviderBillingService + ISubscriberService subscriberService, + ITaxService taxService) : IProviderBillingService { public async Task ChangePlan(ChangeProviderPlanCommand command) { @@ -335,14 +336,30 @@ public class ProviderBillingService( Metadata = new Dictionary<string, string> { { "region", globalSettings.BaseServiceUri.CloudRegion } - }, - TaxIdData = taxInfo.HasTaxId ? - [ - new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber } - ] - : null + } }; + if (!string.IsNullOrEmpty(taxInfo.TaxIdNumber)) + { + var taxIdType = taxService.GetStripeTaxCode(taxInfo.BillingAddressCountry, + taxInfo.TaxIdNumber); + + if (taxIdType == null) + { + logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.", + taxInfo.BillingAddressCountry, + taxInfo.TaxIdNumber); + throw new BadRequestException("billingTaxIdTypeInferenceError"); + } + + customerCreateOptions.TaxIdData = taxInfo.HasTaxId + ? + [ + new CustomerTaxIdDataOptions { Type = taxIdType, Value = taxInfo.TaxIdNumber } + ] + : null; + } + try { return await stripeAdapter.CustomerCreateAsync(customerCreateOptions); diff --git a/bitwarden_license/src/Sso/package-lock.json b/bitwarden_license/src/Sso/package-lock.json index 67fc4d71f1..f1e23abd60 100644 --- a/bitwarden_license/src/Sso/package-lock.json +++ b/bitwarden_license/src/Sso/package-lock.json @@ -779,9 +779,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -799,9 +799,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -819,9 +819,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001688", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", - "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -840,9 +840,9 @@ "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -972,16 +972,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.73", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", - "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1271,9 +1271,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -1792,19 +1792,22 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2082,17 +2085,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -2116,59 +2119,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs index 881a984554..3739603a2d 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs @@ -746,6 +746,12 @@ public class ProviderBillingServiceTests { provider.Name = "MSP"; + sutProvider.GetDependency<ITaxService>() + .GetStripeTaxCode(Arg.Is<string>( + p => p == taxInfo.BillingAddressCountry), + Arg.Is<string>(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + taxInfo.BillingAddressCountry = "AD"; var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>(); @@ -777,6 +783,29 @@ public class ProviderBillingServiceTests Assert.Equivalent(expected, actual); } + [Theory, BitAutoData] + public async Task SetupCustomer_Throws_BadRequestException_WhenTaxIdIsInvalid( + SutProvider<ProviderBillingService> sutProvider, + Provider provider, + TaxInfo taxInfo) + { + provider.Name = "MSP"; + + taxInfo.BillingAddressCountry = "AD"; + + sutProvider.GetDependency<ITaxService>() + .GetStripeTaxCode(Arg.Is<string>( + p => p == taxInfo.BillingAddressCountry), + Arg.Is<string>(p => p == taxInfo.TaxIdNumber)) + .Returns((string)null); + + var actual = await Assert.ThrowsAsync<BadRequestException>(async () => + await sutProvider.Sut.SetupCustomer(provider, taxInfo)); + + Assert.IsType<BadRequestException>(actual); + Assert.Equal("billingTaxIdTypeInferenceError", actual.Message); + } + #endregion #region SetupSubscription diff --git a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs index 4c4df3d15b..86aebfaad7 100644 --- a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs @@ -3,7 +3,6 @@ using Bit.Admin.AdminConsole.Models; using Bit.Admin.Enums; using Bit.Admin.Services; using Bit.Admin.Utilities; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Providers.Interfaces; @@ -476,14 +475,6 @@ public class OrganizationsController : Controller Organization organization, OrganizationEditModel update) { - var scaleMSPOnClientOrganizationUpdate = - _featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate); - - if (!scaleMSPOnClientOrganizationUpdate) - { - return; - } - var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id); // No scaling required diff --git a/src/Admin/AdminConsole/Models/OrganizationsModel.cs b/src/Admin/AdminConsole/Models/OrganizationsModel.cs index 147c5275f8..a98985ef01 100644 --- a/src/Admin/AdminConsole/Models/OrganizationsModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationsModel.cs @@ -10,4 +10,6 @@ public class OrganizationsModel : PagedModel<Organization> public bool? Paid { get; set; } public string Action { get; set; } public bool SelfHosted { get; set; } + + public double StorageGB(Organization org) => org.Storage.HasValue ? Math.Round(org.Storage.Value / 1073741824D, 2) : 0; } diff --git a/src/Admin/AdminConsole/Views/Organizations/Index.cshtml b/src/Admin/AdminConsole/Views/Organizations/Index.cshtml index 756cd76f62..d42d0e8aa2 100644 --- a/src/Admin/AdminConsole/Views/Organizations/Index.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/Index.cshtml @@ -81,16 +81,7 @@ <i class="fa fa-smile-o fa-lg fa-fw text-body-secondary" title="Freeloader"></i> } } - @if(org.MaxStorageGb.HasValue && org.MaxStorageGb > 1) - { - <i class="fa fa-plus-square fa-lg fa-fw" - title="Additional Storage, @(org.MaxStorageGb - 1) GB"></i> - } - else - { - <i class="fa fa-plus-square-o fa-lg fa-fw text-body-secondary" - title="No Additional Storage"></i> - } + <i class="fa fa-hdd-o fa-lg fa-fw" title="Used Storage, @Model.StorageGB(org) GB"></i> @if(org.Enabled) { <i class="fa fa-check-circle fa-lg fa-fw" diff --git a/src/Admin/Controllers/ToolsController.cs b/src/Admin/Controllers/ToolsController.cs index ea91d01cb8..45319cf79c 100644 --- a/src/Admin/Controllers/ToolsController.cs +++ b/src/Admin/Controllers/ToolsController.cs @@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Models.BitStripe; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Admin/Views/Shared/_Layout.cshtml b/src/Admin/Views/Shared/_Layout.cshtml index b1f0a24420..939eb86b86 100644 --- a/src/Admin/Views/Shared/_Layout.cshtml +++ b/src/Admin/Views/Shared/_Layout.cshtml @@ -92,7 +92,7 @@ @if (canPromoteAdmin) { <a class="dropdown-item" asp-controller="Tools" asp-action="PromoteAdmin"> - Promote Admin + Promote Organization Admin </a> } @if (canPromoteProviderServiceUser) diff --git a/src/Admin/package-lock.json b/src/Admin/package-lock.json index e792106499..cc2693eae6 100644 --- a/src/Admin/package-lock.json +++ b/src/Admin/package-lock.json @@ -780,9 +780,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -800,9 +800,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -820,9 +820,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001688", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", - "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -841,9 +841,9 @@ "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { @@ -973,16 +973,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.73", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", - "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1272,9 +1272,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -1793,19 +1793,22 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2083,17 +2086,17 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -2117,59 +2120,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/src/Api/AdminConsole/Controllers/GroupsController.cs b/src/Api/AdminConsole/Controllers/GroupsController.cs index 0e46656994..946d7399c2 100644 --- a/src/Api/AdminConsole/Controllers/GroupsController.cs +++ b/src/Api/AdminConsole/Controllers/GroupsController.cs @@ -2,7 +2,6 @@ using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Models.Response; using Bit.Api.Vault.AuthorizationHandlers.Collections; -using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization; @@ -90,7 +89,7 @@ public class GroupsController : Controller } [HttpGet("")] - public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroups(Guid orgId) + public async Task<ListResponseModel<GroupResponseModel>> GetOrganizationGroups(Guid orgId) { var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll); if (!authResult.Succeeded) @@ -98,24 +97,15 @@ public class GroupsController : Controller throw new NotFoundException(); } - if (_featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails)) - { - var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId); - var responses = groups.Select(g => new GroupDetailsResponseModel(g, [])); - return new ListResponseModel<GroupDetailsResponseModel>(responses); - } - - var groupDetails = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId); - var detailResponses = groupDetails.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2)); - return new ListResponseModel<GroupDetailsResponseModel>(detailResponses); + var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId); + var responses = groups.Select(g => new GroupResponseModel(g)); + return new ListResponseModel<GroupResponseModel>(responses); } [HttpGet("details")] public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroupDetails(Guid orgId) { - var authResult = _featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails) - ? await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails) - : await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll); + var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails); if (!authResult.Succeeded) { diff --git a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs index 7227d7a11a..211476dca1 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -43,6 +43,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo UserId = organization.UserId; ProviderId = organization.ProviderId; ProviderName = organization.ProviderName; + ProviderType = organization.ProviderType; ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier; LimitCollectionCreation = organization.LimitCollectionCreation; LimitCollectionDeletion = organization.LimitCollectionDeletion; diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index a6fc6bd177..46c9ffd808 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -1023,11 +1023,28 @@ public class AccountsController : Controller [RequireFeature(FeatureFlagKeys.NewDeviceVerification)] [AllowAnonymous] [HttpPost("resend-new-device-otp")] - public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificatioRequestModel request) + public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificationRequestModel request) { await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret); } + [RequireFeature(FeatureFlagKeys.NewDeviceVerification)] + [HttpPost("verify-devices")] + [HttpPut("verify-devices")] + public async Task SetUserVerifyDevicesAsync([FromBody] SetVerifyDevicesRequestModel request) + { + var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException(); + + if (!await _userService.VerifySecretAsync(user, request.Secret)) + { + await Task.Delay(2000); + throw new BadRequestException(string.Empty, "User verification failed."); + } + user.VerifyDevices = request.VerifyDevices; + + await _userService.SaveUserAsync(user); + } + private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId) { var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId); diff --git a/src/Api/Auth/Models/Request/Accounts/SetVerifyDevicesRequestModel.cs b/src/Api/Auth/Models/Request/Accounts/SetVerifyDevicesRequestModel.cs new file mode 100644 index 0000000000..0dcbe1fa11 --- /dev/null +++ b/src/Api/Auth/Models/Request/Accounts/SetVerifyDevicesRequestModel.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Auth.Models.Request.Accounts; + +public class SetVerifyDevicesRequestModel : SecretVerificationRequestModel +{ + [Required] + public bool VerifyDevices { get; set; } +} diff --git a/src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificatioRequestModel.cs b/src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificationRequestModel.cs similarity index 71% rename from src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificatioRequestModel.cs rename to src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificationRequestModel.cs index 629896b8c4..abd37023c8 100644 --- a/src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificatioRequestModel.cs +++ b/src/Api/Auth/Models/Request/Accounts/UnauthenticatedSecretVerificationRequestModel.cs @@ -3,7 +3,7 @@ using Bit.Core.Utilities; namespace Bit.Api.Auth.Models.Request.Accounts; -public class UnauthenticatedSecretVerificatioRequestModel : SecretVerificationRequestModel +public class UnauthenticatedSecretVerificationRequestModel : SecretVerificationRequestModel { [Required] [StrictEmailAddress] diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 7da0a0f602..1c0cfd9388 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -1,7 +1,9 @@ #nullable enable +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Billing.Models.Requests; using Bit.Api.Billing.Models.Responses; using Bit.Core; +using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Repositories; @@ -21,7 +23,8 @@ public class OrganizationBillingController( IOrganizationRepository organizationRepository, IPaymentService paymentService, ISubscriberService subscriberService, - IPaymentHistoryService paymentHistoryService) : BaseBillingController + IPaymentHistoryService paymentHistoryService, + IUserService userService) : BaseBillingController { [HttpGet("metadata")] public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId) @@ -278,4 +281,37 @@ public class OrganizationBillingController( return TypedResults.Ok(); } + + [HttpPost("restart-subscription")] + public async Task<IResult> RestartSubscriptionAsync([FromRoute] Guid organizationId, + [FromBody] OrganizationCreateRequestModel model) + { + var user = await userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) + { + return Error.NotFound(); + } + + if (!await currentContext.EditPaymentMethods(organizationId)) + { + return Error.Unauthorized(); + } + + var organization = await organizationRepository.GetByIdAsync(organizationId); + + if (organization == null) + { + return Error.NotFound(); + } + var organizationSignup = model.ToOrganizationSignup(user); + var sale = OrganizationSale.From(organization, organizationSignup); + await organizationBillingService.Finalize(sale); + + return TypedResults.Ok(); + } } diff --git a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs index 28f156fa39..1dfc79be21 100644 --- a/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs +++ b/src/Api/Billing/Models/Responses/OrganizationMetadataResponse.cs @@ -9,6 +9,7 @@ public record OrganizationMetadataResponse( bool IsSubscriptionUnpaid, bool HasSubscription, bool HasOpenInvoice, + bool IsSubscriptionCanceled, DateTime? InvoiceDueDate, DateTime? InvoiceCreatedDate, DateTime? SubPeriodEndDate) @@ -21,6 +22,7 @@ public record OrganizationMetadataResponse( metadata.IsSubscriptionUnpaid, metadata.HasSubscription, metadata.HasOpenInvoice, + metadata.IsSubscriptionCanceled, metadata.InvoiceDueDate, metadata.InvoiceCreatedDate, metadata.SubPeriodEndDate); diff --git a/src/Api/Controllers/DevicesController.cs b/src/Api/Controllers/DevicesController.cs index f55b30eb27..aab898cd62 100644 --- a/src/Api/Controllers/DevicesController.cs +++ b/src/Api/Controllers/DevicesController.cs @@ -6,7 +6,6 @@ using Bit.Api.Models.Response; using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Context; -using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; @@ -70,11 +69,17 @@ public class DevicesController : Controller } [HttpGet("")] - public async Task<ListResponseModel<DeviceResponseModel>> Get() + public async Task<ListResponseModel<DeviceAuthRequestResponseModel>> Get() { - ICollection<Device> devices = await _deviceRepository.GetManyByUserIdAsync(_userService.GetProperUserId(User).Value); - var responses = devices.Select(d => new DeviceResponseModel(d)); - return new ListResponseModel<DeviceResponseModel>(responses); + var devicesWithPendingAuthData = await _deviceRepository.GetManyByUserIdWithDeviceAuth(_userService.GetProperUserId(User).Value); + + // Convert from DeviceAuthDetails to DeviceAuthRequestResponseModel + var deviceAuthRequestResponseList = devicesWithPendingAuthData + .Select(DeviceAuthRequestResponseModel.From) + .ToList(); + + var response = new ListResponseModel<DeviceAuthRequestResponseModel>(deviceAuthRequestResponseList); + return response; } [HttpPost("")] diff --git a/src/Api/Controllers/InstallationsController.cs b/src/Api/Platform/Installations/Controllers/InstallationsController.cs similarity index 70% rename from src/Api/Controllers/InstallationsController.cs rename to src/Api/Platform/Installations/Controllers/InstallationsController.cs index a2eeebab37..96cdc9d95c 100644 --- a/src/Api/Controllers/InstallationsController.cs +++ b/src/Api/Platform/Installations/Controllers/InstallationsController.cs @@ -1,13 +1,20 @@ -using Bit.Api.Models.Request; -using Bit.Api.Models.Response; -using Bit.Core.Exceptions; -using Bit.Core.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Platform.Installations; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.Platform.Installations; +/// <summary> +/// Routes used to manipulate `Installation` objects: a type used to manage +/// a record of a self hosted installation. +/// </summary> +/// <remarks> +/// This controller is not called from any clients. It's primarily referenced +/// in the `Setup` project for creating a new self hosted installation. +/// </remarks> +/// <seealso>Bit.Setup.Program</seealso> [Route("installations")] [SelfHosted(NotSelfHostedOnly = true)] public class InstallationsController : Controller diff --git a/src/Api/Models/Request/InstallationRequestModel.cs b/src/Api/Platform/Installations/Models/InstallationRequestModel.cs similarity index 84% rename from src/Api/Models/Request/InstallationRequestModel.cs rename to src/Api/Platform/Installations/Models/InstallationRequestModel.cs index 65b542e62e..242701a66f 100644 --- a/src/Api/Models/Request/InstallationRequestModel.cs +++ b/src/Api/Platform/Installations/Models/InstallationRequestModel.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Bit.Core.Entities; +using Bit.Core.Platform.Installations; using Bit.Core.Utilities; -namespace Bit.Api.Models.Request; +namespace Bit.Api.Platform.Installations; public class InstallationRequestModel { diff --git a/src/Api/Models/Response/InstallationResponseModel.cs b/src/Api/Platform/Installations/Models/InstallationResponseModel.cs similarity index 78% rename from src/Api/Models/Response/InstallationResponseModel.cs rename to src/Api/Platform/Installations/Models/InstallationResponseModel.cs index 2fdc55d847..0be5795275 100644 --- a/src/Api/Models/Response/InstallationResponseModel.cs +++ b/src/Api/Platform/Installations/Models/InstallationResponseModel.cs @@ -1,7 +1,7 @@ -using Bit.Core.Entities; -using Bit.Core.Models.Api; +using Bit.Core.Models.Api; +using Bit.Core.Platform.Installations; -namespace Bit.Api.Models.Response; +namespace Bit.Api.Platform.Installations; public class InstallationResponseModel : ResponseModel { diff --git a/src/Api/Controllers/PushController.cs b/src/Api/Platform/Push/Controllers/PushController.cs similarity index 94% rename from src/Api/Controllers/PushController.cs rename to src/Api/Platform/Push/Controllers/PushController.cs index 3839805106..4b9f1c3e11 100644 --- a/src/Api/Controllers/PushController.cs +++ b/src/Api/Platform/Push/Controllers/PushController.cs @@ -1,14 +1,18 @@ using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Api; -using Bit.Core.Services; +using Bit.Core.Platform.Push; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.Platform.Push; +/// <summary> +/// Routes for push relay: functionality that facilitates communication +/// between self hosted organizations and Bitwarden cloud. +/// </summary> [Route("push")] [Authorize("Push")] [SelfHosted(NotSelfHostedOnly = true)] diff --git a/src/Api/Utilities/ServiceCollectionExtensions.cs b/src/Api/Utilities/ServiceCollectionExtensions.cs index 3d206fd887..270055be8f 100644 --- a/src/Api/Utilities/ServiceCollectionExtensions.cs +++ b/src/Api/Utilities/ServiceCollectionExtensions.cs @@ -34,6 +34,9 @@ public static class ServiceCollectionExtensions Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt") } }); + + config.CustomSchemaIds(type => type.FullName); + config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" }); config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme diff --git a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs index 49578187f9..b16baea52e 100644 --- a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs +++ b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Enums; using Bit.Core.Context; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Enums; diff --git a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs index d49b22b7fb..6b4fef43d1 100644 --- a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs @@ -1,5 +1,6 @@ using Bit.Billing.Constants; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; diff --git a/src/Core/AdminConsole/Enums/Provider/ProviderType.cs b/src/Core/AdminConsole/Enums/Provider/ProviderType.cs index 50c344ec95..e244b9391e 100644 --- a/src/Core/AdminConsole/Enums/Provider/ProviderType.cs +++ b/src/Core/AdminConsole/Enums/Provider/ProviderType.cs @@ -4,10 +4,10 @@ namespace Bit.Core.AdminConsole.Enums.Provider; public enum ProviderType : byte { - [Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Access to clients organization", Order = 0)] + [Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Creates provider portal for client organization management", Order = 0)] Msp = 0, - [Display(ShortName = "Reseller", Name = "Reseller", Description = "Access to clients billing", Order = 1000)] + [Display(ShortName = "Reseller", Name = "Reseller", Description = "Creates Bitwarden Portal page for client organization billing management", Order = 1000)] Reseller = 1, - [Display(ShortName = "MOE", Name = "Multi-organization Enterprise", Description = "Access to multiple organizations", Order = 1)] + [Display(ShortName = "MOE", Name = "Multi-organization Enterprises", Description = "Creates provider portal for multi-organization management", Order = 1)] MultiOrganizationEnterprise = 2, } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs index f37cc644d4..bd5592edfc 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -44,4 +44,5 @@ public class ProviderUserOrganizationDetails public bool LimitCollectionDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool UseRiskInsights { get; set; } + public ProviderType ProviderType { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs b/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs index 407ca61c4d..af966a6e16 100644 --- a/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs +++ b/src/Core/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommand.cs @@ -7,6 +7,7 @@ using Bit.Core.Auth.Models.Api.Request.AuthRequest; using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Services; using Bit.Core.Enums; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteManagedOrganizationUserAccountCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteManagedOrganizationUserAccountCommand.cs index cb7e2a6250..010f5de9bf 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteManagedOrganizationUserAccountCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/DeleteManagedOrganizationUserAccountCommand.cs @@ -4,6 +4,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Enums; diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RemoveOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RemoveOrganizationUserCommand.cs index e45f109df1..9375a231ec 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RemoveOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/RemoveOrganizationUserCommand.cs @@ -3,6 +3,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs index 3eb4d35ef1..df841adf42 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs @@ -11,6 +11,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data; using Bit.Core.Models.StaticStore; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Enums; diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 1cf22b23ad..9d178697ac 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -27,6 +27,7 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Mail; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tokens; diff --git a/src/Core/Auth/Enums/AuthRequestType.cs b/src/Core/Auth/Enums/AuthRequestType.cs index fff75e8d22..0a3bf4b3bc 100644 --- a/src/Core/Auth/Enums/AuthRequestType.cs +++ b/src/Core/Auth/Enums/AuthRequestType.cs @@ -1,5 +1,12 @@ namespace Bit.Core.Auth.Enums; +/** + * The type of auth request. + * + * Note: + * Used by the Device_ReadActiveWithPendingAuthRequestsByUserId.sql stored procedure. + * If the enum changes be aware of this reference. + */ public enum AuthRequestType : byte { AuthenticateAndUnlock = 0, diff --git a/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs b/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs new file mode 100644 index 0000000000..3cfea51ee3 --- /dev/null +++ b/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs @@ -0,0 +1,51 @@ +using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Utilities; +using Bit.Core.Enums; +using Bit.Core.Models.Api; + +namespace Bit.Core.Auth.Models.Api.Response; + +public class DeviceAuthRequestResponseModel : ResponseModel +{ + public DeviceAuthRequestResponseModel() + : base("device") { } + + public static DeviceAuthRequestResponseModel From(DeviceAuthDetails deviceAuthDetails) + { + var converted = new DeviceAuthRequestResponseModel + { + Id = deviceAuthDetails.Id, + Name = deviceAuthDetails.Name, + Type = deviceAuthDetails.Type, + Identifier = deviceAuthDetails.Identifier, + CreationDate = deviceAuthDetails.CreationDate, + IsTrusted = deviceAuthDetails.IsTrusted() + }; + + if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreatedAt != null) + { + converted.DevicePendingAuthRequest = new PendingAuthRequest + { + Id = (Guid)deviceAuthDetails.AuthRequestId, + CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreatedAt + }; + } + + return converted; + } + + public Guid Id { get; set; } + public string Name { get; set; } + public DeviceType Type { get; set; } + public string Identifier { get; set; } + public DateTime CreationDate { get; set; } + public bool IsTrusted { get; set; } + + public PendingAuthRequest DevicePendingAuthRequest { get; set; } + + public class PendingAuthRequest + { + public Guid Id { get; set; } + public DateTime CreationDate { get; set; } + } +} diff --git a/src/Core/Auth/Models/Data/DeviceAuthDetails.cs b/src/Core/Auth/Models/Data/DeviceAuthDetails.cs new file mode 100644 index 0000000000..ef242705f4 --- /dev/null +++ b/src/Core/Auth/Models/Data/DeviceAuthDetails.cs @@ -0,0 +1,81 @@ +using Bit.Core.Auth.Utilities; +using Bit.Core.Entities; +using Bit.Core.Enums; + +namespace Bit.Core.Auth.Models.Data; + +public class DeviceAuthDetails : Device +{ + public bool IsTrusted { get; set; } + public Guid? AuthRequestId { get; set; } + public DateTime? AuthRequestCreatedAt { get; set; } + + /** + * Constructor for EF response. + */ + public DeviceAuthDetails( + Device device, + Guid? authRequestId, + DateTime? authRequestCreationDate) + { + if (device == null) + { + throw new ArgumentNullException(nameof(device)); + } + + Id = device.Id; + Name = device.Name; + Type = device.Type; + Identifier = device.Identifier; + CreationDate = device.CreationDate; + IsTrusted = device.IsTrusted(); + AuthRequestId = authRequestId; + AuthRequestCreatedAt = authRequestCreationDate; + } + + /** + * Constructor for dapper response. + * Note: if the authRequestId or authRequestCreationDate is null it comes back as + * an empty guid and a min value for datetime. That could change if the stored + * procedure runs on a different kind of db. + */ + public DeviceAuthDetails( + Guid id, + Guid userId, + string name, + short type, + string identifier, + string pushToken, + DateTime creationDate, + DateTime revisionDate, + string encryptedUserKey, + string encryptedPublicKey, + string encryptedPrivateKey, + bool active, + Guid authRequestId, + DateTime authRequestCreationDate) + { + Id = id; + Name = name; + Type = (DeviceType)type; + Identifier = identifier; + CreationDate = creationDate; + IsTrusted = new Device + { + Id = id, + UserId = userId, + Name = name, + Type = (DeviceType)type, + Identifier = identifier, + PushToken = pushToken, + RevisionDate = revisionDate, + EncryptedUserKey = encryptedUserKey, + EncryptedPublicKey = encryptedPublicKey, + EncryptedPrivateKey = encryptedPrivateKey, + Active = active + }.IsTrusted(); + AuthRequestId = authRequestId != Guid.Empty ? authRequestId : null; + AuthRequestCreatedAt = + authRequestCreationDate != DateTime.MinValue ? authRequestCreationDate : null; + } +} diff --git a/src/Core/Auth/Models/Data/EmergencyAccessDetails.cs b/src/Core/Auth/Models/Data/EmergencyAccessDetails.cs index 3c925d1a80..15ccad9cb1 100644 --- a/src/Core/Auth/Models/Data/EmergencyAccessDetails.cs +++ b/src/Core/Auth/Models/Data/EmergencyAccessDetails.cs @@ -1,5 +1,4 @@ - -using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Entities; namespace Bit.Core.Auth.Models.Data; diff --git a/src/Core/Auth/Services/Implementations/AuthRequestService.cs b/src/Core/Auth/Services/Implementations/AuthRequestService.cs index a27112425b..f83c5de1f6 100644 --- a/src/Core/Auth/Services/Implementations/AuthRequestService.cs +++ b/src/Core/Auth/Services/Implementations/AuthRequestService.cs @@ -7,6 +7,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs index 89851fce23..834d2722cc 100644 --- a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs +++ b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs @@ -23,7 +23,6 @@ namespace Bit.Core.Auth.UserFeatures.Registration.Implementations; public class RegisterUserCommand : IRegisterUserCommand { - private readonly IGlobalSettings _globalSettings; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IPolicyRepository _policyRepository; diff --git a/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs index d33db18e44..8ef586ab51 100644 --- a/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs +++ b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Identity; diff --git a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs index e9a5d3f736..9a7a4107ae 100644 --- a/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs +++ b/src/Core/Billing/Extensions/ServiceCollectionExtensions.cs @@ -17,6 +17,7 @@ public static class ServiceCollectionExtensions services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>(); services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>(); services.AddTransient<ISubscriberService, SubscriberService>(); + // services.AddSingleton<IPricingClient, PricingClient>(); services.AddLicenseServices(); } } diff --git a/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs index 28c779c3d6..3b7b275469 100644 --- a/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs +++ b/src/Core/Billing/Licenses/Services/Implementations/UserLicenseClaimsFactory.cs @@ -12,26 +12,42 @@ public class UserLicenseClaimsFactory : ILicenseClaimsFactory<User> { var subscriptionInfo = licenseContext.SubscriptionInfo; - var expires = subscriptionInfo.UpcomingInvoice?.Date?.AddDays(7) ?? entity.PremiumExpirationDate?.AddDays(7); - var refresh = subscriptionInfo.UpcomingInvoice?.Date ?? entity.PremiumExpirationDate; - var trial = (subscriptionInfo.Subscription?.TrialEndDate.HasValue ?? false) && + var expires = subscriptionInfo?.UpcomingInvoice?.Date?.AddDays(7) ?? entity.PremiumExpirationDate?.AddDays(7); + var refresh = subscriptionInfo?.UpcomingInvoice?.Date ?? entity.PremiumExpirationDate; + var trial = (subscriptionInfo?.Subscription?.TrialEndDate.HasValue ?? false) && subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow; var claims = new List<Claim> { new(nameof(UserLicenseConstants.LicenseType), LicenseType.User.ToString()), - new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey), new(nameof(UserLicenseConstants.Id), entity.Id.ToString()), new(nameof(UserLicenseConstants.Name), entity.Name), new(nameof(UserLicenseConstants.Email), entity.Email), new(nameof(UserLicenseConstants.Premium), entity.Premium.ToString()), - new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString()), new(nameof(UserLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)), - new(nameof(UserLicenseConstants.Expires), expires.ToString()), - new(nameof(UserLicenseConstants.Refresh), refresh.ToString()), new(nameof(UserLicenseConstants.Trial), trial.ToString()), }; + if (entity.LicenseKey is not null) + { + claims.Add(new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey)); + } + + if (entity.MaxStorageGb is not null) + { + claims.Add(new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString())); + } + + if (expires is not null) + { + claims.Add(new(nameof(UserLicenseConstants.Expires), expires.ToString())); + } + + if (refresh is not null) + { + claims.Add(new(nameof(UserLicenseConstants.Refresh), refresh.ToString())); + } + return Task.FromResult(claims); } } diff --git a/src/Core/Billing/Models/OrganizationMetadata.cs b/src/Core/Billing/Models/OrganizationMetadata.cs index b6442e4c19..4bb9a85825 100644 --- a/src/Core/Billing/Models/OrganizationMetadata.cs +++ b/src/Core/Billing/Models/OrganizationMetadata.cs @@ -7,6 +7,7 @@ public record OrganizationMetadata( bool IsSubscriptionUnpaid, bool HasSubscription, bool HasOpenInvoice, + bool IsSubscriptionCanceled, DateTime? InvoiceDueDate, DateTime? InvoiceCreatedDate, DateTime? SubPeriodEndDate); diff --git a/src/Core/Billing/Models/StaticStore/Plan.cs b/src/Core/Billing/Models/StaticStore/Plan.cs index 15a618cca0..5dbcd7ddc4 100644 --- a/src/Core/Billing/Models/StaticStore/Plan.cs +++ b/src/Core/Billing/Models/StaticStore/Plan.cs @@ -8,8 +8,11 @@ public abstract record Plan public ProductTierType ProductTier { get; protected init; } public string Name { get; protected init; } public bool IsAnnual { get; protected init; } + // TODO: Move to the client public string NameLocalizationKey { get; protected init; } + // TODO: Move to the client public string DescriptionLocalizationKey { get; protected init; } + // TODO: Remove public bool CanBeUsedByBusiness { get; protected init; } public int? TrialPeriodDays { get; protected init; } public bool HasSelfHost { get; protected init; } @@ -27,7 +30,9 @@ public abstract record Plan public bool UsersGetPremium { get; protected init; } public bool HasCustomPermissions { get; protected init; } public int UpgradeSortOrder { get; protected init; } + // TODO: Move to the client public int DisplaySortOrder { get; protected init; } + // TODO: Remove public int? LegacyYear { get; protected init; } public bool Disabled { get; protected init; } public PasswordManagerPlanFeatures PasswordManager { get; protected init; } @@ -45,15 +50,19 @@ public abstract record Plan public string StripeServiceAccountPlanId { get; init; } public decimal? AdditionalPricePerServiceAccount { get; init; } public short BaseServiceAccount { get; init; } + // TODO: Unused, remove public short? MaxAdditionalServiceAccount { get; init; } public bool HasAdditionalServiceAccountOption { get; init; } // Seats public string StripeSeatPlanId { get; init; } public bool HasAdditionalSeatsOption { get; init; } + // TODO: Remove, SM is never packaged public decimal BasePrice { get; init; } public decimal SeatPrice { get; init; } + // TODO: Remove, SM is never packaged public int BaseSeats { get; init; } public short? MaxSeats { get; init; } + // TODO: Unused, remove public int? MaxAdditionalSeats { get; init; } public bool AllowSeatAutoscale { get; init; } @@ -72,8 +81,10 @@ public abstract record Plan public decimal ProviderPortalSeatPrice { get; init; } public bool AllowSeatAutoscale { get; init; } public bool HasAdditionalSeatsOption { get; init; } + // TODO: Remove, never set. public int? MaxAdditionalSeats { get; init; } public int BaseSeats { get; init; } + // TODO: Remove premium access as it's deprecated public bool HasPremiumAccessOption { get; init; } public string StripePremiumAccessPlanId { get; init; } public decimal PremiumAccessOptionPrice { get; init; } @@ -83,6 +94,7 @@ public abstract record Plan public bool HasAdditionalStorageOption { get; init; } public decimal AdditionalStoragePricePerGb { get; init; } public string StripeStoragePlanId { get; init; } + // TODO: Remove public short? MaxAdditionalStorage { get; init; } // Feature public short? MaxCollections { get; init; } diff --git a/src/Core/Billing/Pricing/IPricingClient.cs b/src/Core/Billing/Pricing/IPricingClient.cs new file mode 100644 index 0000000000..68577f1db3 --- /dev/null +++ b/src/Core/Billing/Pricing/IPricingClient.cs @@ -0,0 +1,12 @@ +using Bit.Core.Billing.Enums; +using Bit.Core.Models.StaticStore; + +#nullable enable + +namespace Bit.Core.Billing.Pricing; + +public interface IPricingClient +{ + Task<Plan?> GetPlan(PlanType planType); + Task<List<Plan>> ListPlans(); +} diff --git a/src/Core/Billing/Pricing/PlanAdapter.cs b/src/Core/Billing/Pricing/PlanAdapter.cs new file mode 100644 index 0000000000..b2b24d4cf9 --- /dev/null +++ b/src/Core/Billing/Pricing/PlanAdapter.cs @@ -0,0 +1,232 @@ +using Bit.Core.Billing.Enums; +using Bit.Core.Models.StaticStore; +using Proto.Billing.Pricing; + +#nullable enable + +namespace Bit.Core.Billing.Pricing; + +public record PlanAdapter : Plan +{ + public PlanAdapter(PlanResponse planResponse) + { + Type = ToPlanType(planResponse.LookupKey); + ProductTier = ToProductTierType(Type); + Name = planResponse.Name; + IsAnnual = !string.IsNullOrEmpty(planResponse.Cadence) && planResponse.Cadence == "annually"; + NameLocalizationKey = planResponse.AdditionalData?["nameLocalizationKey"]; + DescriptionLocalizationKey = planResponse.AdditionalData?["descriptionLocalizationKey"]; + TrialPeriodDays = planResponse.TrialPeriodDays; + HasSelfHost = HasFeature("selfHost"); + HasPolicies = HasFeature("policies"); + HasGroups = HasFeature("groups"); + HasDirectory = HasFeature("directory"); + HasEvents = HasFeature("events"); + HasTotp = HasFeature("totp"); + Has2fa = HasFeature("2fa"); + HasApi = HasFeature("api"); + HasSso = HasFeature("sso"); + HasKeyConnector = HasFeature("keyConnector"); + HasScim = HasFeature("scim"); + HasResetPassword = HasFeature("resetPassword"); + UsersGetPremium = HasFeature("usersGetPremium"); + UpgradeSortOrder = planResponse.AdditionalData != null + ? int.Parse(planResponse.AdditionalData["upgradeSortOrder"]) + : 0; + DisplaySortOrder = planResponse.AdditionalData != null + ? int.Parse(planResponse.AdditionalData["displaySortOrder"]) + : 0; + HasCustomPermissions = HasFeature("customPermissions"); + Disabled = !planResponse.Available; + PasswordManager = ToPasswordManagerPlanFeatures(planResponse); + SecretsManager = planResponse.SecretsManager != null ? ToSecretsManagerPlanFeatures(planResponse) : null; + + return; + + bool HasFeature(string lookupKey) => planResponse.Features.Any(feature => feature.LookupKey == lookupKey); + } + + #region Mappings + + private static PlanType ToPlanType(string lookupKey) + => lookupKey switch + { + "enterprise-annually" => PlanType.EnterpriseAnnually, + "enterprise-annually-2019" => PlanType.EnterpriseAnnually2019, + "enterprise-annually-2020" => PlanType.EnterpriseAnnually2020, + "enterprise-annually-2023" => PlanType.EnterpriseAnnually2023, + "enterprise-monthly" => PlanType.EnterpriseMonthly, + "enterprise-monthly-2019" => PlanType.EnterpriseMonthly2019, + "enterprise-monthly-2020" => PlanType.EnterpriseMonthly2020, + "enterprise-monthly-2023" => PlanType.EnterpriseMonthly2023, + "families" => PlanType.FamiliesAnnually, + "families-2019" => PlanType.FamiliesAnnually2019, + "free" => PlanType.Free, + "teams-annually" => PlanType.TeamsAnnually, + "teams-annually-2019" => PlanType.TeamsAnnually2019, + "teams-annually-2020" => PlanType.TeamsAnnually2020, + "teams-annually-2023" => PlanType.TeamsAnnually2023, + "teams-monthly" => PlanType.TeamsMonthly, + "teams-monthly-2019" => PlanType.TeamsMonthly2019, + "teams-monthly-2020" => PlanType.TeamsMonthly2020, + "teams-monthly-2023" => PlanType.TeamsMonthly2023, + "teams-starter" => PlanType.TeamsStarter, + "teams-starter-2023" => PlanType.TeamsStarter2023, + _ => throw new BillingException() // TODO: Flesh out + }; + + private static ProductTierType ToProductTierType(PlanType planType) + => planType switch + { + PlanType.Free => ProductTierType.Free, + PlanType.FamiliesAnnually or PlanType.FamiliesAnnually2019 => ProductTierType.Families, + PlanType.TeamsStarter or PlanType.TeamsStarter2023 => ProductTierType.TeamsStarter, + _ when planType.ToString().Contains("Teams") => ProductTierType.Teams, + _ when planType.ToString().Contains("Enterprise") => ProductTierType.Enterprise, + _ => throw new BillingException() // TODO: Flesh out + }; + + private static PasswordManagerPlanFeatures ToPasswordManagerPlanFeatures(PlanResponse planResponse) + { + var stripePlanId = GetStripePlanId(planResponse.Seats); + var stripeSeatPlanId = GetStripeSeatPlanId(planResponse.Seats); + var stripeProviderPortalSeatPlanId = planResponse.ManagedSeats?.StripePriceId; + var basePrice = GetBasePrice(planResponse.Seats); + var seatPrice = GetSeatPrice(planResponse.Seats); + var providerPortalSeatPrice = + planResponse.ManagedSeats != null ? decimal.Parse(planResponse.ManagedSeats.Price) : 0; + var scales = planResponse.Seats.KindCase switch + { + PurchasableDTO.KindOneofCase.Scalable => true, + PurchasableDTO.KindOneofCase.Packaged => planResponse.Seats.Packaged.Additional != null, + _ => false + }; + var baseSeats = GetBaseSeats(planResponse.Seats); + var maxSeats = GetMaxSeats(planResponse.Seats); + var baseStorageGb = (short?)planResponse.Storage?.Provided; + var hasAdditionalStorageOption = planResponse.Storage != null; + var stripeStoragePlanId = planResponse.Storage?.StripePriceId; + short? maxCollections = + planResponse.AdditionalData != null && + planResponse.AdditionalData.TryGetValue("passwordManager.maxCollections", out var value) ? short.Parse(value) : null; + + return new PasswordManagerPlanFeatures + { + StripePlanId = stripePlanId, + StripeSeatPlanId = stripeSeatPlanId, + StripeProviderPortalSeatPlanId = stripeProviderPortalSeatPlanId, + BasePrice = basePrice, + SeatPrice = seatPrice, + ProviderPortalSeatPrice = providerPortalSeatPrice, + AllowSeatAutoscale = scales, + HasAdditionalSeatsOption = scales, + BaseSeats = baseSeats, + MaxSeats = maxSeats, + BaseStorageGb = baseStorageGb, + HasAdditionalStorageOption = hasAdditionalStorageOption, + StripeStoragePlanId = stripeStoragePlanId, + MaxCollections = maxCollections + }; + } + + private static SecretsManagerPlanFeatures ToSecretsManagerPlanFeatures(PlanResponse planResponse) + { + var seats = planResponse.SecretsManager.Seats; + var serviceAccounts = planResponse.SecretsManager.ServiceAccounts; + + var maxServiceAccounts = GetMaxServiceAccounts(serviceAccounts); + var allowServiceAccountsAutoscale = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable; + var stripeServiceAccountPlanId = GetStripeServiceAccountPlanId(serviceAccounts); + var additionalPricePerServiceAccount = GetAdditionalPricePerServiceAccount(serviceAccounts); + var baseServiceAccount = GetBaseServiceAccount(serviceAccounts); + var hasAdditionalServiceAccountOption = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable; + var stripeSeatPlanId = GetStripeSeatPlanId(seats); + var hasAdditionalSeatsOption = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable; + var seatPrice = GetSeatPrice(seats); + var maxSeats = GetMaxSeats(seats); + var allowSeatAutoscale = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable; + var maxProjects = + planResponse.AdditionalData != null && + planResponse.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0; + + return new SecretsManagerPlanFeatures + { + MaxServiceAccounts = maxServiceAccounts, + AllowServiceAccountsAutoscale = allowServiceAccountsAutoscale, + StripeServiceAccountPlanId = stripeServiceAccountPlanId, + AdditionalPricePerServiceAccount = additionalPricePerServiceAccount, + BaseServiceAccount = baseServiceAccount, + HasAdditionalServiceAccountOption = hasAdditionalServiceAccountOption, + StripeSeatPlanId = stripeSeatPlanId, + HasAdditionalSeatsOption = hasAdditionalSeatsOption, + SeatPrice = seatPrice, + MaxSeats = maxSeats, + AllowSeatAutoscale = allowSeatAutoscale, + MaxProjects = maxProjects + }; + } + + private static decimal? GetAdditionalPricePerServiceAccount(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable + ? null + : decimal.Parse(freeOrScalable.Scalable.Price); + + private static decimal GetBasePrice(PurchasableDTO purchasable) + => purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : decimal.Parse(purchasable.Packaged.Price); + + private static int GetBaseSeats(PurchasableDTO purchasable) + => purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : purchasable.Packaged.Quantity; + + private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase switch + { + FreeOrScalableDTO.KindOneofCase.Free => (short)freeOrScalable.Free.Quantity, + FreeOrScalableDTO.KindOneofCase.Scalable => (short)freeOrScalable.Scalable.Provided, + _ => 0 + }; + + private static short? GetMaxSeats(PurchasableDTO purchasable) + => purchasable.KindCase != PurchasableDTO.KindOneofCase.Free ? null : (short)purchasable.Free.Quantity; + + private static short? GetMaxSeats(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity; + + private static short? GetMaxServiceAccounts(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity; + + private static decimal GetSeatPrice(PurchasableDTO purchasable) + => purchasable.KindCase switch + { + PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional != null ? decimal.Parse(purchasable.Packaged.Additional.Price) : 0, + PurchasableDTO.KindOneofCase.Scalable => decimal.Parse(purchasable.Scalable.Price), + _ => 0 + }; + + private static decimal GetSeatPrice(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable + ? 0 + : decimal.Parse(freeOrScalable.Scalable.Price); + + private static string? GetStripePlanId(PurchasableDTO purchasable) + => purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? null : purchasable.Packaged.StripePriceId; + + private static string? GetStripeSeatPlanId(PurchasableDTO purchasable) + => purchasable.KindCase switch + { + PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional?.StripePriceId, + PurchasableDTO.KindOneofCase.Scalable => purchasable.Scalable.StripePriceId, + _ => null + }; + + private static string? GetStripeSeatPlanId(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable + ? null + : freeOrScalable.Scalable.StripePriceId; + + private static string? GetStripeServiceAccountPlanId(FreeOrScalableDTO freeOrScalable) + => freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable + ? null + : freeOrScalable.Scalable.StripePriceId; + + #endregion +} diff --git a/src/Core/Billing/Pricing/PricingClient.cs b/src/Core/Billing/Pricing/PricingClient.cs new file mode 100644 index 0000000000..65fc1761ad --- /dev/null +++ b/src/Core/Billing/Pricing/PricingClient.cs @@ -0,0 +1,92 @@ +using Bit.Core.Billing.Enums; +using Bit.Core.Models.StaticStore; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Grpc.Net.Client; +using Proto.Billing.Pricing; + +#nullable enable + +namespace Bit.Core.Billing.Pricing; + +public class PricingClient( + IFeatureService featureService, + GlobalSettings globalSettings) : IPricingClient +{ + public async Task<Plan?> GetPlan(PlanType planType) + { + var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService); + + if (!usePricingService) + { + return StaticStore.GetPlan(planType); + } + + using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri); + var client = new PasswordManager.PasswordManagerClient(channel); + + var lookupKey = ToLookupKey(planType); + if (string.IsNullOrEmpty(lookupKey)) + { + return null; + } + + try + { + var response = + await client.GetPlanByLookupKeyAsync(new GetPlanByLookupKeyRequest { LookupKey = lookupKey }); + + return new PlanAdapter(response); + } + catch (RpcException rpcException) when (rpcException.StatusCode == StatusCode.NotFound) + { + return null; + } + } + + public async Task<List<Plan>> ListPlans() + { + var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService); + + if (!usePricingService) + { + return StaticStore.Plans.ToList(); + } + + using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri); + var client = new PasswordManager.PasswordManagerClient(channel); + + var response = await client.ListPlansAsync(new Empty()); + return response.Plans.Select(Plan (plan) => new PlanAdapter(plan)).ToList(); + } + + private static string? ToLookupKey(PlanType planType) + => planType switch + { + PlanType.EnterpriseAnnually => "enterprise-annually", + PlanType.EnterpriseAnnually2019 => "enterprise-annually-2019", + PlanType.EnterpriseAnnually2020 => "enterprise-annually-2020", + PlanType.EnterpriseAnnually2023 => "enterprise-annually-2023", + PlanType.EnterpriseMonthly => "enterprise-monthly", + PlanType.EnterpriseMonthly2019 => "enterprise-monthly-2019", + PlanType.EnterpriseMonthly2020 => "enterprise-monthly-2020", + PlanType.EnterpriseMonthly2023 => "enterprise-monthly-2023", + PlanType.FamiliesAnnually => "families", + PlanType.FamiliesAnnually2019 => "families-2019", + PlanType.Free => "free", + PlanType.TeamsAnnually => "teams-annually", + PlanType.TeamsAnnually2019 => "teams-annually-2019", + PlanType.TeamsAnnually2020 => "teams-annually-2020", + PlanType.TeamsAnnually2023 => "teams-annually-2023", + PlanType.TeamsMonthly => "teams-monthly", + PlanType.TeamsMonthly2019 => "teams-monthly-2019", + PlanType.TeamsMonthly2020 => "teams-monthly-2020", + PlanType.TeamsMonthly2023 => "teams-monthly-2023", + PlanType.TeamsStarter => "teams-starter", + PlanType.TeamsStarter2023 => "teams-starter-2023", + _ => null + }; +} diff --git a/src/Core/Billing/Pricing/Protos/password-manager.proto b/src/Core/Billing/Pricing/Protos/password-manager.proto new file mode 100644 index 0000000000..69a4c51bd1 --- /dev/null +++ b/src/Core/Billing/Pricing/Protos/password-manager.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +option csharp_namespace = "Proto.Billing.Pricing"; + +package plans; + +import "google/protobuf/empty.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +service PasswordManager { + rpc GetPlanByLookupKey (GetPlanByLookupKeyRequest) returns (PlanResponse); + rpc ListPlans (google.protobuf.Empty) returns (ListPlansResponse); +} + +// Requests +message GetPlanByLookupKeyRequest { + string lookupKey = 1; +} + +// Responses +message PlanResponse { + string name = 1; + string lookupKey = 2; + string tier = 4; + optional string cadence = 6; + optional google.protobuf.Int32Value legacyYear = 8; + bool available = 9; + repeated FeatureDTO features = 10; + PurchasableDTO seats = 11; + optional ScalableDTO managedSeats = 12; + optional ScalableDTO storage = 13; + optional SecretsManagerPurchasablesDTO secretsManager = 14; + optional google.protobuf.Int32Value trialPeriodDays = 15; + repeated string canUpgradeTo = 16; + map<string, string> additionalData = 17; +} + +message ListPlansResponse { + repeated PlanResponse plans = 1; +} + +// DTOs +message FeatureDTO { + string name = 1; + string lookupKey = 2; +} + +message FreeDTO { + int32 quantity = 2; + string type = 4; +} + +message PackagedDTO { + message AdditionalSeats { + string stripePriceId = 1; + string price = 2; + } + + int32 quantity = 2; + string stripePriceId = 3; + string price = 4; + optional AdditionalSeats additional = 5; + string type = 6; +} + +message ScalableDTO { + int32 provided = 2; + string stripePriceId = 6; + string price = 7; + string type = 9; +} + +message PurchasableDTO { + oneof kind { + FreeDTO free = 1; + PackagedDTO packaged = 2; + ScalableDTO scalable = 3; + } +} + +message FreeOrScalableDTO { + oneof kind { + FreeDTO free = 1; + ScalableDTO scalable = 2; + } +} + +message SecretsManagerPurchasablesDTO { + FreeOrScalableDTO seats = 1; + FreeOrScalableDTO serviceAccounts = 2; +} diff --git a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs index 8114d5ba65..ec9770c59e 100644 --- a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs +++ b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs @@ -69,7 +69,7 @@ public class OrganizationBillingService( if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { return new OrganizationMetadata(isEligibleForSelfHost, isManaged, false, - false, false, false, null, null, null); + false, false, false, false, null, null, null); } var customer = await subscriberService.GetCustomer(organization, @@ -79,6 +79,7 @@ public class OrganizationBillingService( var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription); var isSubscriptionUnpaid = IsSubscriptionUnpaid(subscription); + var isSubscriptionCanceled = IsSubscriptionCanceled(subscription); var hasSubscription = true; var openInvoice = await HasOpenInvoiceAsync(subscription); var hasOpenInvoice = openInvoice.HasOpenInvoice; @@ -87,7 +88,7 @@ public class OrganizationBillingService( var subPeriodEndDate = subscription?.CurrentPeriodEnd; return new OrganizationMetadata(isEligibleForSelfHost, isManaged, isOnSecretsManagerStandalone, - isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate); + isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, isSubscriptionCanceled, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate); } public async Task UpdatePaymentMethod( @@ -437,5 +438,15 @@ public class OrganizationBillingService( ? (true, invoice.Created, invoice.DueDate) : (false, null, null); } + + private static bool IsSubscriptionCanceled(Subscription subscription) + { + if (subscription == null) + { + return false; + } + + return subscription.Status == "canceled"; + } #endregion } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index e0c5564ede..defcc41e93 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -133,6 +133,7 @@ public static class FeatureFlagKeys public const string NativeCreateAccountFlow = "native-create-account-flow"; public const string AccountDeprovisioning = "pm-10308-account-deprovisioning"; public const string NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements"; + public const string BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain"; public const string AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api"; public const string PersistPopupView = "persist-popup-view"; public const string CipherKeyEncryption = "cipher-key-encryption"; @@ -140,7 +141,6 @@ public static class FeatureFlagKeys public const string StorageReseedRefactor = "storage-reseed-refactor"; public const string TrialPayment = "PM-8163-trial-payment"; public const string RemoveServerVersionHeader = "remove-server-version-header"; - public const string SecureOrgGroupDetails = "pm-3479-secure-org-group-details"; public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint"; public const string PM12275_MultiOrganizationEnterprises = "pm-12275-multi-organization-enterprises"; public const string GeneratorToolsModernization = "generator-tools-modernization"; @@ -150,7 +150,6 @@ public static class FeatureFlagKeys public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss"; public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss"; public const string SecurityTasks = "security-tasks"; - public const string PM14401_ScaleMSPOnClientOrganizationUpdate = "PM-14401-scale-msp-on-client-organization-update"; public const string PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission"; public const string DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship"; public const string MacOsNativeCredentialSync = "macos-native-credential-sync"; @@ -164,6 +163,8 @@ public static class FeatureFlagKeys public const string AuthenticatorSyncAndroid = "enable-authenticator-sync-android"; public const string AppReviewPrompt = "app-review-prompt"; public const string ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs"; + public const string UsePricingService = "use-pricing-service"; + public const string RecordInstallationLastActivityDate = "installation-last-activity-date"; public static List<string> GetAllKeys() { diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index c5cb31d9c5..44b4729a10 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -21,10 +21,16 @@ <ItemGroup> <PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" /> - <PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.402.7" /> - <PackageReference Include="AWSSDK.SQS" Version="3.7.400.64" /> + <PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.402.18" /> + <PackageReference Include="AWSSDK.SQS" Version="3.7.400.75" /> <PackageReference Include="Azure.Data.Tables" Version="12.9.0" /> <PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" /> + <PackageReference Include="Google.Protobuf" Version="3.29.2" /> + <PackageReference Include="Grpc.Net.Client" Version="2.67.0" /> + <PackageReference Include="Grpc.Tools" Version="2.68.1"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> <PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" /> <PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" /> <PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" /> @@ -41,9 +47,10 @@ <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" /> <PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.7.0" /> <PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.10" /> - <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" /> - <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" /> + <PackageReference Include="OneOf" Version="3.0.271" /> <PackageReference Include="Quartz" Version="3.13.1" /> <PackageReference Include="SendGrid" Version="9.29.3" /> <PackageReference Include="Serilog.AspNetCore" Version="8.0.3" /> @@ -62,6 +69,10 @@ <PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" /> </ItemGroup> + <ItemGroup> + <Protobuf Include="Billing\Pricing\Protos\password-manager.proto" GrpcServices="Client" /> + </ItemGroup> + <ItemGroup> <Folder Include="Resources\" /> <Folder Include="Properties\" /> diff --git a/src/Core/Entities/User.cs b/src/Core/Entities/User.cs index 0e538b9014..9878c96c1c 100644 --- a/src/Core/Entities/User.cs +++ b/src/Core/Entities/User.cs @@ -72,6 +72,7 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac public DateTime? LastKdfChangeDate { get; set; } public DateTime? LastKeyRotationDate { get; set; } public DateTime? LastEmailChangeDate { get; set; } + public bool VerifyDevices { get; set; } = true; public void SetNewId() { diff --git a/src/Core/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommand.cs b/src/Core/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommand.cs index a54223f685..9b93d44182 100644 --- a/src/Core/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommand.cs +++ b/src/Core/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommand.cs @@ -8,7 +8,7 @@ using Bit.Core.Exceptions; using Bit.Core.KeyManagement.Commands.Interfaces; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.KeyManagement.Repositories; -using Bit.Core.Services; +using Bit.Core.Platform.Push; using Microsoft.Extensions.Logging; namespace Bit.Core.KeyManagement.Commands; diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs index 68b2c60293..8cece5f762 100644 --- a/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs +++ b/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs @@ -1,6 +1,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Entities; using Bit.Core.KeyManagement.Models.Data; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Repositories; diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs index 7438e812e0..67faff619d 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs @@ -6,8 +6,8 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; diff --git a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs b/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs index 123152c01c..180b2b641b 100644 --- a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs @@ -1,7 +1,7 @@ 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.Settings; using Microsoft.Azure.NotificationHubs; using Microsoft.Extensions.Logging; diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs index d7782fcd98..53050c7824 100644 --- a/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs +++ b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; -using Bit.Core.Repositories; +using Bit.Core.Platform.Installations; using Bit.Core.Services; namespace Bit.Core.OrganizationFeatures.OrganizationLicenses; diff --git a/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/IUpdateInstallationCommand.cs b/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/IUpdateInstallationCommand.cs new file mode 100644 index 0000000000..d0c25b96a4 --- /dev/null +++ b/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/IUpdateInstallationCommand.cs @@ -0,0 +1,14 @@ +namespace Bit.Core.Platform.Installations; + +/// <summary> +/// Command interface responsible for updating data on an `Installation` +/// record. +/// </summary> +/// <remarks> +/// This interface is implemented by `UpdateInstallationCommand` +/// </remarks> +/// <seealso cref="Bit.Core.Platform.Installations.UpdateInstallationCommand"/> +public interface IUpdateInstallationCommand +{ + Task UpdateLastActivityDateAsync(Guid installationId); +} diff --git a/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/UpdateInstallationCommand.cs b/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/UpdateInstallationCommand.cs new file mode 100644 index 0000000000..4b0bc3bbe8 --- /dev/null +++ b/src/Core/Platform/Installations/Commands/UpdateInstallationActivityDateCommand/UpdateInstallationCommand.cs @@ -0,0 +1,53 @@ +namespace Bit.Core.Platform.Installations; + +/// <summary> +/// Commands responsible for updating an installation from +/// `InstallationRepository`. +/// </summary> +/// <remarks> +/// If referencing: you probably want the interface +/// `IUpdateInstallationCommand` instead of directly calling this class. +/// </remarks> +/// <seealso cref="IUpdateInstallationCommand"/> +public class UpdateInstallationCommand : IUpdateInstallationCommand +{ + private readonly IGetInstallationQuery _getInstallationQuery; + private readonly IInstallationRepository _installationRepository; + private readonly TimeProvider _timeProvider; + + public UpdateInstallationCommand( + IGetInstallationQuery getInstallationQuery, + IInstallationRepository installationRepository, + TimeProvider timeProvider + ) + { + _getInstallationQuery = getInstallationQuery; + _installationRepository = installationRepository; + _timeProvider = timeProvider; + } + + public async Task UpdateLastActivityDateAsync(Guid installationId) + { + if (installationId == default) + { + throw new Exception + ( + "Tried to update the last activity date for " + + "an installation, but an invalid installation id was " + + "provided." + ); + } + var installation = await _getInstallationQuery.GetByIdAsync(installationId); + if (installation == null) + { + throw new Exception + ( + "Tried to update the last activity date for " + + $"installation {installationId.ToString()}, but no " + + "installation was found for that id." + ); + } + installation.LastActivityDate = _timeProvider.GetUtcNow().UtcDateTime; + await _installationRepository.UpsertAsync(installation); + } +} diff --git a/src/Core/Entities/Installation.cs b/src/Core/Platform/Installations/Entities/Installation.cs similarity index 63% rename from src/Core/Entities/Installation.cs rename to src/Core/Platform/Installations/Entities/Installation.cs index ff30236d3d..acd53db0fb 100644 --- a/src/Core/Entities/Installation.cs +++ b/src/Core/Platform/Installations/Entities/Installation.cs @@ -1,10 +1,15 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Entities; using Bit.Core.Utilities; #nullable enable -namespace Bit.Core.Entities; +namespace Bit.Core.Platform.Installations; +/// <summary> +/// The base entity for the SQL table `dbo.Installation`. Used to store +/// information pertinent to self hosted Bitwarden installations. +/// </summary> public class Installation : ITableObject<Guid> { public Guid Id { get; set; } @@ -14,6 +19,7 @@ public class Installation : ITableObject<Guid> public string Key { get; set; } = null!; public bool Enabled { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; + public DateTime? LastActivityDate { get; internal set; } public void SetNewId() { diff --git a/src/Core/Platform/Installations/Queries/GetInstallationQuery/GetInstallationQuery.cs b/src/Core/Platform/Installations/Queries/GetInstallationQuery/GetInstallationQuery.cs new file mode 100644 index 0000000000..b0d8745800 --- /dev/null +++ b/src/Core/Platform/Installations/Queries/GetInstallationQuery/GetInstallationQuery.cs @@ -0,0 +1,30 @@ +namespace Bit.Core.Platform.Installations; + +/// <summary> +/// Queries responsible for fetching an installation from +/// `InstallationRepository`. +/// </summary> +/// <remarks> +/// If referencing: you probably want the interface `IGetInstallationQuery` +/// instead of directly calling this class. +/// </remarks> +/// <seealso cref="IGetInstallationQuery"/> +public class GetInstallationQuery : IGetInstallationQuery +{ + private readonly IInstallationRepository _installationRepository; + + public GetInstallationQuery(IInstallationRepository installationRepository) + { + _installationRepository = installationRepository; + } + + /// <inheritdoc cref="IGetInstallationQuery.GetByIdAsync"/> + public async Task<Installation> GetByIdAsync(Guid installationId) + { + if (installationId == default(Guid)) + { + return null; + } + return await _installationRepository.GetByIdAsync(installationId); + } +} diff --git a/src/Core/Platform/Installations/Queries/GetInstallationQuery/IGetInstallationQuery.cs b/src/Core/Platform/Installations/Queries/GetInstallationQuery/IGetInstallationQuery.cs new file mode 100644 index 0000000000..9615cf986d --- /dev/null +++ b/src/Core/Platform/Installations/Queries/GetInstallationQuery/IGetInstallationQuery.cs @@ -0,0 +1,20 @@ +namespace Bit.Core.Platform.Installations; + +/// <summary> +/// Query interface responsible for fetching an installation from +/// `InstallationRepository`. +/// </summary> +/// <remarks> +/// This interface is implemented by `GetInstallationQuery` +/// </remarks> +/// <seealso cref="GetInstallationQuery"/> +public interface IGetInstallationQuery +{ + /// <summary> + /// Retrieves an installation from the `InstallationRepository` by its id. + /// </summary> + /// <param name="installationId">The GUID id of the installation.</param> + /// <returns>A task containing an `Installation`.</returns> + /// <seealso cref="T:Bit.Core.Platform.Installations.Repositories.IInstallationRepository"/> + Task<Installation> GetByIdAsync(Guid installationId); +} diff --git a/src/Core/Platform/Installations/Repositories/IInstallationRepository.cs b/src/Core/Platform/Installations/Repositories/IInstallationRepository.cs new file mode 100644 index 0000000000..5303eb04e6 --- /dev/null +++ b/src/Core/Platform/Installations/Repositories/IInstallationRepository.cs @@ -0,0 +1,19 @@ +using Bit.Core.Repositories; + +#nullable enable + +namespace Bit.Core.Platform.Installations; + +/// <summary> +/// The CRUD repository interface for communicating with `dbo.Installation`, +/// which is used to store information pertinent to self-hosted +/// installations. +/// </summary> +/// <remarks> +/// This interface is implemented by `InstallationRepository` in the Dapper +/// and Entity Framework projects. +/// </remarks> +/// <seealso cref="T:Bit.Infrastructure.Dapper.Platform.Installations.Repositories.InstallationRepository"/> +public interface IInstallationRepository : IRepository<Installation, Guid> +{ +} diff --git a/src/Core/Platform/PlatformServiceCollectionExtensions.cs b/src/Core/Platform/PlatformServiceCollectionExtensions.cs new file mode 100644 index 0000000000..bba0b0aedd --- /dev/null +++ b/src/Core/Platform/PlatformServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using Bit.Core.Platform.Installations; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Platform; + +public static class PlatformServiceCollectionExtensions +{ + /// <summary> + /// Extend DI to include commands and queries exported from the Platform + /// domain. + /// </summary> + public static IServiceCollection AddPlatformServices(this IServiceCollection services) + { + services.AddScoped<IGetInstallationQuery, GetInstallationQuery>(); + services.AddScoped<IUpdateInstallationCommand, UpdateInstallationCommand>(); + + return services; + } +} diff --git a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs similarity index 99% rename from src/Core/Services/Implementations/AzureQueuePushNotificationService.cs rename to src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs index 3daadebf3a..332b322be6 100644 --- a/src/Core/Services/Implementations/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs @@ -11,7 +11,7 @@ using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class AzureQueuePushNotificationService : IPushNotificationService { diff --git a/src/Core/Services/IPushNotificationService.cs b/src/Core/Platform/Push/Services/IPushNotificationService.cs similarity index 97% rename from src/Core/Services/IPushNotificationService.cs rename to src/Core/Platform/Push/Services/IPushNotificationService.cs index 6e2e47e27f..986b54b6d9 100644 --- a/src/Core/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/IPushNotificationService.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push; public interface IPushNotificationService { diff --git a/src/Core/Services/IPushRegistrationService.cs b/src/Core/Platform/Push/Services/IPushRegistrationService.cs similarity index 93% rename from src/Core/Services/IPushRegistrationService.cs rename to src/Core/Platform/Push/Services/IPushRegistrationService.cs index 985246de0c..482e7ae1c4 100644 --- a/src/Core/Services/IPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/IPushRegistrationService.cs @@ -1,6 +1,6 @@ using Bit.Core.Enums; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push; public interface IPushRegistrationService { diff --git a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs similarity index 99% rename from src/Core/Services/Implementations/MultiServicePushNotificationService.cs rename to src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs index 185a11adbb..a291aa037f 100644 --- a/src/Core/Services/Implementations/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs @@ -7,7 +7,7 @@ using Bit.Core.Vault.Entities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class MultiServicePushNotificationService : IPushNotificationService { diff --git a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs similarity index 98% rename from src/Core/Services/NoopImplementations/NoopPushNotificationService.cs rename to src/Core/Platform/Push/Services/NoopPushNotificationService.cs index b5e2616220..6d5fbfd9a4 100644 --- a/src/Core/Services/NoopImplementations/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class NoopPushNotificationService : IPushNotificationService { diff --git a/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs b/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs similarity index 94% rename from src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs rename to src/Core/Platform/Push/Services/NoopPushRegistrationService.cs index f6279c9467..6d1716a6ce 100644 --- a/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs @@ -1,6 +1,6 @@ using Bit.Core.Enums; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class NoopPushRegistrationService : IPushRegistrationService { diff --git a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs similarity index 97% rename from src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs rename to src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index feec75fbe0..adf6d829e7 100644 --- a/src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -3,13 +3,15 @@ using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +// This service is not in the `Internal` namespace because it has direct external references. +namespace Bit.Core.Platform.Push; public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService { diff --git a/src/Core/Services/Implementations/RelayPushNotificationService.cs b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs similarity index 99% rename from src/Core/Services/Implementations/RelayPushNotificationService.cs rename to src/Core/Platform/Push/Services/RelayPushNotificationService.cs index d725296779..93db0c0c5b 100644 --- a/src/Core/Services/Implementations/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs @@ -6,13 +6,14 @@ using Bit.Core.IdentityServer; using Bit.Core.Models; using Bit.Core.Models.Api; using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService { diff --git a/src/Core/Services/Implementations/RelayPushRegistrationService.cs b/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs similarity index 96% rename from src/Core/Services/Implementations/RelayPushRegistrationService.cs rename to src/Core/Platform/Push/Services/RelayPushRegistrationService.cs index d0f7736e98..79b033e877 100644 --- a/src/Core/Services/Implementations/RelayPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs @@ -1,14 +1,14 @@ using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models.Api; +using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.Extensions.Logging; -namespace Bit.Core.Services; +namespace Bit.Core.Platform.Push.Internal; public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService { - public RelayPushRegistrationService( IHttpClientFactory httpFactory, GlobalSettings globalSettings, diff --git a/src/Core/Repositories/IDeviceRepository.cs b/src/Core/Repositories/IDeviceRepository.cs index c5d14a0945..c9809c1de6 100644 --- a/src/Core/Repositories/IDeviceRepository.cs +++ b/src/Core/Repositories/IDeviceRepository.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Entities; #nullable enable @@ -10,5 +11,9 @@ public interface IDeviceRepository : IRepository<Device, Guid> Task<Device?> GetByIdentifierAsync(string identifier); Task<Device?> GetByIdentifierAsync(string identifier, Guid userId); Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId); + // DeviceAuthDetails is passed back to decouple the response model from the + // repository in case more fields are ever added to the details response for + // other requests. + Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId); Task ClearPushTokenAsync(Guid id); } diff --git a/src/Core/Repositories/IInstallationRepository.cs b/src/Core/Repositories/IInstallationRepository.cs deleted file mode 100644 index f9c7d85edf..0000000000 --- a/src/Core/Repositories/IInstallationRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Bit.Core.Entities; - -#nullable enable - -namespace Bit.Core.Repositories; - -public interface IInstallationRepository : IRepository<Installation, Guid> -{ -} diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index 638e4c5e07..afbc574417 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -2,6 +2,7 @@ using Bit.Core.Auth.Utilities; using Bit.Core.Entities; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; namespace Bit.Core.Services; diff --git a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs index 48d8fa1222..69b8a94e5a 100644 --- a/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs +++ b/src/Core/Services/Implementations/LaunchDarklyFeatureService.cs @@ -16,6 +16,7 @@ public class LaunchDarklyFeatureService : IFeatureService private readonly ICurrentContext _currentContext; private const string _anonymousUser = "25a15cac-58cf-4ac0-ad0f-b17c4bd92294"; + private const string _contextKindDevice = "device"; private const string _contextKindOrganization = "organization"; private const string _contextKindServiceAccount = "service-account"; @@ -158,6 +159,16 @@ public class LaunchDarklyFeatureService : IFeatureService var builder = LaunchDarkly.Sdk.Context.MultiBuilder(); + if (!string.IsNullOrWhiteSpace(_currentContext.DeviceIdentifier)) + { + var ldDevice = LaunchDarkly.Sdk.Context.Builder(_currentContext.DeviceIdentifier); + + ldDevice.Kind(_contextKindDevice); + SetCommonContextAttributes(ldDevice); + + builder.Add(ldDevice.Build()); + } + switch (_currentContext.IdentityClientType) { case IdentityClientType.User: diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 2ad134053f..7e5077dee5 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -18,6 +18,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Settings; using Bit.Core.Tokens; @@ -1143,7 +1144,10 @@ public class UserService : UserManager<User>, IUserService, IDisposable ? new UserLicense(user, _licenseService) : new UserLicense(user, subscriptionInfo, _licenseService); - userLicense.Token = await _licenseService.CreateUserTokenAsync(user, subscriptionInfo); + if (_featureService.IsEnabled(FeatureFlagKeys.SelfHostLicenseRefactor)) + { + userLicense.Token = await _licenseService.CreateUserTokenAsync(user, subscriptionInfo); + } return userLicense; } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index cdbfc7cf3a..420151a34f 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -81,8 +81,8 @@ public class GlobalSettings : IGlobalSettings public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings(); public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings(); public virtual string DevelopmentDirectory { get; set; } - public virtual bool EnableEmailVerification { get; set; } + public virtual string PricingUri { get; set; } public string BuildExternalUri(string explicitValue, string name) { diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 02d151ed95..afe35ed34b 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -24,5 +24,7 @@ public interface IGlobalSettings IPasswordlessAuthSettings PasswordlessAuth { get; set; } IDomainVerificationSettings DomainVerification { get; set; } ILaunchDarklySettings LaunchDarkly { get; set; } + string DatabaseProvider { get; set; } + GlobalSettings.SqlSettings SqlServer { get; set; } string DevelopmentDirectory { get; set; } } diff --git a/src/Core/Tools/Services/Implementations/SendService.cs b/src/Core/Tools/Services/Implementations/SendService.cs index fad941362b..918379d7a5 100644 --- a/src/Core/Tools/Services/Implementations/SendService.cs +++ b/src/Core/Tools/Services/Implementations/SendService.cs @@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index d6947b5412..d6806bd115 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -5,6 +5,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/src/Identity/IdentityServer/ClientStore.cs b/src/Identity/IdentityServer/ClientStore.cs index 3f1c1c2fd4..c204e364ce 100644 --- a/src/Identity/IdentityServer/ClientStore.cs +++ b/src/Identity/IdentityServer/ClientStore.cs @@ -5,6 +5,7 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Identity; using Bit.Core.IdentityServer; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Models.Data; using Bit.Core.SecretsManager.Repositories; diff --git a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs index fb7b129b09..597d5257e2 100644 --- a/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs @@ -1,11 +1,13 @@ using System.Diagnostics; using System.Security.Claims; +using Bit.Core; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Auth.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.IdentityServer; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; @@ -23,6 +25,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque ICustomTokenRequestValidator { private readonly UserManager<User> _userManager; + private readonly IUpdateInstallationCommand _updateInstallationCommand; public CustomTokenRequestValidator( UserManager<User> userManager, @@ -39,7 +42,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque IPolicyService policyService, IFeatureService featureService, ISsoConfigRepository ssoConfigRepository, - IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder + IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, + IUpdateInstallationCommand updateInstallationCommand ) : base( userManager, @@ -59,6 +63,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque userDecryptionOptionsBuilder) { _userManager = userManager; + _updateInstallationCommand = updateInstallationCommand; } public async Task ValidateAsync(CustomTokenRequestValidationContext context) @@ -76,16 +81,24 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque } string[] allowedGrantTypes = ["authorization_code", "client_credentials"]; + string clientId = context.Result.ValidatedRequest.ClientId; if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType) - || context.Result.ValidatedRequest.ClientId.StartsWith("organization") - || context.Result.ValidatedRequest.ClientId.StartsWith("installation") - || context.Result.ValidatedRequest.ClientId.StartsWith("internal") + || clientId.StartsWith("organization") + || clientId.StartsWith("installation") + || clientId.StartsWith("internal") || context.Result.ValidatedRequest.Client.AllowedScopes.Contains(ApiScopes.ApiSecrets)) { if (context.Result.ValidatedRequest.Client.Properties.TryGetValue("encryptedPayload", out var payload) && !string.IsNullOrWhiteSpace(payload)) { context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } }; + + } + if (FeatureService.IsEnabled(FeatureFlagKeys.RecordInstallationLastActivityDate) + && context.Result.ValidatedRequest.ClientId.StartsWith("installation")) + { + var installationIdPart = clientId.Split(".")[1]; + await RecordActivityForInstallation(clientId.Split(".")[1]); } return; } @@ -152,6 +165,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque context.Result.CustomResponse["KeyConnectorUrl"] = userDecryptionOptions.KeyConnectorOption.KeyConnectorUrl; context.Result.CustomResponse["ResetMasterPassword"] = false; } + return Task.CompletedTask; } @@ -202,4 +216,25 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque context.Result.ErrorDescription = requestContext.ValidationErrorResult.ErrorDescription; context.Result.CustomResponse = requestContext.CustomResponse; } + + /// <summary> + /// To help mentally separate organizations that self host from abandoned + /// organizations we hook in to the token refresh event for installations + /// to write a simple `DateTime.Now` to the database. + /// </summary> + /// <remarks> + /// This works well because installations don't phone home very often. + /// Currently self hosted installations only refresh tokens every 24 + /// hours or so for the sake of hooking in to cloud's push relay service. + /// If installations ever start refreshing tokens more frequently we may need to + /// adjust this to avoid making a bunch of unnecessary database calls! + /// </remarks> + private async Task RecordActivityForInstallation(string? installationIdString) + { + if (!Guid.TryParse(installationIdString, out var installationId)) + { + return; + } + await _updateInstallationCommand.UpdateLastActivityDateAsync(installationId); + } } diff --git a/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs b/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs index d59417bfa7..1b148c5974 100644 --- a/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs @@ -115,7 +115,7 @@ public class DeviceValidator( /// </summary> /// <param name="user">user attempting to authenticate</param> /// <param name="ValidatedRequest">The Request is used to check for the NewDeviceOtp and for the raw device data</param> - /// <returns>returns deviceValtaionResultType</returns> + /// <returns>returns deviceValidationResultType</returns> private async Task<DeviceValidationResultType> HandleNewDeviceVerificationAsync(User user, ValidatedRequest request) { // currently unreachable due to backward compatibility @@ -125,6 +125,12 @@ public class DeviceValidator( return DeviceValidationResultType.InvalidUser; } + // Has the User opted out of new device verification + if (!user.VerifyDevices) + { + return DeviceValidationResultType.Success; + } + // CS exception flow // Check cache for user information var cacheKey = string.Format(AuthConstants.NewDeviceVerificationExceptionCacheKeyFormat, user.Id.ToString()); @@ -146,6 +152,12 @@ public class DeviceValidator( var otpValid = await _userService.VerifyOTPAsync(user, newDeviceOtp); if (otpValid) { + // In order to get here they would have to have access to their email so we verify it if it's not already + if (!user.EmailVerified) + { + user.EmailVerified = true; + await _userService.SaveUserAsync(user); + } return DeviceValidationResultType.Success; } return DeviceValidationResultType.InvalidNewDeviceOtp; diff --git a/src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs b/src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs index 499c22ad89..085ed15efd 100644 --- a/src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs @@ -44,8 +44,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector, IFeatureService featureService, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, - IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand - ) + IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand) : base( userManager, userService, diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 834f681d28..93814a6d7f 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Repositories; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Tools.Repositories; @@ -12,6 +13,7 @@ using Bit.Infrastructure.Dapper.Auth.Repositories; using Bit.Infrastructure.Dapper.Billing.Repositories; using Bit.Infrastructure.Dapper.KeyManagement.Repositories; using Bit.Infrastructure.Dapper.NotificationCenter.Repositories; +using Bit.Infrastructure.Dapper.Platform; using Bit.Infrastructure.Dapper.Repositories; using Bit.Infrastructure.Dapper.SecretsManager.Repositories; using Bit.Infrastructure.Dapper.Tools.Repositories; diff --git a/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs b/src/Infrastructure.Dapper/Platform/Installations/Repositories/InstallationRepository.cs similarity index 53% rename from src/Infrastructure.Dapper/Repositories/InstallationRepository.cs rename to src/Infrastructure.Dapper/Platform/Installations/Repositories/InstallationRepository.cs index ae10932699..41ca18950a 100644 --- a/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs +++ b/src/Infrastructure.Dapper/Platform/Installations/Repositories/InstallationRepository.cs @@ -1,11 +1,19 @@ -using Bit.Core.Entities; -using Bit.Core.Repositories; +using Bit.Core.Platform.Installations; using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; #nullable enable -namespace Bit.Infrastructure.Dapper.Repositories; +namespace Bit.Infrastructure.Dapper.Platform; +/// <summary> +/// The CRUD repository for communicating with `dbo.Installation`. +/// </summary> +/// <remarks> +/// If referencing: you probably want the interface `IInstallationRepository` +/// instead of directly calling this class. +/// </remarks> +/// <seealso cref="IInstallationRepository"/> public class InstallationRepository : Repository<Installation, Guid>, IInstallationRepository { public InstallationRepository(GlobalSettings globalSettings) diff --git a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs index 7216d87f57..4abf4a4649 100644 --- a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs @@ -1,4 +1,5 @@ using System.Data; +using Bit.Core.Auth.Models.Data; using Bit.Core.Entities; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -11,9 +12,13 @@ namespace Bit.Infrastructure.Dapper.Repositories; public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository { + private readonly IGlobalSettings _globalSettings; + public DeviceRepository(GlobalSettings globalSettings) : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) - { } + { + _globalSettings = globalSettings; + } public DeviceRepository(string connectionString, string readOnlyConnectionString) : base(connectionString, readOnlyConnectionString) @@ -76,6 +81,24 @@ public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository } } + public async Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId) + { + var expirationMinutes = _globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes; + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync<DeviceAuthDetails>( + $"[{Schema}].[{Table}_ReadActiveWithPendingAuthRequestsByUserId]", + new + { + UserId = userId, + ExpirationMinutes = expirationMinutes + }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task ClearPushTokenAsync(Guid id) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/Repository.cs b/src/Infrastructure.Dapper/Repositories/Repository.cs index fd37b611d0..43bffb3598 100644 --- a/src/Infrastructure.Dapper/Repositories/Repository.cs +++ b/src/Infrastructure.Dapper/Repositories/Repository.cs @@ -51,7 +51,7 @@ public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId> var parameters = new DynamicParameters(); parameters.AddDynamicParams(obj); parameters.Add("Id", obj.Id, direction: ParameterDirection.InputOutput); - var results = await connection.ExecuteAsync( + await connection.ExecuteAsync( $"[{Schema}].[{Table}_Create]", parameters, commandType: CommandType.StoredProcedure); @@ -64,7 +64,7 @@ public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId> { using (var connection = new SqlConnection(ConnectionString)) { - var results = await connection.ExecuteAsync( + await connection.ExecuteAsync( $"[{Schema}].[{Table}_Update]", obj, commandType: CommandType.StoredProcedure); diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs index 7d9974d117..3f3d3d389e 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs @@ -48,6 +48,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan LimitCollectionDeletion = x.o.LimitCollectionDeletion, AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems, UseRiskInsights = x.o.UseRiskInsights, + ProviderType = x.p.Type }); } } diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs new file mode 100644 index 0000000000..5ab6d498e3 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs @@ -0,0 +1,38 @@ +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Data; +using Bit.Infrastructure.EntityFramework.Repositories; + +namespace Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; + +public class DeviceWithPendingAuthByUserIdQuery +{ + public IQueryable<DeviceAuthDetails> GetQuery( + DatabaseContext dbContext, + Guid userId, + int expirationMinutes) + { + var devicesWithAuthQuery = ( + from device in dbContext.Devices + where device.UserId == userId && device.Active + select new + { + device, + authRequest = + ( + from authRequest in dbContext.AuthRequests + where authRequest.RequestDeviceIdentifier == device.Identifier + where authRequest.Type == AuthRequestType.AuthenticateAndUnlock || authRequest.Type == AuthRequestType.Unlock + where authRequest.Approved == null + where authRequest.UserId == userId + where authRequest.CreationDate.AddMinutes(expirationMinutes) > DateTime.UtcNow + orderby authRequest.CreationDate descending + select authRequest + ).First() + }).Select(deviceWithAuthRequest => new DeviceAuthDetails( + deviceWithAuthRequest.device, + deviceWithAuthRequest.authRequest.Id, + deviceWithAuthRequest.authRequest.CreationDate)); + + return devicesWithAuthQuery; + } +} diff --git a/src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs b/src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs index 2f00768206..c59a2accba 100644 --- a/src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs +++ b/src/Infrastructure.EntityFramework/Billing/Models/OrganizationInstallation.cs @@ -1,6 +1,6 @@ using AutoMapper; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; -using Bit.Infrastructure.EntityFramework.Models; +using Bit.Infrastructure.EntityFramework.Platform; namespace Bit.Infrastructure.EntityFramework.Billing.Models; diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index b2eefe4523..f3b96c201b 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Bit.Core.Billing.Repositories; using Bit.Core.Enums; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Tools.Repositories; @@ -13,6 +14,7 @@ using Bit.Infrastructure.EntityFramework.Auth.Repositories; using Bit.Infrastructure.EntityFramework.Billing.Repositories; using Bit.Infrastructure.EntityFramework.KeyManagement.Repositories; using Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories; +using Bit.Infrastructure.EntityFramework.Platform; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.SecretsManager.Repositories; using Bit.Infrastructure.EntityFramework.Tools.Repositories; diff --git a/src/Infrastructure.EntityFramework/Models/Installation.cs b/src/Infrastructure.EntityFramework/Platform/Installations/Models/Installation.cs similarity index 70% rename from src/Infrastructure.EntityFramework/Models/Installation.cs rename to src/Infrastructure.EntityFramework/Platform/Installations/Models/Installation.cs index c38680a23c..96b60a39ed 100644 --- a/src/Infrastructure.EntityFramework/Models/Installation.cs +++ b/src/Infrastructure.EntityFramework/Platform/Installations/Models/Installation.cs @@ -1,8 +1,9 @@ using AutoMapper; +using C = Bit.Core.Platform.Installations; -namespace Bit.Infrastructure.EntityFramework.Models; +namespace Bit.Infrastructure.EntityFramework.Platform; -public class Installation : Core.Entities.Installation +public class Installation : C.Installation { // Shadow property - to be introduced by https://bitwarden.atlassian.net/browse/PM-11129 // This isn't a value or entity used by self hosted servers, but it's @@ -14,10 +15,10 @@ public class InstallationMapperProfile : Profile { public InstallationMapperProfile() { - CreateMap<Core.Entities.Installation, Installation>() + CreateMap<C.Installation, Installation>() // Shadow property - to be introduced by https://bitwarden.atlassian.net/browse/PM-11129 .ForMember(i => i.LastActivityDate, opt => opt.Ignore()) .ReverseMap(); - CreateMap<Core.Entities.Installation, Installation>().ReverseMap(); + CreateMap<C.Installation, Installation>().ReverseMap(); } } diff --git a/src/Infrastructure.EntityFramework/Platform/Installations/Repositories/InstallationRepository.cs b/src/Infrastructure.EntityFramework/Platform/Installations/Repositories/InstallationRepository.cs new file mode 100644 index 0000000000..255cc76cf2 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Platform/Installations/Repositories/InstallationRepository.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.Extensions.DependencyInjection; +using C = Bit.Core.Platform.Installations; +using Ef = Bit.Infrastructure.EntityFramework.Platform; + +#nullable enable + +namespace Bit.Infrastructure.EntityFramework.Platform; + +public class InstallationRepository : Repository<C.Installation, Ef.Installation, Guid>, C.IInstallationRepository +{ + public InstallationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Installations) + { } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 24ef2ab269..dd1b97b4f2 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -6,6 +6,7 @@ using Bit.Infrastructure.EntityFramework.Billing.Models; using Bit.Infrastructure.EntityFramework.Converters; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.NotificationCenter.Models; +using Bit.Infrastructure.EntityFramework.Platform; using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Bit.Infrastructure.EntityFramework.Tools.Models; using Bit.Infrastructure.EntityFramework.Vault.Models; diff --git a/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs b/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs index da82427cbb..ad31d0fb8b 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs @@ -1,5 +1,8 @@ using AutoMapper; +using Bit.Core.Auth.Models.Data; using Bit.Core.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -10,9 +13,17 @@ namespace Bit.Infrastructure.EntityFramework.Repositories; public class DeviceRepository : Repository<Core.Entities.Device, Device, Guid>, IDeviceRepository { - public DeviceRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + private readonly IGlobalSettings _globalSettings; + + public DeviceRepository( + IServiceScopeFactory serviceScopeFactory, + IMapper mapper, + IGlobalSettings globalSettings + ) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Devices) - { } + { + _globalSettings = globalSettings; + } public async Task ClearPushTokenAsync(Guid id) { @@ -69,4 +80,15 @@ public class DeviceRepository : Repository<Core.Entities.Device, Device, Guid>, return Mapper.Map<List<Core.Entities.Device>>(devices); } } + + public async Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId) + { + var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes; + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new DeviceWithPendingAuthByUserIdQuery(); + return await query.GetQuery(dbContext, userId, expirationMinutes).ToListAsync(); + } + } } diff --git a/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs deleted file mode 100644 index 64777a384b..0000000000 --- a/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.Models; -using Microsoft.Extensions.DependencyInjection; - -#nullable enable - -namespace Bit.Infrastructure.EntityFramework.Repositories; - -public class InstallationRepository : Repository<Core.Entities.Installation, Installation, Guid>, IInstallationRepository -{ - public InstallationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) - : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Installations) - { } -} diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index 3e2d6e44a4..e339c13351 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -147,14 +147,13 @@ public class OrganizationDomainRepository : Repository<Core.Entities.Organizatio using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); - //Get domains that have not been verified after 72 hours - var domains = await dbContext.OrganizationDomains - .Where(x => (DateTime.UtcNow - x.CreationDate).Days == 4 - && x.VerifiedDate == null) + var threeDaysOldUnverifiedDomains = await dbContext.OrganizationDomains + .Where(x => x.CreationDate.Date == DateTime.UtcNow.AddDays(-4).Date + && x.VerifiedDate == null) .AsNoTracking() .ToListAsync(); - return Mapper.Map<List<Core.Entities.OrganizationDomain>>(domains); + return Mapper.Map<List<Core.Entities.OrganizationDomain>>(threeDaysOldUnverifiedDomains); } public async Task<bool> DeleteExpiredAsync(int expirationPeriod) diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 85bd0301c3..891b8d6664 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -30,6 +30,9 @@ using Bit.Core.KeyManagement; using Bit.Core.NotificationCenter; using Bit.Core.NotificationHub; using Bit.Core.OrganizationFeatures; +using Bit.Core.Platform; +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Resources; using Bit.Core.SecretsManager.Repositories; @@ -124,6 +127,7 @@ public static class ServiceCollectionExtensions services.AddReportingServices(); services.AddKeyManagementServices(); services.AddNotificationCenterServices(); + services.AddPlatformServices(); } public static void AddTokenizers(this IServiceCollection services) diff --git a/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql b/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql new file mode 100644 index 0000000000..015d0f7c1f --- /dev/null +++ b/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql @@ -0,0 +1,27 @@ +CREATE PROCEDURE [dbo].[Device_ReadActiveWithPendingAuthRequestsByUserId] + @UserId UNIQUEIDENTIFIER, + @ExpirationMinutes INT +AS +BEGIN + SET NOCOUNT ON; + + SELECT + D.*, + AR.Id as AuthRequestId, + AR.CreationDate as AuthRequestCreationDate + FROM dbo.DeviceView D + LEFT JOIN ( + SELECT TOP 1 -- Take only the top record sorted by auth request creation date + Id, + CreationDate, + RequestDeviceIdentifier + FROM dbo.AuthRequestView + WHERE Type IN (0, 1) -- Include only AuthenticateAndUnlock and Unlock types, excluding Admin Approval (type 2) + AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired + AND Approved IS NULL -- Include only requests that haven't been acknowledged or approved + ORDER BY CreationDate DESC + ) AR ON D.Identifier = AR.RequestDeviceIdentifier + WHERE + D.UserId = @UserId + AND D.Active = 1; -- Include only active devices +END; diff --git a/src/Sql/dbo/Stored Procedures/Installation_Create.sql b/src/Sql/Platform/dbo/Stored Procedures/Installation_Create.sql similarity index 100% rename from src/Sql/dbo/Stored Procedures/Installation_Create.sql rename to src/Sql/Platform/dbo/Stored Procedures/Installation_Create.sql diff --git a/src/Sql/dbo/Stored Procedures/Installation_DeleteById.sql b/src/Sql/Platform/dbo/Stored Procedures/Installation_DeleteById.sql similarity index 100% rename from src/Sql/dbo/Stored Procedures/Installation_DeleteById.sql rename to src/Sql/Platform/dbo/Stored Procedures/Installation_DeleteById.sql diff --git a/src/Sql/dbo/Stored Procedures/Installation_ReadById.sql b/src/Sql/Platform/dbo/Stored Procedures/Installation_ReadById.sql similarity index 100% rename from src/Sql/dbo/Stored Procedures/Installation_ReadById.sql rename to src/Sql/Platform/dbo/Stored Procedures/Installation_ReadById.sql diff --git a/src/Sql/dbo/Stored Procedures/Installation_Update.sql b/src/Sql/Platform/dbo/Stored Procedures/Installation_Update.sql similarity index 100% rename from src/Sql/dbo/Stored Procedures/Installation_Update.sql rename to src/Sql/Platform/dbo/Stored Procedures/Installation_Update.sql diff --git a/src/Sql/dbo/Tables/Installation.sql b/src/Sql/Platform/dbo/Tables/Installation.sql similarity index 100% rename from src/Sql/dbo/Tables/Installation.sql rename to src/Sql/Platform/dbo/Tables/Installation.sql diff --git a/src/Sql/dbo/Views/InstallationView.sql b/src/Sql/Platform/dbo/Views/InstallationView.sql similarity index 100% rename from src/Sql/dbo/Views/InstallationView.sql rename to src/Sql/Platform/dbo/Views/InstallationView.sql diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 3aabab8c23..60d9b5eb32 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -40,7 +40,8 @@ @LastPasswordChangeDate DATETIME2(7) = NULL, @LastKdfChangeDate DATETIME2(7) = NULL, @LastKeyRotationDate DATETIME2(7) = NULL, - @LastEmailChangeDate DATETIME2(7) = NULL + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 AS BEGIN SET NOCOUNT ON @@ -88,7 +89,8 @@ BEGIN [LastPasswordChangeDate], [LastKdfChangeDate], [LastKeyRotationDate], - [LastEmailChangeDate] + [LastEmailChangeDate], + [VerifyDevices] ) VALUES ( @@ -133,6 +135,7 @@ BEGIN @LastPasswordChangeDate, @LastKdfChangeDate, @LastKeyRotationDate, - @LastEmailChangeDate + @LastEmailChangeDate, + @VerifyDevices ) END diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index 5725f243ff..15d04d72f6 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -40,7 +40,8 @@ @LastPasswordChangeDate DATETIME2(7) = NULL, @LastKdfChangeDate DATETIME2(7) = NULL, @LastKeyRotationDate DATETIME2(7) = NULL, - @LastEmailChangeDate DATETIME2(7) = NULL + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 AS BEGIN SET NOCOUNT ON @@ -88,7 +89,8 @@ BEGIN [LastPasswordChangeDate] = @LastPasswordChangeDate, [LastKdfChangeDate] = @LastKdfChangeDate, [LastKeyRotationDate] = @LastKeyRotationDate, - [LastEmailChangeDate] = @LastEmailChangeDate + [LastEmailChangeDate] = @LastEmailChangeDate, + [VerifyDevices] = @VerifyDevices WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 0c34784e97..188dd4ea3c 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -36,11 +36,12 @@ [UsesKeyConnector] BIT NOT NULL, [FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL, [LastFailedLoginDate] DATETIME2 (7) NULL, - [AvatarColor] VARCHAR(7) NULL, + [AvatarColor] VARCHAR(7) NULL, [LastPasswordChangeDate] DATETIME2 (7) NULL, [LastKdfChangeDate] DATETIME2 (7) NULL, [LastKeyRotationDate] DATETIME2 (7) NULL, [LastEmailChangeDate] DATETIME2 (7) NULL, + [VerifyDevices] BIT DEFAULT ((1)) NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql index a6c96299c2..20b896b6ad 100644 --- a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql @@ -36,7 +36,8 @@ SELECT O.[LimitCollectionCreation], O.[LimitCollectionDeletion], O.[AllowAdminAccessToAllCollectionItems], - O.[UseRiskInsights] + O.[UseRiskInsights], + P.[Type] ProviderType FROM [dbo].[ProviderUser] PU INNER JOIN diff --git a/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index 485126ebb2..0b5f5c1f01 100644 --- a/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs +++ b/test/Admin.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -1,6 +1,5 @@ using Bit.Admin.AdminConsole.Controllers; using Bit.Admin.AdminConsole.Models; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; @@ -9,7 +8,6 @@ using Bit.Core.Billing.Enums; using Bit.Core.Billing.Services; using Bit.Core.Enums; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -22,32 +20,6 @@ public class OrganizationsControllerTests { #region Edit (POST) - [BitAutoData] - [SutProviderCustomize] - [Theory] - public async Task Edit_ProviderSeatScaling_RequiredFFDisabled_NoOp( - SutProvider<OrganizationsController> sutProvider) - { - // Arrange - var organizationId = new Guid(); - var update = new OrganizationEditModel { UseSecretsManager = false }; - - var organization = new Organization - { - Id = organizationId - }; - - sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) - .Returns(organization); - - // Act - _ = await sutProvider.Sut.Edit(organizationId, update); - - // Assert - await sutProvider.GetDependency<IProviderBillingService>().DidNotReceiveWithAnyArgs() - .ScaleSeats(Arg.Any<Provider>(), Arg.Any<PlanType>(), Arg.Any<int>()); - } - [BitAutoData] [SutProviderCustomize] [Theory] @@ -66,10 +38,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Created }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -101,10 +69,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -143,10 +107,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -185,10 +145,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -227,10 +183,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -271,10 +223,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); @@ -314,10 +262,6 @@ public class OrganizationsControllerTests sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId) .Returns(organization); - var featureService = sutProvider.GetDependency<IFeatureService>(); - - featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate).Returns(true); - var provider = new Provider { Type = ProviderType.Msp, Status = ProviderStatusType.Billable }; sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organizationId).Returns(provider); diff --git a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs index 2839549159..d3d9f8d068 100644 --- a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs @@ -566,6 +566,49 @@ public class AccountsControllerTests : IDisposable await _userService.Received(1).DeleteAsync(user); } + [Theory] + [BitAutoData] + public async Task SetVerifyDevices_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException( + SetVerifyDevicesRequestModel model) + { + // Arrange + _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult((User)null)); + + // Act & Assert + await Assert.ThrowsAsync<UnauthorizedAccessException>(() => _sut.SetUserVerifyDevicesAsync(model)); + } + + [Theory] + [BitAutoData] + public async Task SetVerifyDevices_WhenInvalidSecret_ShouldFail( + User user, SetVerifyDevicesRequestModel model) + { + // Arrange + _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult((user))); + _userService.VerifySecretAsync(user, Arg.Any<string>()).Returns(Task.FromResult(false)); + + // Act & Assert + await Assert.ThrowsAsync<BadRequestException>(() => _sut.SetUserVerifyDevicesAsync(model)); + } + + [Theory] + [BitAutoData] + public async Task SetVerifyDevices_WhenRequestValid_ShouldSucceed( + User user, SetVerifyDevicesRequestModel model) + { + // Arrange + user.VerifyDevices = false; + model.VerifyDevices = true; + _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult((user))); + _userService.VerifySecretAsync(user, Arg.Any<string>()).Returns(Task.FromResult(true)); + + // Act + await _sut.SetUserVerifyDevicesAsync(model); + + await _userService.Received(1).SaveUserAsync(user); + Assert.Equal(model.VerifyDevices, user.VerifyDevices); + } + // Below are helper functions that currently belong to this // test class, but ultimately may need to be split out into // something greater in order to share common test steps with diff --git a/test/Api.Test/Auth/Controllers/DevicesControllerTests.cs b/test/Api.Test/Auth/Controllers/DevicesControllerTests.cs new file mode 100644 index 0000000000..3dcf2016c4 --- /dev/null +++ b/test/Api.Test/Auth/Controllers/DevicesControllerTests.cs @@ -0,0 +1,88 @@ +using Bit.Api.Controllers; +using Bit.Api.Models.Response; +using Bit.Core.Auth.Models.Api.Response; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Auth.Controllers; + +public class DevicesControllerTest +{ + private readonly IDeviceRepository _deviceRepositoryMock; + private readonly IDeviceService _deviceServiceMock; + private readonly IUserService _userServiceMock; + private readonly IUserRepository _userRepositoryMock; + private readonly ICurrentContext _currentContextMock; + private readonly IGlobalSettings _globalSettingsMock; + private readonly ILogger<DevicesController> _loggerMock; + private readonly DevicesController _sut; + + public DevicesControllerTest() + { + _deviceRepositoryMock = Substitute.For<IDeviceRepository>(); + _deviceServiceMock = Substitute.For<IDeviceService>(); + _userServiceMock = Substitute.For<IUserService>(); + _userRepositoryMock = Substitute.For<IUserRepository>(); + _currentContextMock = Substitute.For<ICurrentContext>(); + _loggerMock = Substitute.For<ILogger<DevicesController>>(); + + _sut = new DevicesController( + _deviceRepositoryMock, + _deviceServiceMock, + _userServiceMock, + _userRepositoryMock, + _currentContextMock, + _loggerMock); + } + + [Fact] + public async Task Get_ReturnsExpectedResult() + { + // Arrange + var userId = Guid.Parse("AD89E6F8-4E84-4CFE-A978-256CC0DBF974"); + + var authDateTimeResponse = new DateTime(2024, 12, 9, 12, 0, 0); + var devicesWithPendingAuthData = new List<DeviceAuthDetails> + { + new ( + new Device + { + Id = Guid.Parse("B3136B10-7818-444F-B05B-4D7A9B8C48BF"), + UserId = userId, + Name = "chrome", + Type = DeviceType.ChromeBrowser, + Identifier = Guid.Parse("811E9254-F77C-48C8-AF0A-A181943F5708").ToString() + }, + Guid.Parse("E09D6943-D574-49E5-AC85-C3F12B4E019E"), + authDateTimeResponse) + }; + + _userServiceMock.GetProperUserId(Arg.Any<System.Security.Claims.ClaimsPrincipal>()).Returns(userId); + _deviceRepositoryMock.GetManyByUserIdWithDeviceAuth(userId).Returns(devicesWithPendingAuthData); + + // Act + var result = await _sut.Get(); + + // Assert + Assert.NotNull(result); + Assert.IsType<ListResponseModel<DeviceAuthRequestResponseModel>>(result); + } + + [Fact] + public async Task Get_ThrowsException_WhenUserIdIsInvalid() + { + // Arrange + _userServiceMock.GetProperUserId(Arg.Any<System.Security.Claims.ClaimsPrincipal>()).Returns((Guid?)null); + + // Act & Assert + await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.Get()); + } +} diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index d500fb354a..a8c3cf15a9 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -52,7 +52,7 @@ public class OrganizationBillingControllerTests { sutProvider.GetDependency<ICurrentContext>().OrganizationUser(organizationId).Returns(true); sutProvider.GetDependency<IOrganizationBillingService>().GetMetadata(organizationId) - .Returns(new OrganizationMetadata(true, true, true, true, true, true, null, null, null)); + .Returns(new OrganizationMetadata(true, true, true, true, true, true, true, null, null, null)); var result = await sutProvider.Sut.GetMetadataAsync(organizationId); diff --git a/test/Core.Test/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommandTests.cs index 9dcfee78af..0103650777 100644 --- a/test/Core.Test/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationAuth/UpdateOrganizationAuthRequestCommandTests.cs @@ -6,6 +6,7 @@ using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Services; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs index 2c32f0504b..a16b48240c 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs @@ -36,6 +36,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PremiumAccessAddon = false; signup.UseSecretsManager = false; signup.IsFromSecretsManagerTrial = false; + signup.IsFromProvider = false; var result = await sutProvider.Sut.SignUpOrganizationAsync(signup); @@ -85,6 +86,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; signup.UseSecretsManager = false; + signup.IsFromProvider = false; // Extract orgUserId when created Guid? orgUserId = null; @@ -128,6 +130,8 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; signup.IsFromSecretsManagerTrial = false; + signup.IsFromProvider = false; + var result = await sutProvider.Sut.SignUpOrganizationAsync(signup); @@ -196,6 +200,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PremiumAccessAddon = false; signup.AdditionalServiceAccounts = 10; signup.AdditionalStorageGb = 0; + signup.IsFromProvider = false; var exception = await Assert.ThrowsAsync<BadRequestException>( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); @@ -213,6 +218,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; signup.AdditionalServiceAccounts = 10; + signup.IsFromProvider = false; var exception = await Assert.ThrowsAsync<BadRequestException>( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); @@ -230,6 +236,7 @@ public class CloudICloudOrganizationSignUpCommandTests signup.PaymentMethodType = PaymentMethodType.Card; signup.PremiumAccessAddon = false; signup.AdditionalServiceAccounts = -10; + signup.IsFromProvider = false; var exception = await Assert.ThrowsAsync<BadRequestException>( () => sutProvider.Sut.SignUpOrganizationAsync(signup)); diff --git a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs index cd7f85ae8b..4e42125dce 100644 --- a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs +++ b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs @@ -7,6 +7,7 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/test/Core.Test/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommandTests.cs b/test/Core.Test/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommandTests.cs index 3388956156..ba40198ef6 100644 --- a/test/Core.Test/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommandTests.cs +++ b/test/Core.Test/KeyManagement/Commands/RegenerateUserAsymmetricKeysCommandTests.cs @@ -8,7 +8,7 @@ using Bit.Core.Exceptions; using Bit.Core.KeyManagement.Commands; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.KeyManagement.Repositories; -using Bit.Core.Services; +using Bit.Core.Platform.Push; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; diff --git a/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs b/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs index b650d17240..53263d8805 100644 --- a/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs +++ b/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Entities; using Bit.Core.KeyManagement.Models.Data; using Bit.Core.KeyManagement.UserKey.Implementations; +using Bit.Core.Platform.Push; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index ea9ce54131..c26fc23460 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -1,6 +1,6 @@ using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; -using Bit.Core.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NSubstitute; diff --git a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs index 52bee7068f..44c87f7182 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs @@ -1,12 +1,11 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationLicenses; -using Bit.Core.Repositories; +using Bit.Core.Platform.Installations; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Test.Common.AutoFixture; diff --git a/test/Core.Test/Platform/Installations/Commands/UpdateInstallationCommandTests.cs b/test/Core.Test/Platform/Installations/Commands/UpdateInstallationCommandTests.cs new file mode 100644 index 0000000000..ec04ac711a --- /dev/null +++ b/test/Core.Test/Platform/Installations/Commands/UpdateInstallationCommandTests.cs @@ -0,0 +1,83 @@ +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Extensions.Time.Testing; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Platform.Installations.Tests; + +[SutProviderCustomize] +public class UpdateInstallationCommandTests +{ + [Theory] + [BitAutoData] + public async Task UpdateLastActivityDateAsync_WithDefaultGuid_ThrowsException(SutProvider<UpdateInstallationCommand> sutProvider) + { + // Arrange + var defaultGuid = default(Guid); + + // Act & Assert + var exception = await Assert.ThrowsAsync<Exception>( + () => sutProvider.Sut.UpdateLastActivityDateAsync(defaultGuid)); + + Assert.Contains("invalid installation id", exception.Message); + + await sutProvider + .GetDependency<IInstallationRepository>() + .DidNotReceive() + .UpsertAsync(Arg.Any<Installation>()); + } + + [Theory] + [BitAutoData] + public async Task UpdateLastActivityDateAsync_WithNonExistentInstallation_ThrowsException( + Guid installationId, + SutProvider<UpdateInstallationCommand> sutProvider) + { + // Arrange + sutProvider + .GetDependency<IGetInstallationQuery>() + .GetByIdAsync(installationId) + .Returns((Installation)null); + + // Act & Assert + var exception = await Assert.ThrowsAsync<Exception>( + () => sutProvider.Sut.UpdateLastActivityDateAsync(installationId)); + + Assert.Contains("no installation was found", exception.Message); + + await sutProvider + .GetDependency<IInstallationRepository>() + .DidNotReceive() + .UpsertAsync(Arg.Any<Installation>()); + } + + [Theory] + [BitAutoData] + public async Task UpdateLastActivityDateAsync_ShouldUpdateLastActivityDate( + Installation installation + ) + { + // Arrange + var sutProvider = new SutProvider<UpdateInstallationCommand>() + .WithFakeTimeProvider() + .Create(); + + var someDate = new DateTime(2014, 11, 3, 18, 27, 0, DateTimeKind.Utc); + sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(someDate); + + sutProvider + .GetDependency<IGetInstallationQuery>() + .GetByIdAsync(installation.Id) + .Returns(installation); + + // Act + await sutProvider.Sut.UpdateLastActivityDateAsync(installation.Id); + + // Assert + await sutProvider + .GetDependency<IInstallationRepository>() + .Received(1) + .UpsertAsync(Arg.Is<Installation>(inst => inst.LastActivityDate == someDate)); + } +} diff --git a/test/Core.Test/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs similarity index 90% rename from test/Core.Test/Services/AzureQueuePushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs index 7f9cb750aa..85ce5a79ac 100644 --- a/test/Core.Test/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs @@ -1,10 +1,9 @@ -using Bit.Core.Services; -using Bit.Core.Settings; +using Bit.Core.Settings; using Microsoft.AspNetCore.Http; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Platform.Push.Internal.Test; public class AzureQueuePushNotificationServiceTests { diff --git a/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs similarity index 96% rename from test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs index 68d6c50a7e..021aa7f2cc 100644 --- a/test/Core.Test/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs @@ -1,12 +1,11 @@ using AutoFixture; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; using GlobalSettingsCustomization = Bit.Test.Common.AutoFixture.GlobalSettings; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Platform.Push.Internal.Test; public class MultiServicePushNotificationServiceTests { diff --git a/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs similarity index 93% rename from test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs index d1ba15d6a5..78f60da359 100644 --- a/test/Core.Test/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs @@ -1,11 +1,10 @@ -using Bit.Core.Services; -using Bit.Core.Settings; +using Bit.Core.Settings; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Platform.Push.Internal.Test; public class NotificationsApiPushNotificationServiceTests { diff --git a/test/Core.Test/Services/RelayPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs similarity index 95% rename from test/Core.Test/Services/RelayPushNotificationServiceTests.cs rename to test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs index ccf5e3d4bb..61d7f0a788 100644 --- a/test/Core.Test/Services/RelayPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs @@ -1,12 +1,11 @@ using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Platform.Push.Internal.Test; public class RelayPushNotificationServiceTests { diff --git a/test/Core.Test/Services/RelayPushRegistrationServiceTests.cs b/test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs similarity index 91% rename from test/Core.Test/Services/RelayPushRegistrationServiceTests.cs rename to test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs index 926a19bc00..cfd843d2eb 100644 --- a/test/Core.Test/Services/RelayPushRegistrationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/RelayPushRegistrationServiceTests.cs @@ -1,10 +1,9 @@ -using Bit.Core.Services; -using Bit.Core.Settings; +using Bit.Core.Settings; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Platform.Push.Internal.Test; public class RelayPushRegistrationServiceTests { diff --git a/test/Core.Test/Services/DeviceServiceTests.cs b/test/Core.Test/Services/DeviceServiceTests.cs index cb2aebc992..41ef0b4d74 100644 --- a/test/Core.Test/Services/DeviceServiceTests.cs +++ b/test/Core.Test/Services/DeviceServiceTests.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; diff --git a/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs b/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs index 35b5e4ea72..a2c86b5a76 100644 --- a/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs +++ b/test/Core.Test/Services/LaunchDarklyFeatureServiceTests.cs @@ -22,6 +22,7 @@ public class LaunchDarklyFeatureServiceTests globalSettings.ProjectName = "LaunchDarkly Tests"; var currentContext = Substitute.For<ICurrentContext>(); + currentContext.DeviceIdentifier.Returns(Guid.NewGuid().ToString()); currentContext.UserId.Returns(Guid.NewGuid()); currentContext.ClientVersion.Returns(new Version(AssemblyHelpers.GetVersion())); currentContext.ClientVersionIsPrerelease.Returns(true); diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index e44609c6d6..74bebf328f 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -18,6 +18,7 @@ using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; diff --git a/test/Core.Test/Tools/Services/SendServiceTests.cs b/test/Core.Test/Tools/Services/SendServiceTests.cs index 0174efa67e..7ef6f915dd 100644 --- a/test/Core.Test/Tools/Services/SendServiceTests.cs +++ b/test/Core.Test/Tools/Services/SendServiceTests.cs @@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.CurrentContextFixtures; diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index 0df8f67490..dd34127efe 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -3,6 +3,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.CipherFixtures; diff --git a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs index ae64b832fe..38a1518d14 100644 --- a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs +++ b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs @@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; +using Bit.Core.Platform.Installations; using Bit.Core.Repositories; using Bit.Identity.IdentityServer; using Bit.Identity.Models.Request.Accounts; @@ -462,7 +463,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory> } [Theory, BitAutoData] - public async Task TokenEndpoint_GrantTypeClientCredentials_AsInstallation_InstallationExists_Succeeds(Bit.Core.Entities.Installation installation) + public async Task TokenEndpoint_GrantTypeClientCredentials_AsInstallation_InstallationExists_Succeeds(Installation installation) { var installationRepo = _factory.Services.GetRequiredService<IInstallationRepository>(); installation = await installationRepo.CreateAsync(installation); diff --git a/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs b/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs index 105267ea30..fa3a117c55 100644 --- a/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs @@ -429,6 +429,30 @@ public class DeviceValidatorTests Assert.Equal(expectedErrorMessage, actualResponse.Message); } + [Theory, BitAutoData] + public async void HandleNewDeviceVerificationAsync_VerifyDevicesFalse_ReturnsSuccess( + CustomValidatorRequestContext context, + [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) + { + // Arrange + ArrangeForHandleNewDeviceVerificationTest(context, request); + _featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true); + _globalSettings.EnableNewDeviceVerification = true; + context.User.VerifyDevices = false; + + // Act + var result = await _sut.ValidateRequestDeviceAsync(request, context); + + // Assert + await _userService.Received(0).SendOTPAsync(context.User); + await _deviceService.Received(1).SaveAsync(Arg.Any<Device>()); + + Assert.True(result); + Assert.False(context.CustomResponse.ContainsKey("ErrorModel")); + Assert.Equal(context.User.Id, context.Device.UserId); + Assert.NotNull(context.Device); + } + [Theory, BitAutoData] public async void HandleNewDeviceVerificationAsync_UserHasCacheValue_ReturnsSuccess( CustomValidatorRequestContext context, diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/DeviceFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/DeviceFixtures.cs index da5b5b7676..0ac3881511 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/DeviceFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/DeviceFixtures.cs @@ -2,7 +2,9 @@ using AutoFixture.Kernel; using Bit.Core.Entities; using Bit.Core.Test.AutoFixture.UserFixtures; +using Bit.Infrastructure.EFIntegration.Test.Auth.AutoFixture; using Bit.Infrastructure.EFIntegration.Test.AutoFixture.Relays; +using Bit.Infrastructure.EntityFramework.Auth.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -39,8 +41,10 @@ internal class EfDevice : ICustomization fixture.Customizations.Add(new GlobalSettingsBuilder()); fixture.Customizations.Add(new DeviceBuilder()); fixture.Customizations.Add(new UserBuilder()); + fixture.Customizations.Add(new AuthRequestBuilder()); fixture.Customizations.Add(new EfRepositoryListBuilder<DeviceRepository>()); fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<AuthRequestRepository>()); } } diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs index 3775c9953d..0ebcf8903d 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs @@ -8,6 +8,7 @@ using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider; using Bit.Infrastructure.EntityFramework.Auth.Models; using Bit.Infrastructure.EntityFramework.Models; +using Bit.Infrastructure.EntityFramework.Platform; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Tools.Models; using Bit.Infrastructure.EntityFramework.Vault.Models; diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/InstallationFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/InstallationFixtures.cs index c090a2e38e..7b57824442 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/InstallationFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/InstallationFixtures.cs @@ -1,9 +1,9 @@ using AutoFixture; using AutoFixture.Kernel; -using Bit.Core.Entities; -using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; +using C = Bit.Core.Platform.Installations; +using Ef = Bit.Infrastructure.EntityFramework.Platform; namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture; @@ -17,13 +17,13 @@ internal class InstallationBuilder : ISpecimenBuilder } var type = request as Type; - if (type == null || type != typeof(Installation)) + if (type == null || type != typeof(C.Installation)) { return new NoSpecimen(); } var fixture = new Fixture(); - var obj = fixture.WithAutoNSubstitutions().Create<Installation>(); + var obj = fixture.WithAutoNSubstitutions().Create<C.Installation>(); return obj; } } @@ -35,7 +35,7 @@ internal class EfInstallation : ICustomization fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); fixture.Customizations.Add(new GlobalSettingsBuilder()); fixture.Customizations.Add(new InstallationBuilder()); - fixture.Customizations.Add(new EfRepositoryListBuilder<InstallationRepository>()); + fixture.Customizations.Add(new EfRepositoryListBuilder<Ef.InstallationRepository>()); } } diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/InstallationCompare.cs b/test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationCompare.cs similarity index 78% rename from test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/InstallationCompare.cs rename to test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationCompare.cs index 7794785b31..9b685f8095 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/InstallationCompare.cs +++ b/test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationCompare.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using Bit.Core.Entities; +using Bit.Core.Platform.Installations; -namespace Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; +namespace Bit.Infrastructure.EFIntegration.Test.Platform; public class InstallationCompare : IEqualityComparer<Installation> { diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/InstallationRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationRepositoryTests.cs similarity index 64% rename from test/Infrastructure.EFIntegration.Test/Repositories/InstallationRepositoryTests.cs rename to test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationRepositoryTests.cs index 3e4f7eb5df..e57b2311ef 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/InstallationRepositoryTests.cs +++ b/test/Infrastructure.EFIntegration.Test/Platform/Installations/Repositories/InstallationRepositoryTests.cs @@ -1,24 +1,23 @@ -using Bit.Core.Entities; -using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture.Attributes; using Bit.Infrastructure.EFIntegration.Test.AutoFixture; -using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; using Xunit; -using EfRepo = Bit.Infrastructure.EntityFramework.Repositories; -using SqlRepo = Bit.Infrastructure.Dapper.Repositories; +using C = Bit.Core.Platform.Installations; +using D = Bit.Infrastructure.Dapper.Platform; +using Ef = Bit.Infrastructure.EntityFramework.Platform; -namespace Bit.Infrastructure.EFIntegration.Test.Repositories; +namespace Bit.Infrastructure.EFIntegration.Test.Platform; public class InstallationRepositoryTests { [CiSkippedTheory, EfInstallationAutoData] public async Task CreateAsync_Works_DataMatches( - Installation installation, + C.Installation installation, InstallationCompare equalityComparer, - List<EfRepo.InstallationRepository> suts, - SqlRepo.InstallationRepository sqlInstallationRepo + List<Ef.InstallationRepository> suts, + D.InstallationRepository sqlInstallationRepo ) { - var savedInstallations = new List<Installation>(); + var savedInstallations = new List<C.Installation>(); foreach (var sut in suts) { var postEfInstallation = await sut.CreateAsync(installation); diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/DeviceRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Repositories/DeviceRepositoryTests.cs index 078fed0469..cc914d9aae 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/DeviceRepositoryTests.cs +++ b/test/Infrastructure.EFIntegration.Test/Repositories/DeviceRepositoryTests.cs @@ -11,9 +11,13 @@ namespace Bit.Infrastructure.EFIntegration.Test.Repositories; public class DeviceRepositoryTests { [CiSkippedTheory, EfDeviceAutoData] - public async Task CreateAsync_Works_DataMatches(Device device, User user, - DeviceCompare equalityComparer, List<EfRepo.DeviceRepository> suts, - List<EfRepo.UserRepository> efUserRepos, SqlRepo.DeviceRepository sqlDeviceRepo, + public async Task CreateAsync_Works_DataMatches( + Device device, + User user, + DeviceCompare equalityComparer, + List<EfRepo.DeviceRepository> suts, + List<EfRepo.UserRepository> efUserRepos, + SqlRepo.DeviceRepository sqlDeviceRepo, SqlRepo.UserRepository sqlUserRepo) { var savedDevices = new List<Device>(); @@ -40,7 +44,6 @@ public class DeviceRepositoryTests savedDevices.Add(savedSqlDevice); var distinctItems = savedDevices.Distinct(equalityComparer); - Assert.True(!distinctItems.Skip(1).Any()); + Assert.False(distinctItems.Skip(1).Any()); } - } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs new file mode 100644 index 0000000000..8e0b502a47 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs @@ -0,0 +1,191 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.Repositories; + +public class OrganizationDomainRepositoryTests +{ + [DatabaseTheory, DatabaseData] + public async Task GetExpiredOrganizationDomainsAsync_ShouldReturn3DaysOldUnverifiedDomains( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var user1 = await userRepository.CreateAsync(new User + { + Name = "Test User 1", + Email = $"test+{id}@example.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization1 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = user1.Email, + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var organizationDomain1 = new OrganizationDomain + { + OrganizationId = organization1.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + var dummyInterval = 1; + organizationDomain1.SetNextRunDate(dummyInterval); + + var beforeValidationDate = DateTime.UtcNow.AddDays(-4).Date; + + await organizationDomainRepository.CreateAsync(organizationDomain1); + var organization2 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = user1.Email, + Plan = "Test", + PrivateKey = "privatekey", + CreationDate = beforeValidationDate + }); + var organizationDomain2 = new OrganizationDomain + { + OrganizationId = organization2.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345", + CreationDate = beforeValidationDate + }; + organizationDomain2.SetNextRunDate(dummyInterval); + await organizationDomainRepository.CreateAsync(organizationDomain2); + + // Act + var domains = await organizationDomainRepository.GetExpiredOrganizationDomainsAsync(); + + // Assert + var expectedDomain1 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain1.DomainName); + Assert.NotNull(expectedDomain1); + + var expectedDomain2 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain2.DomainName); + Assert.NotNull(expectedDomain2); + } + + [DatabaseTheory, DatabaseData] + public async Task GetExpiredOrganizationDomainsAsync_ShouldNotReturnDomainsUnder3DaysOld( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{id}@example.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = user.Email, + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var beforeValidationDate = DateTime.UtcNow.AddDays(-1).Date; + var organizationDomain = new OrganizationDomain + { + OrganizationId = organization.Id, + DomainName = $"domain{id}@example.com", + Txt = "btw+12345", + CreationDate = beforeValidationDate + }; + var dummyInterval = 1; + organizationDomain.SetNextRunDate(dummyInterval); + await organizationDomainRepository.CreateAsync(organizationDomain); + + // Act + var domains = await organizationDomainRepository.GetExpiredOrganizationDomainsAsync(); + + // Assert + var expectedDomain2 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain.DomainName); + Assert.Null(expectedDomain2); + } + + [DatabaseTheory, DatabaseData] + public async Task GetExpiredOrganizationDomainsAsync_ShouldNotReturnVerifiedDomains( + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var user = await userRepository.CreateAsync(new User + { + Name = "Test User 1", + Email = $"test+{id}@example.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var organization1 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = user.Email, + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var organizationDomain1 = new OrganizationDomain + { + OrganizationId = organization1.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + organizationDomain1.SetVerifiedDate(); + var dummyInterval = 1; + + organizationDomain1.SetNextRunDate(dummyInterval); + + await organizationDomainRepository.CreateAsync(organizationDomain1); + + var organization2 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = user.Email, + Plan = "Test", + PrivateKey = "privatekey", + }); + + var organizationDomain2 = new OrganizationDomain + { + OrganizationId = organization2.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + organizationDomain2.SetNextRunDate(dummyInterval); + organizationDomain2.SetVerifiedDate(); + + await organizationDomainRepository.CreateAsync(organizationDomain2); + + // Act + var domains = await organizationDomainRepository.GetExpiredOrganizationDomainsAsync(); + + // Assert + var expectedDomain1 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain1.DomainName); + Assert.Null(expectedDomain1); + + var expectedDomain2 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain2.DomainName); + Assert.Null(expectedDomain2); + } +} diff --git a/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs index 9fddb571b9..8cd8cb607c 100644 --- a/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs @@ -8,9 +8,9 @@ namespace Bit.Infrastructure.IntegrationTest.Auth.Repositories; public class AuthRequestRepositoryTests { - private readonly static TimeSpan _userRequestExpiration = TimeSpan.FromMinutes(15); - private readonly static TimeSpan _adminRequestExpiration = TimeSpan.FromDays(6); - private readonly static TimeSpan _afterAdminApprovalExpiration = TimeSpan.FromHours(12); + private static readonly TimeSpan _userRequestExpiration = TimeSpan.FromMinutes(15); + private static readonly TimeSpan _adminRequestExpiration = TimeSpan.FromDays(6); + private static readonly TimeSpan _afterAdminApprovalExpiration = TimeSpan.FromHours(12); [DatabaseTheory, DatabaseData] public async Task DeleteExpiredAsync_Works( @@ -25,11 +25,11 @@ public class AuthRequestRepositoryTests SecurityStamp = "stamp", }); - // A user auth request type that has passed it's expiration time, should be deleted. + // A user auth request type that has passed its expiration time, should be deleted. var userExpiredAuthRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.AuthenticateAndUnlock, CreateExpiredDate(_userRequestExpiration))); - // An AdminApproval request that hasn't had any action taken on it and has passed it's expiration time, should be deleted. + // An AdminApproval request that hasn't had any action taken on it and has passed its expiration time, should be deleted. var adminApprovalExpiredAuthRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration))); @@ -37,7 +37,7 @@ public class AuthRequestRepositoryTests var adminApprovedExpiredAuthRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.AddDays(-6), true, CreateExpiredDate(_afterAdminApprovalExpiration))); - // An AdminApproval request that was rejected within it's allowed lifetime but has no gone past it's expiration time, should be deleted. + // An AdminApproval request that was rejected within its allowed lifetime but has not gone past its expiration time, should be deleted. var adminRejectedExpiredAuthRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration), false, DateTime.UtcNow.AddHours(-1))); @@ -45,7 +45,7 @@ public class AuthRequestRepositoryTests var notExpiredUserAuthRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.Unlock, DateTime.UtcNow.AddMinutes(-1))); - // An AdminApproval AuthRequest that was create 6 days 23 hours 59 minutes 59 seconds ago which is right on the edge of still being valid + // An AdminApproval AuthRequest that was created 6 days 23 hours 59 minutes 59 seconds ago which is right on the edge of still being valid var notExpiredAdminApprovalRequest = await authRequestRepository.CreateAsync( CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.Add(new TimeSpan(days: 6, hours: 23, minutes: 59, seconds: 59)))); diff --git a/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs new file mode 100644 index 0000000000..a9eec23194 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs @@ -0,0 +1,191 @@ +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.Auth.Repositories; + +public class DeviceRepositoryTests +{ + [DatabaseTheory] + [DatabaseData] + public async Task GetManyByUserIdWithDeviceAuth_Works_ReturnsExpectedResults( + IDeviceRepository sutRepository, + IUserRepository userRepository, + IAuthRequestRepository authRequestRepository) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var device = await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "chrome-test", + UserId = user.Id, + Type = DeviceType.ChromeBrowser, + Identifier = Guid.NewGuid().ToString(), + }); + + var staleAuthRequest = await authRequestRepository.CreateAsync(new AuthRequest + { + ResponseDeviceId = null, + Approved = null, + Type = AuthRequestType.AuthenticateAndUnlock, + OrganizationId = null, + UserId = user.Id, + RequestIpAddress = ":1", + RequestDeviceIdentifier = device.Identifier, + AccessCode = "AccessCode_1234", + PublicKey = "PublicKey_1234" + }); + staleAuthRequest.CreationDate = DateTime.UtcNow.AddMinutes(-10); + await authRequestRepository.ReplaceAsync(staleAuthRequest); + + var freshAuthRequest = await authRequestRepository.CreateAsync(new AuthRequest + { + ResponseDeviceId = null, + Approved = null, + Type = AuthRequestType.AuthenticateAndUnlock, + OrganizationId = null, + UserId = user.Id, + RequestIpAddress = ":1", + RequestDeviceIdentifier = device.Identifier, + AccessCode = "AccessCode_1234", + PublicKey = "PublicKey_1234", + Key = "Key_1234", + MasterPasswordHash = "MasterPasswordHash_1234" + }); + + // Act + var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id); + + // Assert + Assert.NotNull(response.First().AuthRequestId); + Assert.NotNull(response.First().AuthRequestCreatedAt); + Assert.Equal(response.First().AuthRequestId, freshAuthRequest.Id); + } + + [DatabaseTheory] + [DatabaseData] + public async Task GetManyByUserIdWithDeviceAuth_WorksWithNoAuthRequestAndMultipleDevices_ReturnsExpectedResults( + IDeviceRepository sutRepository, + IUserRepository userRepository) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "chrome-test", + UserId = user.Id, + Type = DeviceType.ChromeBrowser, + Identifier = Guid.NewGuid().ToString(), + }); + + await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "macos-test", + UserId = user.Id, + Type = DeviceType.MacOsDesktop, + Identifier = Guid.NewGuid().ToString(), + }); + + // Act + var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id); + + // Assert + Assert.NotNull(response.First()); + Assert.Null(response.First().AuthRequestId); + Assert.True(response.Count == 2); + } + + [DatabaseTheory] + [DatabaseData] + public async Task GetManyByUserIdWithDeviceAuth_FailsToRespondWithAnyAuthData_ReturnsExpectedResults( + IDeviceRepository sutRepository, + IUserRepository userRepository, + IAuthRequestRepository authRequestRepository) + { + var casesThatCauseNoAuthDataInResponse = new[] + { + new + { + authRequestType = AuthRequestType.AdminApproval, // Device typing is wrong + authRequestApproved = (bool?)null, + expirey = DateTime.UtcNow.AddMinutes(0), + }, + new + { + authRequestType = AuthRequestType.AuthenticateAndUnlock, + authRequestApproved = (bool?)true, // Auth request is already approved + expirey = DateTime.UtcNow.AddMinutes(0), + }, + new + { + authRequestType = AuthRequestType.AuthenticateAndUnlock, + authRequestApproved = (bool?)null, + expirey = DateTime.UtcNow.AddMinutes(-30), // Past the point of expiring + } + }; + + foreach (var testCase in casesThatCauseNoAuthDataInResponse) + { + // Arrange + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var device = await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "chrome-test", + UserId = user.Id, + Type = DeviceType.ChromeBrowser, + Identifier = Guid.NewGuid().ToString(), + }); + + var authRequest = await authRequestRepository.CreateAsync(new AuthRequest + { + ResponseDeviceId = null, + Approved = testCase.authRequestApproved, + Type = testCase.authRequestType, + OrganizationId = null, + UserId = user.Id, + RequestIpAddress = ":1", + RequestDeviceIdentifier = device.Identifier, + AccessCode = "AccessCode_1234", + PublicKey = "PublicKey_1234" + }); + + authRequest.CreationDate = testCase.expirey; + await authRequestRepository.ReplaceAsync(authRequest); + + // Act + var response = await sutRepository.GetManyByUserIdWithDeviceAuth(user.Id); + + // Assert + Assert.Null(response.First().AuthRequestId); + Assert.Null(response.First().AuthRequestCreatedAt); + } + } +} diff --git a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs index 746ce988a4..498cc668c0 100644 --- a/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs +++ b/test/Infrastructure.IntegrationTest/DatabaseDataAttribute.cs @@ -41,6 +41,9 @@ public class DatabaseDataAttribute : DataAttribute protected virtual IEnumerable<IServiceProvider> GetDatabaseProviders(IConfiguration config) { + // This is for the device repository integration testing. + var userRequestExpiration = 15; + var configureLogging = (ILoggingBuilder builder) => { if (!config.GetValue<bool>("Quiet")) @@ -67,11 +70,15 @@ public class DatabaseDataAttribute : DataAttribute { ConnectionString = database.ConnectionString, }, + PasswordlessAuth = new GlobalSettings.PasswordlessAuthSettings + { + UserRequestExpiration = TimeSpan.FromMinutes(userRequestExpiration), + } }; dapperSqlServerCollection.AddSingleton(globalSettings); dapperSqlServerCollection.AddSingleton<IGlobalSettings>(globalSettings); dapperSqlServerCollection.AddSingleton(database); - dapperSqlServerCollection.AddDistributedSqlServerCache((o) => + dapperSqlServerCollection.AddDistributedSqlServerCache(o => { o.ConnectionString = database.ConnectionString; o.SchemaName = "dbo"; @@ -91,6 +98,17 @@ public class DatabaseDataAttribute : DataAttribute AddCommonServices(efCollection, configureLogging); efCollection.SetupEntityFramework(database.ConnectionString, database.Type); efCollection.AddPasswordManagerEFRepositories(SelfHosted); + + var globalSettings = new GlobalSettings + { + PasswordlessAuth = new GlobalSettings.PasswordlessAuthSettings + { + UserRequestExpiration = TimeSpan.FromMinutes(userRequestExpiration), + } + }; + efCollection.AddSingleton(globalSettings); + efCollection.AddSingleton<IGlobalSettings>(globalSettings); + efCollection.AddSingleton(database); efCollection.AddSingleton<IDistributedCache, EntityFrameworkCache>(); @@ -117,7 +135,7 @@ public class DatabaseDataAttribute : DataAttribute private void AddSqlMigrationTester(IServiceCollection services, string connectionString, string migrationName) { - services.AddSingleton<IMigrationTesterService, SqlMigrationTesterService>(sp => new SqlMigrationTesterService(connectionString, migrationName)); + services.AddSingleton<IMigrationTesterService, SqlMigrationTesterService>(_ => new SqlMigrationTesterService(connectionString, migrationName)); } private void AddEfMigrationTester(IServiceCollection services, SupportedDatabaseProviders databaseType, string migrationName) diff --git a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj index 417525f064..159572f387 100644 --- a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj +++ b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj @@ -7,9 +7,9 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" /> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" /> <PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.10.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNetTestSdkVersion)" /> diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index 3ce2599705..9474ffb862 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -1,5 +1,7 @@ using AspNetCoreRateLimit; using Bit.Core.Auth.Services; +using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Services; diff --git a/test/IntegrationTestCommon/IntegrationTestCommon.csproj b/test/IntegrationTestCommon/IntegrationTestCommon.csproj index 2a65c4c364..3e8e55524b 100644 --- a/test/IntegrationTestCommon/IntegrationTestCommon.csproj +++ b/test/IntegrationTestCommon/IntegrationTestCommon.csproj @@ -6,7 +6,7 @@ <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" /> - <PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" /> </ItemGroup> <ItemGroup> diff --git a/util/Migrator/DbScripts/2024-12-04_00_AddActiveDeviceWithPendingAuth.sql b/util/Migrator/DbScripts/2024-12-04_00_AddActiveDeviceWithPendingAuth.sql new file mode 100644 index 0000000000..1f358d53ab --- /dev/null +++ b/util/Migrator/DbScripts/2024-12-04_00_AddActiveDeviceWithPendingAuth.sql @@ -0,0 +1,27 @@ +CREATE OR ALTER PROCEDURE [dbo].[Device_ReadActiveWithPendingAuthRequestsByUserId] + @UserId UNIQUEIDENTIFIER, + @ExpirationMinutes INT +AS +BEGIN + SET NOCOUNT ON; + + SELECT + D.*, + AR.Id as AuthRequestId, + AR.CreationDate as AuthRequestCreationDate + FROM dbo.DeviceView D + LEFT JOIN ( + SELECT TOP 1 -- Take only the top record sorted by auth request creation date + Id, + CreationDate, + RequestDeviceIdentifier + FROM dbo.AuthRequestView + WHERE Type IN (0, 1) -- Include only AuthenticateAndUnlock and Unlock types, excluding Admin Approval (type 2) + AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired + AND Approved IS NULL -- Include only requests that haven't been acknowledged or approved + ORDER BY CreationDate DESC + ) AR ON D.Identifier = AR.RequestDeviceIdentifier + WHERE + D.UserId = @UserId + AND D.Active = 1; -- Include only active devices +END; diff --git a/util/Migrator/DbScripts/2024-12-18_00_AlterUserTable_AddVerifyDevices.sql b/util/Migrator/DbScripts/2024-12-18_00_AlterUserTable_AddVerifyDevices.sql new file mode 100644 index 0000000000..f6b778bef5 --- /dev/null +++ b/util/Migrator/DbScripts/2024-12-18_00_AlterUserTable_AddVerifyDevices.sql @@ -0,0 +1,252 @@ +IF COL_LENGTH('[dbo].[User]', 'VerifyDevices') IS NULL +BEGIN + ALTER TABLE + [dbo].[User] + ADD + [VerifyDevices] BIT NOT NULL DEFAULT 1 +END +GO + +EXECUTE sp_refreshview 'dbo.UserView' +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT = 0, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) = NULL, + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[User] + ( + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [Kdf], + [KdfIterations], + [CreationDate], + [RevisionDate], + [ApiKey], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor], + [KdfMemory], + [KdfParallelism], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate], + [VerifyDevices] + ) + VALUES + ( + @Id, + @Name, + @Email, + @EmailVerified, + @MasterPassword, + @MasterPasswordHint, + @Culture, + @SecurityStamp, + @TwoFactorProviders, + @TwoFactorRecoveryCode, + @EquivalentDomains, + @ExcludedGlobalEquivalentDomains, + @AccountRevisionDate, + @Key, + @PublicKey, + @PrivateKey, + @Premium, + @PremiumExpirationDate, + @RenewalReminderDate, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @LicenseKey, + @Kdf, + @KdfIterations, + @CreationDate, + @RevisionDate, + @ApiKey, + @ForcePasswordReset, + @UsesKeyConnector, + @FailedLoginCount, + @LastFailedLoginDate, + @AvatarColor, + @KdfMemory, + @KdfParallelism, + @LastPasswordChangeDate, + @LastKdfChangeDate, + @LastKeyRotationDate, + @LastEmailChangeDate, + @VerifyDevices + ) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7), + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL, + @VerifyDevices BIT = 1 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [KdfMemory] = @KdfMemory, + [KdfParallelism] = @KdfParallelism, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesKeyConnector] = @UsesKeyConnector, + [FailedLoginCount] = @FailedLoginCount, + [LastFailedLoginDate] = @LastFailedLoginDate, + [AvatarColor] = @AvatarColor, + [LastPasswordChangeDate] = @LastPasswordChangeDate, + [LastKdfChangeDate] = @LastKdfChangeDate, + [LastKeyRotationDate] = @LastKeyRotationDate, + [LastEmailChangeDate] = @LastEmailChangeDate, + [VerifyDevices] = @VerifyDevices + WHERE + [Id] = @Id +END +GO diff --git a/util/Migrator/DbScripts/2025-01-03_00_ProviderUserProviderOrgDetailsView_AddProviderType.sql b/util/Migrator/DbScripts/2025-01-03_00_ProviderUserProviderOrgDetailsView_AddProviderType.sql new file mode 100644 index 0000000000..aafef5a5b7 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-03_00_ProviderUserProviderOrgDetailsView_AddProviderType.sql @@ -0,0 +1,49 @@ +CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] +AS +SELECT + PU.[UserId], + PO.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + PO.[Key], + O.[PublicKey], + O.[PrivateKey], + PU.[Status], + PU.[Type], + PO.[ProviderId], + PU.[Id] ProviderUserId, + P.[Name] ProviderName, + O.[PlanType], + O.[LimitCollectionCreationDeletion], -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + P.[Type] ProviderType +FROM + [dbo].[ProviderUser] PU + INNER JOIN + [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId] + INNER JOIN + [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId] + INNER JOIN + [dbo].[Provider] P ON P.[Id] = PU.[ProviderId] +GO diff --git a/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.Designer.cs b/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.Designer.cs new file mode 100644 index 0000000000..3c8c56e1cc --- /dev/null +++ b/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.Designer.cs @@ -0,0 +1,2997 @@ +// <auto-generated /> +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("20241219035803_AlterUser_AddVerifyDevice")] + partial class AlterUser_AddVerifyDevice + { + /// <inheritdoc /> + 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.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property<string>("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<bool>("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("LimitCollectionCreationDeletion") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property<int?>("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property<int?>("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<DateTime?>("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property<string>("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<int?>("Seats") + .HasColumnType("int"); + + b.Property<bool>("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property<int?>("SmSeats") + .HasColumnType("int"); + + b.Property<int?>("SmServiceAccounts") + .HasColumnType("int"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<bool>("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("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.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("BillingEmail") + .HasColumnType("longtext"); + + b.Property<string>("BillingPhone") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property<string>("BusinessCountry") + .HasColumnType("longtext"); + + b.Property<string>("BusinessName") + .HasColumnType("longtext"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property<string>("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<bool>("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasColumnType("longtext"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property<bool?>("Approved") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<DateTime?>("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid?>("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GrantorId") + .HasColumnType("char(36)"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("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<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<int>("Id")); + + b.Property<string>("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<string>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id")); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id")); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid>("AaGuid") + .HasColumnType("char(36)"); + + b.Property<int>("Counter") + .HasColumnType("int"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property<string>("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property<string>("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<int>("Seats") + .HasColumnType("int"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("InstallationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int>("AssignedSeats") + .HasColumnType("int"); + + b.Property<Guid?>("ClientId") + .HasColumnType("char(36)"); + + b.Property<string>("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<DateTime>("Created") + .HasColumnType("datetime(6)"); + + b.Property<string>("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<decimal>("Total") + .HasColumnType("decimal(65,30)"); + + b.Property<int>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int?>("AllocatedSeats") + .HasColumnType("int"); + + b.Property<byte>("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<int?>("PurchasedSeats") + .HasColumnType("int"); + + b.Property<int?>("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property<string>("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property<DateTime?>("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property<long?>("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property<byte[]>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("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<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("Manage") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<bool>("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("Manage") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property<bool>("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property<string>("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property<string>("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("Date") + .HasColumnType("datetime(6)"); + + b.Property<byte?>("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("DomainName") + .HasColumnType("longtext"); + + b.Property<Guid?>("GroupId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("InstallationId") + .HasColumnType("char(36)"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("SecretId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property<byte?>("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("Type") + .HasColumnType("int"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("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<Guid>("GroupId") + .HasColumnType("char(36)"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property<DateTime?>("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Config") + .HasColumnType("longtext"); + + b.Property<bool>("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property<int>("JobRunCount") + .HasColumnType("int"); + + b.Property<DateTime?>("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<DateTime?>("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<byte?>("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid?>("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property<Guid>("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property<bool>("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<bool>("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Permissions") + .HasColumnType("longtext"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<short>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<int>("AccessCount") + .HasColumnType("int"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool?>("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("int"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("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<string>("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property<bool>("Active") + .HasColumnType("tinyint(1)"); + + b.Property<string>("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<decimal>("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("char(36)"); + + b.Property<bool?>("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property<string>("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<bool>("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property<int>("FailedLoginCount") + .HasColumnType("int"); + + b.Property<bool>("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property<byte?>("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property<int>("KdfIterations") + .HasColumnType("int"); + + b.Property<int?>("KdfMemory") + .HasColumnType("int"); + + b.Property<int?>("KdfParallelism") + .HasColumnType("int"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<DateTime?>("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<bool>("Premium") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("PrivateKey") + .HasColumnType("longtext"); + + b.Property<string>("PublicKey") + .HasColumnType("longtext"); + + b.Property<string>("ReferenceData") + .HasColumnType("longtext"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property<bool>("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property<bool>("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Body") + .HasColumnType("longtext"); + + b.Property<byte>("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("Global") + .HasColumnType("tinyint(1)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte>("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property<Guid?>("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .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<Guid>("UserId") + .HasColumnType("char(36)"); + + b.Property<Guid>("NotificationId") + .HasColumnType("char(36)"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("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.SecretsManager.Models.AccessPolicy", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property<bool>("Read") + .HasColumnType("tinyint(1)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<bool>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property<DateTime?>("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<string>("Note") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("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.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("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.Vault.Models.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<string>("Attachments") + .HasColumnType("longtext"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Data") + .HasColumnType("longtext"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Favorites") + .HasColumnType("longtext"); + + b.Property<string>("Folders") + .HasColumnType("longtext"); + + b.Property<string>("Key") + .HasColumnType("longtext"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<byte?>("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Type") + .HasColumnType("tinyint unsigned"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<string>("Name") + .HasColumnType("longtext"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("char(36)"); + + b.Property<Guid?>("CipherId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("char(36)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property<byte>("Status") + .HasColumnType("tinyint unsigned"); + + b.Property<byte>("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<Guid>("ProjectsId") + .HasColumnType("char(36)"); + + b.Property<Guid>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.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.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.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.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"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.cs b/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.cs new file mode 100644 index 0000000000..f3a3ccd316 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20241219035803_AlterUser_AddVerifyDevice.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// <inheritdoc /> +public partial class AlterUser_AddVerifyDevice : Migration +{ + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<bool>( + name: "VerifyDevices", + table: "User", + type: "tinyint(1)", + nullable: false, + defaultValue: true); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VerifyDevices", + table: "User"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index ed26d612a2..dcc525c433 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1662,6 +1662,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property<bool>("UsesKeyConnector") .HasColumnType("tinyint(1)"); + b.Property<bool>("VerifyDevices") + .HasColumnType("tinyint(1)"); + b.HasKey("Id"); b.HasIndex("Email") diff --git a/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.Designer.cs b/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.Designer.cs new file mode 100644 index 0000000000..14101cd0b1 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.Designer.cs @@ -0,0 +1,3003 @@ +// <auto-generated /> +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("20241219035734_AlterUser_AddVerifyDevice")] + partial class AlterUser_AddVerifyDevice + { + /// <inheritdoc /> + 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.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<bool>("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property<string>("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<bool>("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property<bool>("LimitCollectionCreationDeletion") + .HasColumnType("boolean"); + + b.Property<bool>("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property<int?>("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property<int?>("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property<short?>("MaxCollections") + .HasColumnType("smallint"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<DateTime?>("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<byte>("PlanType") + .HasColumnType("smallint"); + + b.Property<string>("PrivateKey") + .HasColumnType("text"); + + b.Property<string>("PublicKey") + .HasColumnType("text"); + + b.Property<string>("ReferenceData") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<int?>("Seats") + .HasColumnType("integer"); + + b.Property<bool>("SelfHost") + .HasColumnType("boolean"); + + b.Property<int?>("SmSeats") + .HasColumnType("integer"); + + b.Property<int?>("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("text"); + + b.Property<bool>("Use2fa") + .HasColumnType("boolean"); + + b.Property<bool>("UseApi") + .HasColumnType("boolean"); + + b.Property<bool>("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property<bool>("UseDirectory") + .HasColumnType("boolean"); + + b.Property<bool>("UseEvents") + .HasColumnType("boolean"); + + b.Property<bool>("UseGroups") + .HasColumnType("boolean"); + + b.Property<bool>("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property<bool>("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property<bool>("UsePolicies") + .HasColumnType("boolean"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("boolean"); + + b.Property<bool>("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property<bool>("UseScim") + .HasColumnType("boolean"); + + b.Property<bool>("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property<bool>("UseSso") + .HasColumnType("boolean"); + + b.Property<bool>("UseTotp") + .HasColumnType("boolean"); + + b.Property<bool>("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.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("BillingEmail") + .HasColumnType("text"); + + b.Property<string>("BillingPhone") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("text"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("text"); + + b.Property<string>("BusinessCountry") + .HasColumnType("text"); + + b.Property<string>("BusinessName") + .HasColumnType("text"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayCustomerId") + .HasColumnType("text"); + + b.Property<string>("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<bool>("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Email") + .HasColumnType("text"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<string>("Permissions") + .HasColumnType("text"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property<bool?>("Approved") + .HasColumnType("boolean"); + + b.Property<DateTime?>("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<string>("MasterPasswordHash") + .HasColumnType("text"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("PublicKey") + .HasColumnType("text"); + + b.Property<string>("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<byte>("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property<string>("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<DateTime?>("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid?>("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("uuid"); + + b.Property<Guid>("GrantorId") + .HasColumnType("uuid"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("text"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<int>("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<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<string>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id")); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<Guid>("AaGuid") + .HasColumnType("uuid"); + + b.Property<int>("Counter") + .HasColumnType("integer"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property<string>("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property<string>("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("SupportsPrf") + .HasColumnType("boolean"); + + b.Property<string>("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte>("PlanType") + .HasColumnType("smallint"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<int>("Seats") + .HasColumnType("integer"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("InstallationId") + .HasColumnType("uuid"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<int>("AssignedSeats") + .HasColumnType("integer"); + + b.Property<Guid?>("ClientId") + .HasColumnType("uuid"); + + b.Property<string>("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<DateTime>("Created") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<decimal>("Total") + .HasColumnType("numeric"); + + b.Property<int>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<int?>("AllocatedSeats") + .HasColumnType("integer"); + + b.Property<byte>("PlanType") + .HasColumnType("smallint"); + + b.Property<Guid>("ProviderId") + .HasColumnType("uuid"); + + b.Property<int?>("PurchasedSeats") + .HasColumnType("integer"); + + b.Property<int?>("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property<string>("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property<DateTime?>("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property<long?>("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property<byte[]>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("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<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("GroupId") + .HasColumnType("uuid"); + + b.Property<bool>("HidePasswords") + .HasColumnType("boolean"); + + b.Property<bool>("Manage") + .HasColumnType("boolean"); + + b.Property<bool>("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("uuid"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property<bool>("HidePasswords") + .HasColumnType("boolean"); + + b.Property<bool>("Manage") + .HasColumnType("boolean"); + + b.Property<bool>("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<bool>("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property<string>("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property<string>("EncryptedUserKey") + .HasColumnType("text"); + + b.Property<string>("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("CipherId") + .HasColumnType("uuid"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("uuid"); + + b.Property<DateTime>("Date") + .HasColumnType("timestamp with time zone"); + + b.Property<byte?>("DeviceType") + .HasColumnType("smallint"); + + b.Property<string>("DomainName") + .HasColumnType("text"); + + b.Property<Guid?>("GroupId") + .HasColumnType("uuid"); + + b.Property<Guid?>("InstallationId") + .HasColumnType("uuid"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("uuid"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("uuid"); + + b.Property<Guid?>("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid?>("ProviderUserId") + .HasColumnType("uuid"); + + b.Property<Guid?>("SecretId") + .HasColumnType("uuid"); + + b.Property<Guid?>("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property<byte?>("SystemUser") + .HasColumnType("smallint"); + + b.Property<int>("Type") + .HasColumnType("integer"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("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<Guid>("GroupId") + .HasColumnType("uuid"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property<DateTime?>("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("Config") + .HasColumnType("text"); + + b.Property<bool>("Enabled") + .HasColumnType("boolean"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property<int>("JobRunCount") + .HasColumnType("integer"); + + b.Property<DateTime?>("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<DateTime?>("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<byte?>("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property<Guid?>("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid?>("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property<Guid>("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property<bool>("ToDelete") + .HasColumnType("boolean"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<bool>("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("Permissions") + .HasColumnType("text"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<short>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<int>("AccessCount") + .HasColumnType("integer"); + + b.Property<Guid?>("CipherId") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("Disabled") + .HasColumnType("boolean"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool?>("HideEmail") + .HasColumnType("boolean"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("integer"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("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<string>("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property<bool>("Active") + .HasColumnType("boolean"); + + b.Property<string>("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property<decimal>("Rate") + .HasColumnType("numeric"); + + b.Property<string>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<decimal>("Amount") + .HasColumnType("numeric"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("uuid"); + + b.Property<bool?>("Refunded") + .HasColumnType("boolean"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("numeric"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property<string>("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property<bool>("EmailVerified") + .HasColumnType("boolean"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("text"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property<int>("FailedLoginCount") + .HasColumnType("integer"); + + b.Property<bool>("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property<byte?>("Gateway") + .HasColumnType("smallint"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<byte>("Kdf") + .HasColumnType("smallint"); + + b.Property<int>("KdfIterations") + .HasColumnType("integer"); + + b.Property<int?>("KdfMemory") + .HasColumnType("integer"); + + b.Property<int?>("KdfParallelism") + .HasColumnType("integer"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<DateTime?>("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<bool>("Premium") + .HasColumnType("boolean"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("PrivateKey") + .HasColumnType("text"); + + b.Property<string>("PublicKey") + .HasColumnType("text"); + + b.Property<string>("ReferenceData") + .HasColumnType("text"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<long?>("Storage") + .HasColumnType("bigint"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("text"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property<bool>("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property<bool>("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("Body") + .HasColumnType("text"); + + b.Property<byte>("ClientType") + .HasColumnType("smallint"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("Global") + .HasColumnType("boolean"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte>("Priority") + .HasColumnType("smallint"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .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<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<Guid>("NotificationId") + .HasColumnType("uuid"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("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.SecretsManager.Models.AccessPolicy", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property<bool>("Read") + .HasColumnType("boolean"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property<DateTime?>("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<string>("Note") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("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.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("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.Vault.Models.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<string>("Attachments") + .HasColumnType("text"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Data") + .HasColumnType("text"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Favorites") + .HasColumnType("text"); + + b.Property<string>("Folders") + .HasColumnType("text"); + + b.Property<string>("Key") + .HasColumnType("text"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<byte?>("Reprompt") + .HasColumnType("smallint"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("uuid"); + + b.Property<Guid?>("CipherId") + .HasColumnType("uuid"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("uuid"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.Property<byte>("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<Guid>("ProjectsId") + .HasColumnType("uuid"); + + b.Property<Guid>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.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.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.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.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"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.cs b/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.cs new file mode 100644 index 0000000000..0fa41d6d95 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20241219035734_AlterUser_AddVerifyDevice.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// <inheritdoc /> +public partial class AlterUser_AddVerifyDevice : Migration +{ + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<bool>( + name: "VerifyDevices", + table: "User", + type: "boolean", + nullable: false, + defaultValue: true); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VerifyDevices", + table: "User"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 04636ab15d..971ba96310 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1668,6 +1668,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property<bool>("UsesKeyConnector") .HasColumnType("boolean"); + b.Property<bool>("VerifyDevices") + .HasColumnType("boolean"); + b.HasKey("Id"); b.HasIndex("Email") diff --git a/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.Designer.cs b/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.Designer.cs new file mode 100644 index 0000000000..ef2eb70530 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.Designer.cs @@ -0,0 +1,2986 @@ +// <auto-generated /> +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("20241219035748_AlterUser_AddVerifyDevice")] + partial class AlterUser_AddVerifyDevice + { + /// <inheritdoc /> + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<bool>("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property<string>("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property<byte?>("Gateway") + .HasColumnType("INTEGER"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property<bool>("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property<bool>("LimitCollectionCreationDeletion") + .HasColumnType("INTEGER"); + + b.Property<bool>("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property<int?>("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property<int?>("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property<short?>("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property<string>("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<byte>("PlanType") + .HasColumnType("INTEGER"); + + b.Property<string>("PrivateKey") + .HasColumnType("TEXT"); + + b.Property<string>("PublicKey") + .HasColumnType("TEXT"); + + b.Property<string>("ReferenceData") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<int?>("Seats") + .HasColumnType("INTEGER"); + + b.Property<bool>("SelfHost") + .HasColumnType("INTEGER"); + + b.Property<int?>("SmSeats") + .HasColumnType("INTEGER"); + + b.Property<int?>("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property<byte>("Status") + .HasColumnType("INTEGER"); + + b.Property<long?>("Storage") + .HasColumnType("INTEGER"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property<bool>("Use2fa") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseApi") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseEvents") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseGroups") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property<bool>("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property<bool>("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseScim") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseSso") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseTotp") + .HasColumnType("INTEGER"); + + b.Property<bool>("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.Policy", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("BillingEmail") + .HasColumnType("TEXT"); + + b.Property<string>("BillingPhone") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessName") + .HasColumnType("TEXT"); + + b.Property<string>("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<byte?>("Gateway") + .HasColumnType("INTEGER"); + + b.Property<string>("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property<string>("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Status") + .HasColumnType("INTEGER"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<bool>("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<string>("Permissions") + .HasColumnType("TEXT"); + + b.Property<Guid>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Status") + .HasColumnType("INTEGER"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property<bool?>("Approved") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<string>("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<string>("PublicKey") + .HasColumnType("TEXT"); + + b.Property<string>("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<byte>("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property<string>("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ResponseDate") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<Guid?>("GranteeId") + .HasColumnType("TEXT"); + + b.Property<Guid>("GrantorId") + .HasColumnType("TEXT"); + + b.Property<string>("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Status") + .HasColumnType("INTEGER"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<int>("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<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property<string>("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property<string>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("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<long>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<Guid>("AaGuid") + .HasColumnType("TEXT"); + + b.Property<int>("Counter") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<bool>("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property<string>("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property<string>("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<int?>("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<byte>("PlanType") + .HasColumnType("INTEGER"); + + b.Property<Guid>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<int>("Seats") + .HasColumnType("INTEGER"); + + b.Property<byte>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<Guid>("InstallationId") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("ClientId") + .HasColumnType("TEXT"); + + b.Property<string>("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<string>("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<Guid>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<decimal>("Total") + .HasColumnType("TEXT"); + + b.Property<int>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<int?>("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property<byte>("PlanType") + .HasColumnType("INTEGER"); + + b.Property<Guid>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<int?>("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property<int?>("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property<string>("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property<DateTime>("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property<long?>("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property<byte[]>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("TEXT"); + + b.Property<Guid>("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<Guid>("CollectionId") + .HasColumnType("TEXT"); + + b.Property<Guid>("GroupId") + .HasColumnType("TEXT"); + + b.Property<bool>("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property<bool>("Manage") + .HasColumnType("INTEGER"); + + b.Property<bool>("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property<Guid>("CollectionId") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property<bool>("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property<bool>("Manage") + .HasColumnType("INTEGER"); + + b.Property<bool>("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<bool>("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property<string>("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ActingUserId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CipherId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("Date") + .HasColumnType("TEXT"); + + b.Property<byte?>("DeviceType") + .HasColumnType("INTEGER"); + + b.Property<string>("DomainName") + .HasColumnType("TEXT"); + + b.Property<Guid?>("GroupId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("InstallationId") + .HasColumnType("TEXT"); + + b.Property<string>("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("PolicyId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("SecretId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property<byte?>("SystemUser") + .HasColumnType("INTEGER"); + + b.Property<int>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property<Guid>("GroupId") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<string>("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("Config") + .HasColumnType("TEXT"); + + b.Property<bool>("Enabled") + .HasColumnType("INTEGER"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<int>("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("NextRunDate") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<string>("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<DateTime?>("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property<string>("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<byte?>("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid?>("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property<Guid>("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property<bool>("ToDelete") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<bool>("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<string>("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<string>("Permissions") + .HasColumnType("TEXT"); + + b.Property<string>("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<short>("Status") + .HasColumnType("INTEGER"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AccessCount") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("CipherId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .HasColumnType("TEXT"); + + b.Property<DateTime>("DeletionDate") + .HasColumnType("TEXT"); + + b.Property<bool>("Disabled") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property<bool?>("HideEmail") + .HasColumnType("INTEGER"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<int?>("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<string>("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("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<string>("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property<bool>("Active") + .HasColumnType("INTEGER"); + + b.Property<string>("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property<decimal>("Rate") + .HasColumnType("TEXT"); + + b.Property<string>("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<decimal>("Amount") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property<byte?>("Gateway") + .HasColumnType("INTEGER"); + + b.Property<string>("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<byte?>("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("ProviderId") + .HasColumnType("TEXT"); + + b.Property<bool?>("Refunded") + .HasColumnType("INTEGER"); + + b.Property<decimal?>("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property<string>("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<bool>("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property<string>("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property<string>("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property<int>("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property<bool>("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property<byte?>("Gateway") + .HasColumnType("INTEGER"); + + b.Property<string>("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<string>("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<byte>("Kdf") + .HasColumnType("INTEGER"); + + b.Property<int>("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property<int?>("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property<int?>("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property<string>("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property<string>("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property<string>("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<short?>("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<bool>("Premium") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property<string>("PrivateKey") + .HasColumnType("TEXT"); + + b.Property<string>("PublicKey") + .HasColumnType("TEXT"); + + b.Property<string>("ReferenceData") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property<long?>("Storage") + .HasColumnType("INTEGER"); + + b.Property<string>("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property<string>("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<bool>("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property<bool>("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("Body") + .HasColumnType("TEXT"); + + b.Property<byte>("ClientType") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<bool>("Global") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<byte>("Priority") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .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<Guid>("UserId") + .HasColumnType("TEXT"); + + b.Property<Guid>("NotificationId") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property<bool>("Read") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<bool>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property<DateTime?>("ExpireAt") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<string>("Note") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("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.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<string>("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.Vault.Models.Cipher", b => + { + b.Property<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("Attachments") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("DeletedDate") + .HasColumnType("TEXT"); + + b.Property<string>("Favorites") + .HasColumnType("TEXT"); + + b.Property<string>("Folders") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .HasColumnType("TEXT"); + + b.Property<Guid?>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<byte?>("Reprompt") + .HasColumnType("INTEGER"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Type") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<Guid>("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<Guid>("Id") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CipherId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("CreationDate") + .HasColumnType("TEXT"); + + b.Property<Guid>("OrganizationId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("RevisionDate") + .HasColumnType("TEXT"); + + b.Property<byte>("Status") + .HasColumnType("INTEGER"); + + b.Property<byte>("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<Guid>("ProjectsId") + .HasColumnType("TEXT"); + + b.Property<Guid>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property<Guid?>("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<Guid?>("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property<Guid?>("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<Guid?>("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property<Guid?>("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.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.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.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.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"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.cs b/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.cs new file mode 100644 index 0000000000..da6fdc6f32 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20241219035748_AlterUser_AddVerifyDevice.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// <inheritdoc /> +public partial class AlterUser_AddVerifyDevice : Migration +{ + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<bool>( + name: "VerifyDevices", + table: "User", + type: "INTEGER", + nullable: false, + defaultValue: true); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "VerifyDevices", + table: "User"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index d813ebcbcc..d9be32398b 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1651,6 +1651,9 @@ namespace Bit.SqliteMigrations.Migrations b.Property<bool>("UsesKeyConnector") .HasColumnType("INTEGER"); + b.Property<bool>("VerifyDevices") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("Email")