diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 536ca306f8..0000000000 --- a/.github/renovate.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>bitwarden/renovate-config"], - "enabledManagers": [ - "dockerfile", - "docker-compose", - "github-actions", - "npm", - "nuget" - ], - "packageRules": [ - { - "groupName": "dockerfile minor", - "matchManagers": ["dockerfile"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "groupName": "docker-compose minor", - "matchManagers": ["docker-compose"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "groupName": "gh minor", - "matchManagers": ["github-actions"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["github-actions", "dockerfile", "docker-compose"], - "commitMessagePrefix": "[deps] BRE:" - }, - { - "matchPackageNames": ["DnsClient"], - "description": "Admin Console owned dependencies", - "commitMessagePrefix": "[deps] AC:", - "reviewers": ["team:team-admin-console-dev"] - }, - { - "matchFileNames": ["src/Admin/package.json", "src/Sso/package.json"], - "description": "Admin & SSO npm packages", - "commitMessagePrefix": "[deps] Auth:", - "reviewers": ["team:team-auth-dev"] - }, - { - "matchPackageNames": [ - "Azure.Extensions.AspNetCore.DataProtection.Blobs", - "DuoUniversal", - "Fido2.AspNet", - "Duende.IdentityServer", - "Microsoft.Extensions.Identity.Stores", - "Otp.NET", - "Sustainsys.Saml2.AspNetCore2", - "YubicoDotNetClient" - ], - "description": "Auth owned dependencies", - "commitMessagePrefix": "[deps] Auth:", - "reviewers": ["team:team-auth-dev"] - }, - { - "matchPackageNames": [ - "AutoFixture.AutoNSubstitute", - "AutoFixture.Xunit2", - "BenchmarkDotNet", - "BitPay.Light", - "Braintree", - "coverlet.collector", - "CsvHelper", - "FluentAssertions", - "Kralizek.AutoFixture.Extensions.MockHttp", - "Microsoft.AspNetCore.Mvc.Testing", - "Microsoft.Extensions.Logging", - "Microsoft.Extensions.Logging.Console", - "Newtonsoft.Json", - "NSubstitute", - "Sentry.Serilog", - "Serilog.AspNetCore", - "Serilog.Extensions.Logging", - "Serilog.Extensions.Logging.File", - "Serilog.Sinks.AzureCosmosDB", - "Serilog.Sinks.SyslogMessages", - "Stripe.net", - "Swashbuckle.AspNetCore", - "Swashbuckle.AspNetCore.SwaggerGen", - "xunit", - "xunit.runner.visualstudio" - ], - "description": "Billing owned dependencies", - "commitMessagePrefix": "[deps] Billing:", - "reviewers": ["team:team-billing-dev"] - }, - { - "matchPackagePatterns": ["^Microsoft.Extensions.Logging"], - "groupName": "Microsoft.Extensions.Logging", - "description": "Group Microsoft.Extensions.Logging to exclude them from the dotnet monorepo preset" - }, - { - "matchPackageNames": [ - "Dapper", - "dbup-sqlserver", - "dotnet-ef", - "linq2db.EntityFrameworkCore", - "Microsoft.Azure.Cosmos", - "Microsoft.Data.SqlClient", - "Microsoft.EntityFrameworkCore.Design", - "Microsoft.EntityFrameworkCore.InMemory", - "Microsoft.EntityFrameworkCore.Relational", - "Microsoft.EntityFrameworkCore.Sqlite", - "Microsoft.EntityFrameworkCore.SqlServer", - "Microsoft.Extensions.Caching.Cosmos", - "Microsoft.Extensions.Caching.SqlServer", - "Microsoft.Extensions.Caching.StackExchangeRedis", - "Npgsql.EntityFrameworkCore.PostgreSQL", - "Pomelo.EntityFrameworkCore.MySql" - ], - "description": "DbOps owned dependencies", - "commitMessagePrefix": "[deps] DbOps:", - "reviewers": ["team:dept-dbops"] - }, - { - "matchPackageNames": ["CommandDotNet", "YamlDotNet"], - "description": "DevOps owned dependencies", - "commitMessagePrefix": "[deps] BRE:", - "reviewers": ["team:dept-bre"] - }, - { - "matchPackageNames": [ - "AspNetCoreRateLimit", - "AspNetCoreRateLimit.Redis", - "Azure.Data.Tables", - "Azure.Messaging.EventGrid", - "Azure.Messaging.ServiceBus", - "Azure.Storage.Blobs", - "Azure.Storage.Queues", - "Microsoft.AspNetCore.Authentication.JwtBearer", - "Microsoft.AspNetCore.Http", - "Quartz" - ], - "description": "Platform owned dependencies", - "commitMessagePrefix": "[deps] Platform:", - "reviewers": ["team:team-platform-dev"] - }, - { - "matchPackagePatterns": ["EntityFrameworkCore", "^dotnet-ef"], - "groupName": "EntityFrameworkCore", - "description": "Group EntityFrameworkCore to exclude them from the dotnet monorepo preset" - }, - { - "matchPackageNames": [ - "AutoMapper.Extensions.Microsoft.DependencyInjection", - "AWSSDK.SimpleEmail", - "AWSSDK.SQS", - "Handlebars.Net", - "LaunchDarkly.ServerSdk", - "MailKit", - "Microsoft.AspNetCore.SignalR.Protocols.MessagePack", - "Microsoft.AspNetCore.SignalR.StackExchangeRedis", - "Microsoft.Azure.NotificationHubs", - "Microsoft.Extensions.Configuration.EnvironmentVariables", - "Microsoft.Extensions.Configuration.UserSecrets", - "Microsoft.Extensions.Configuration", - "Microsoft.Extensions.DependencyInjection.Abstractions", - "Microsoft.Extensions.DependencyInjection", - "SendGrid" - ], - "description": "Tools owned dependencies", - "commitMessagePrefix": "[deps] Tools:", - "reviewers": ["team:team-tools-dev"] - }, - { - "matchPackagePatterns": ["^Microsoft.AspNetCore.SignalR"], - "groupName": "SignalR", - "description": "Group SignalR to exclude them from the dotnet monorepo preset" - }, - { - "matchPackagePatterns": ["^Microsoft.Extensions.Configuration"], - "groupName": "Microsoft.Extensions.Configuration", - "description": "Group Microsoft.Extensions.Configuration to exclude them from the dotnet monorepo preset" - }, - { - "matchPackagePatterns": ["^Microsoft.Extensions.DependencyInjection"], - "groupName": "Microsoft.Extensions.DependencyInjection", - "description": "Group Microsoft.Extensions.DependencyInjection to exclude them from the dotnet monorepo preset" - }, - { - "matchPackageNames": [ - "AngleSharp", - "AspNetCore.HealthChecks.AzureServiceBus", - "AspNetCore.HealthChecks.AzureStorage", - "AspNetCore.HealthChecks.Network", - "AspNetCore.HealthChecks.Redis", - "AspNetCore.HealthChecks.SendGrid", - "AspNetCore.HealthChecks.SqlServer", - "AspNetCore.HealthChecks.Uris" - ], - "description": "Vault owned dependencies", - "commitMessagePrefix": "[deps] Vault:", - "reviewers": ["team:team-vault-dev"] - } - ], - "ignoreDeps": ["dotnet-sdk"] -} diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 0000000000..4722307d10 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,199 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: ["github>bitwarden/renovate-config"], // Extends our default configuration for pinned dependencies + enabledManagers: [ + "dockerfile", + "docker-compose", + "github-actions", + "npm", + "nuget", + ], + packageRules: [ + { + groupName: "dockerfile minor", + matchManagers: ["dockerfile"], + matchUpdateTypes: ["minor"], + }, + { + groupName: "docker-compose minor", + matchManagers: ["docker-compose"], + matchUpdateTypes: ["minor"], + }, + { + groupName: "github-action minor", + matchManagers: ["github-actions"], + matchUpdateTypes: ["minor"], + }, + { + matchManagers: ["dockerfile", "docker-compose"], + commitMessagePrefix: "[deps] BRE:", + }, + { + matchPackageNames: ["DnsClient"], + description: "Admin Console owned dependencies", + commitMessagePrefix: "[deps] AC:", + reviewers: ["team:team-admin-console-dev"], + }, + { + matchFileNames: ["src/Admin/package.json", "src/Sso/package.json"], + description: "Admin & SSO npm packages", + commitMessagePrefix: "[deps] Auth:", + reviewers: ["team:team-auth-dev"], + }, + { + matchPackageNames: [ + "Azure.Extensions.AspNetCore.DataProtection.Blobs", + "DuoUniversal", + "Fido2.AspNet", + "Duende.IdentityServer", + "Microsoft.Extensions.Identity.Stores", + "Otp.NET", + "Sustainsys.Saml2.AspNetCore2", + "YubicoDotNetClient", + ], + description: "Auth owned dependencies", + commitMessagePrefix: "[deps] Auth:", + reviewers: ["team:team-auth-dev"], + }, + { + matchPackageNames: [ + "AutoFixture.AutoNSubstitute", + "AutoFixture.Xunit2", + "BenchmarkDotNet", + "BitPay.Light", + "Braintree", + "coverlet.collector", + "CsvHelper", + "Kralizek.AutoFixture.Extensions.MockHttp", + "Microsoft.AspNetCore.Mvc.Testing", + "Microsoft.Extensions.Logging", + "Microsoft.Extensions.Logging.Console", + "Newtonsoft.Json", + "NSubstitute", + "Sentry.Serilog", + "Serilog.AspNetCore", + "Serilog.Extensions.Logging", + "Serilog.Extensions.Logging.File", + "Serilog.Sinks.AzureCosmosDB", + "Serilog.Sinks.SyslogMessages", + "Stripe.net", + "Swashbuckle.AspNetCore", + "Swashbuckle.AspNetCore.SwaggerGen", + "xunit", + "xunit.runner.visualstudio", + ], + description: "Billing owned dependencies", + commitMessagePrefix: "[deps] Billing:", + reviewers: ["team:team-billing-dev"], + }, + { + matchPackagePatterns: ["^Microsoft.Extensions.Logging"], + groupName: "Microsoft.Extensions.Logging", + description: "Group Microsoft.Extensions.Logging to exclude them from the dotnet monorepo preset", + }, + { + matchPackageNames: [ + "Dapper", + "dbup-sqlserver", + "dotnet-ef", + "linq2db.EntityFrameworkCore", + "Microsoft.Azure.Cosmos", + "Microsoft.Data.SqlClient", + "Microsoft.EntityFrameworkCore.Design", + "Microsoft.EntityFrameworkCore.InMemory", + "Microsoft.EntityFrameworkCore.Relational", + "Microsoft.EntityFrameworkCore.Sqlite", + "Microsoft.EntityFrameworkCore.SqlServer", + "Microsoft.Extensions.Caching.Cosmos", + "Microsoft.Extensions.Caching.SqlServer", + "Microsoft.Extensions.Caching.StackExchangeRedis", + "Npgsql.EntityFrameworkCore.PostgreSQL", + "Pomelo.EntityFrameworkCore.MySql", + ], + description: "DbOps owned dependencies", + commitMessagePrefix: "[deps] DbOps:", + reviewers: ["team:dept-dbops"], + }, + { + matchPackageNames: ["CommandDotNet", "YamlDotNet"], + description: "DevOps owned dependencies", + commitMessagePrefix: "[deps] BRE:", + reviewers: ["team:dept-bre"], + }, + { + matchPackageNames: [ + "AspNetCoreRateLimit", + "AspNetCoreRateLimit.Redis", + "Azure.Data.Tables", + "Azure.Messaging.EventGrid", + "Azure.Messaging.ServiceBus", + "Azure.Storage.Blobs", + "Azure.Storage.Queues", + "Microsoft.AspNetCore.Authentication.JwtBearer", + "Microsoft.AspNetCore.Http", + "Quartz", + ], + description: "Platform owned dependencies", + commitMessagePrefix: "[deps] Platform:", + reviewers: ["team:team-platform-dev"], + }, + { + matchPackagePatterns: ["EntityFrameworkCore", "^dotnet-ef"], + groupName: "EntityFrameworkCore", + description: "Group EntityFrameworkCore to exclude them from the dotnet monorepo preset", + }, + { + matchPackageNames: [ + "AutoMapper.Extensions.Microsoft.DependencyInjection", + "AWSSDK.SimpleEmail", + "AWSSDK.SQS", + "Handlebars.Net", + "LaunchDarkly.ServerSdk", + "MailKit", + "Microsoft.AspNetCore.SignalR.Protocols.MessagePack", + "Microsoft.AspNetCore.SignalR.StackExchangeRedis", + "Microsoft.Azure.NotificationHubs", + "Microsoft.Extensions.Configuration.EnvironmentVariables", + "Microsoft.Extensions.Configuration.UserSecrets", + "Microsoft.Extensions.Configuration", + "Microsoft.Extensions.DependencyInjection.Abstractions", + "Microsoft.Extensions.DependencyInjection", + "SendGrid", + ], + description: "Tools owned dependencies", + commitMessagePrefix: "[deps] Tools:", + reviewers: ["team:team-tools-dev"], + }, + { + matchPackagePatterns: ["^Microsoft.AspNetCore.SignalR"], + groupName: "SignalR", + description: "Group SignalR to exclude them from the dotnet monorepo preset", + }, + { + matchPackagePatterns: ["^Microsoft.Extensions.Configuration"], + groupName: "Microsoft.Extensions.Configuration", + description: "Group Microsoft.Extensions.Configuration to exclude them from the dotnet monorepo preset", + }, + { + matchPackagePatterns: ["^Microsoft.Extensions.DependencyInjection"], + groupName: "Microsoft.Extensions.DependencyInjection", + description: "Group Microsoft.Extensions.DependencyInjection to exclude them from the dotnet monorepo preset", + }, + { + matchPackageNames: [ + "AngleSharp", + "AspNetCore.HealthChecks.AzureServiceBus", + "AspNetCore.HealthChecks.AzureStorage", + "AspNetCore.HealthChecks.Network", + "AspNetCore.HealthChecks.Redis", + "AspNetCore.HealthChecks.SendGrid", + "AspNetCore.HealthChecks.SqlServer", + "AspNetCore.HealthChecks.Uris", + ], + description: "Vault owned dependencies", + commitMessagePrefix: "[deps] Vault:", + reviewers: ["team:team-vault-dev"], + }, + ], + ignoreDeps: ["dotnet-sdk"], +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 510ce3318b..3b96eeb468 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,7 @@ jobs: ls -atlh ../../../ - name: Upload project artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: ${{ matrix.base_path }}/${{ matrix.project_name }} file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile @@ -314,7 +314,7 @@ jobs: output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} @@ -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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: internal.json path: internal.json if-no-files-found: error - name: Upload Identity Swagger artifact - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: identity.json path: identity.json @@ -533,7 +533,7 @@ jobs: - name: Upload project artifact for Windows if: ${{ contains(matrix.target, 'win') == true }} - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.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 7fcf864866..ce8cb8e467 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@b2d44bb453e13c11fd1a6ada7b1e5f9fb0ace629 # v2.0.1 + uses: launchdarkly/find-code-references-in-pull-request@30f4c4ab2949bbf258b797ced2fbf6dea34df9ce # v2.1.0 with: project-key: default environment-key: dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0809ff833f..f749d2e4f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: - name: Create release if: ${{ inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 + uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 with: artifacts: "docker-stub-US.zip, docker-stub-US-sha256.txt, diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index e40ff07148..1fa5c9587c 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0 + uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8 with: sarif_file: cx_result.sarif @@ -80,12 +80,11 @@ jobs: - name: Scan with SonarCloud env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" \ /d:sonar.test.inclusions=test/,bitwarden_license/test/ \ /d:sonar.exclusions=test/,bitwarden_license/test/ \ /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ - /d:sonar.host.url="https://sonarcloud.io" + /d:sonar.host.url="https://sonarcloud.io" ${{ contains(github.event_name, 'pull_request') && format('/d:sonar.pullrequest.key={0}', github.event.pull_request.number) || '' }} dotnet build dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index f8a25288f2..9420f71cb3 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 with: stale-issue-label: "needs-reply" stale-pr-label: "needs-changes" diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index 0d6361eca8..a668ddb37d 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -17,6 +17,7 @@ on: - "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer - "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests + - "src/**/Entities/**/*.cs" # Database entity definitions pull_request: paths: - ".github/workflows/test-database.yml" # This file @@ -28,6 +29,7 @@ on: - "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer - "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests + - "src/**/Entities/**/*.cs" # Database entity definitions jobs: check-test-secrets: @@ -200,7 +202,7 @@ jobs: shell: pwsh - name: Upload DACPAC - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: sql.dacpac path: Sql.dacpac @@ -226,7 +228,7 @@ jobs: shell: pwsh - name: Report validation results - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: report.xml path: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cc31f5c2f..817547fc65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,6 +78,3 @@ jobs: - name: Upload to codecov.io 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/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..3282b1c509 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,18 @@ +{ + "recommendations": [ + "nick-rudenko.back-n-forth", + "streetsidesoftware.code-spell-checker", + "MS-vsliveshare.vsliveshare", + + "mhutchie.git-graph", + "donjayamanne.githistory", + "eamodio.gitlens", + + "jakebathman.mysql-syntax", + "ckolkman.vscode-postgres", + + "ms-dotnettools.csharp", + "formulahendry.dotnet-test-explorer", + "adrianwilczynski.user-secrets" + ] +} diff --git a/Directory.Build.props b/Directory.Build.props index 1467c0822c..c797513c63 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,11 +3,17 @@ net8.0 - 2025.1.2 + 2025.2.0 Bit.$(MSBuildProjectName) enable false + + true + annotations + + + true + $(WarningsNotAsErrors);CS8604 diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index be74986432..ecd6a88517 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -266,8 +266,18 @@ public class AccountsController : Controller throw new UnauthorizedAccessException(); } + try + { + user = model.ToUser(user); + } + catch (Exception e) + { + ModelState.AddModelError(string.Empty, e.Message); + throw new BadRequestException(ModelState); + } + var result = await _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync( - model.ToUser(user), + user, model.MasterPasswordHash, model.Key, model.OrgIdentifier); @@ -977,7 +987,6 @@ public class AccountsController : Controller await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret); } - [RequireFeature(FeatureFlagKeys.NewDeviceVerification)] [HttpPost("verify-devices")] [HttpPut("verify-devices")] public async Task SetUserVerifyDevicesAsync([FromBody] SetVerifyDevicesRequestModel request) diff --git a/src/Api/Auth/Controllers/TwoFactorController.cs b/src/Api/Auth/Controllers/TwoFactorController.cs index 2714b9aba3..c7d39f64b0 100644 --- a/src/Api/Auth/Controllers/TwoFactorController.cs +++ b/src/Api/Auth/Controllers/TwoFactorController.cs @@ -304,7 +304,7 @@ public class TwoFactorController : Controller if (user != null) { - // check if 2FA email is from passwordless + // Check if 2FA email is from Passwordless. if (!string.IsNullOrEmpty(requestModel.AuthRequestAccessCode)) { if (await _verifyAuthRequestCommand @@ -317,17 +317,14 @@ public class TwoFactorController : Controller } else if (!string.IsNullOrEmpty(requestModel.SsoEmail2FaSessionToken)) { - if (this.ValidateSsoEmail2FaToken(requestModel.SsoEmail2FaSessionToken, user)) + if (ValidateSsoEmail2FaToken(requestModel.SsoEmail2FaSessionToken, user)) { await _userService.SendTwoFactorEmailAsync(user); return; } - else - { - await this.ThrowDelayedBadRequestExceptionAsync( - "Cannot send two-factor email: a valid, non-expired SSO Email 2FA Session token is required to send 2FA emails.", - 2000); - } + + await ThrowDelayedBadRequestExceptionAsync( + "Cannot send two-factor email: a valid, non-expired SSO Email 2FA Session token is required to send 2FA emails."); } else if (await _userService.VerifySecretAsync(user, requestModel.Secret)) { @@ -336,8 +333,7 @@ public class TwoFactorController : Controller } } - await this.ThrowDelayedBadRequestExceptionAsync( - "Cannot send two-factor email.", 2000); + await ThrowDelayedBadRequestExceptionAsync("Cannot send two-factor email."); } [HttpPut("email")] @@ -374,7 +370,7 @@ public class TwoFactorController : Controller public async Task PutOrganizationDisable(string id, [FromBody] TwoFactorProviderRequestModel model) { - var user = await CheckAsync(model, false); + await CheckAsync(model, false); var orgIdGuid = new Guid(id); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -401,6 +397,10 @@ public class TwoFactorController : Controller return response; } + /// + /// To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175. + /// + [Obsolete("Two Factor recovery is handled in the TwoFactorAuthenticationValidator.")] [HttpPost("recover")] [AllowAnonymous] public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model) @@ -463,10 +463,8 @@ public class TwoFactorController : Controller await Task.Delay(2000); throw new BadRequestException(name, $"{name} is invalid."); } - else - { - await Task.Delay(500); - } + + await Task.Delay(500); } private bool ValidateSsoEmail2FaToken(string ssoEmail2FaSessionToken, User user) diff --git a/src/Api/Auth/Models/Response/AuthRequestResponseModel.cs b/src/Api/Auth/Models/Response/AuthRequestResponseModel.cs index 0234fc333a..50f7f5a3e7 100644 --- a/src/Api/Auth/Models/Response/AuthRequestResponseModel.cs +++ b/src/Api/Auth/Models/Response/AuthRequestResponseModel.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Reflection; using Bit.Core.Auth.Entities; +using Bit.Core.Enums; using Bit.Core.Models.Api; namespace Bit.Api.Auth.Models.Response; @@ -17,6 +18,8 @@ public class AuthRequestResponseModel : ResponseModel Id = authRequest.Id; PublicKey = authRequest.PublicKey; + RequestDeviceIdentifier = authRequest.RequestDeviceIdentifier; + RequestDeviceTypeValue = authRequest.RequestDeviceType; RequestDeviceType = authRequest.RequestDeviceType.GetType().GetMember(authRequest.RequestDeviceType.ToString()) .FirstOrDefault()?.GetCustomAttribute()?.GetName(); RequestIpAddress = authRequest.RequestIpAddress; @@ -30,6 +33,8 @@ public class AuthRequestResponseModel : ResponseModel public Guid Id { get; set; } public string PublicKey { get; set; } + public string RequestDeviceIdentifier { get; set; } + public DeviceType RequestDeviceTypeValue { get; set; } public string RequestDeviceType { get; set; } public string RequestIpAddress { get; set; } public string Key { get; set; } diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 1c0cfd9388..3ceeaf3c47 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -2,7 +2,7 @@ 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; using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; using Bit.Core.Context; @@ -18,7 +18,6 @@ namespace Bit.Api.Billing.Controllers; [Authorize("Application")] public class OrganizationBillingController( ICurrentContext currentContext, - IFeatureService featureService, IOrganizationBillingService organizationBillingService, IOrganizationRepository organizationRepository, IPaymentService paymentService, @@ -139,11 +138,6 @@ public class OrganizationBillingController( [HttpGet("payment-method")] public async Task GetPaymentMethodAsync([FromRoute] Guid organizationId) { - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -168,11 +162,6 @@ public class OrganizationBillingController( [FromRoute] Guid organizationId, [FromBody] UpdatePaymentMethodRequestBody requestBody) { - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -199,11 +188,6 @@ public class OrganizationBillingController( [FromRoute] Guid organizationId, [FromBody] VerifyBankAccountRequestBody requestBody) { - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -229,11 +213,6 @@ public class OrganizationBillingController( [HttpGet("tax-information")] public async Task GetTaxInformationAsync([FromRoute] Guid organizationId) { - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -258,11 +237,6 @@ public class OrganizationBillingController( [FromRoute] Guid organizationId, [FromBody] TaxInformationRequestBody requestBody) { - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -292,11 +266,6 @@ public class OrganizationBillingController( throw new UnauthorizedAccessException(); } - if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - return Error.NotFound(); - } - if (!await currentContext.EditPaymentMethods(organizationId)) { return Error.Unauthorized(); @@ -310,7 +279,18 @@ public class OrganizationBillingController( } var organizationSignup = model.ToOrganizationSignup(user); var sale = OrganizationSale.From(organization, organizationSignup); + var plan = StaticStore.GetPlan(model.PlanType); + sale.Organization.PlanType = plan.Type; + sale.Organization.Plan = plan.Name; + sale.SubscriptionSetup.SkipTrial = true; await organizationBillingService.Finalize(sale); + var org = await organizationRepository.GetByIdAsync(organizationId); + if (organizationSignup.PaymentMethodType != null) + { + var paymentSource = new TokenizedPaymentSource(organizationSignup.PaymentMethodType.Value, organizationSignup.PaymentToken); + var taxInformation = TaxInformation.From(organizationSignup.TaxInfo); + await organizationBillingService.UpdatePaymentMethod(org, paymentSource, taxInformation); + } return TypedResults.Ok(); } diff --git a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs index a7a4c39054..42263aa88b 100644 --- a/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs +++ b/src/Api/Billing/Controllers/OrganizationSponsorshipsController.cs @@ -1,6 +1,5 @@ using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response.Organizations; -using Bit.Core; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces; using Bit.Core.AdminConsole.Repositories; @@ -107,7 +106,7 @@ public class OrganizationSponsorshipsController : Controller { var isFreeFamilyPolicyEnabled = false; var (isValid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email); - if (isValid && _featureService.IsEnabled(FeatureFlagKeys.DisableFreeFamiliesSponsorship) && sponsorship.SponsoringOrganizationId.HasValue) + if (isValid && sponsorship.SponsoringOrganizationId.HasValue) { var policy = await _policyRepository.GetByOrganizationIdTypeAsync(sponsorship.SponsoringOrganizationId.Value, PolicyType.FreeFamiliesSponsorshipPolicy); diff --git a/src/Api/Billing/Models/Requests/AddExistingOrganizationRequestBody.cs b/src/Api/Billing/Models/Requests/AddExistingOrganizationRequestBody.cs new file mode 100644 index 0000000000..c2add17793 --- /dev/null +++ b/src/Api/Billing/Models/Requests/AddExistingOrganizationRequestBody.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Billing.Models.Requests; + +public class AddExistingOrganizationRequestBody +{ + [Required(ErrorMessage = "'key' must be provided")] + public string Key { get; set; } + + [Required(ErrorMessage = "'organizationId' must be provided")] + public Guid OrganizationId { get; set; } +} diff --git a/src/Api/Controllers/PlansController.cs b/src/Api/Controllers/PlansController.cs index 80aca2d827..c2ee494322 100644 --- a/src/Api/Controllers/PlansController.cs +++ b/src/Api/Controllers/PlansController.cs @@ -1,5 +1,4 @@ using Bit.Api.Models.Response; -using Bit.Core.Repositories; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -10,10 +9,6 @@ namespace Bit.Api.Controllers; [Authorize("Web")] public class PlansController : Controller { - private readonly ITaxRateRepository _taxRateRepository; - - public PlansController(ITaxRateRepository taxRateRepository) => _taxRateRepository = taxRateRepository; - [HttpGet("")] [AllowAnonymous] public ListResponseModel Get() @@ -21,12 +16,4 @@ public class PlansController : Controller var responses = StaticStore.Plans.Select(plan => new PlanResponseModel(plan)); return new ListResponseModel(responses); } - - [HttpGet("sales-tax-rates")] - public async Task> GetTaxRates() - { - var data = await _taxRateRepository.GetAllActiveAsync(); - var responses = data.Select(x => new TaxRateResponseModel(x)); - return new ListResponseModel(responses); - } } diff --git a/src/Api/Models/Public/Response/ListResponseModel.cs b/src/Api/Models/Public/Response/ListResponseModel.cs index 0865be3e8e..a55d6f62bb 100644 --- a/src/Api/Models/Public/Response/ListResponseModel.cs +++ b/src/Api/Models/Public/Response/ListResponseModel.cs @@ -4,10 +4,9 @@ namespace Bit.Api.Models.Public.Response; public class ListResponseModel : IResponseModel where T : IResponseModel { - public ListResponseModel(IEnumerable data, string continuationToken = null) + public ListResponseModel(IEnumerable data) { Data = data; - ContinuationToken = continuationToken; } /// @@ -21,8 +20,4 @@ public class ListResponseModel : IResponseModel where T : IResponseModel /// [Required] public IEnumerable Data { get; set; } - /// - /// A cursor for use in pagination. - /// - public string ContinuationToken { get; set; } } diff --git a/src/Api/Models/Public/Response/PagedListResponseModel.cs b/src/Api/Models/Public/Response/PagedListResponseModel.cs new file mode 100644 index 0000000000..b0f25cb4f8 --- /dev/null +++ b/src/Api/Models/Public/Response/PagedListResponseModel.cs @@ -0,0 +1,10 @@ +namespace Bit.Api.Models.Public.Response; + +public class PagedListResponseModel(IEnumerable data, string continuationToken) : ListResponseModel(data) + where T : IResponseModel +{ + /// + /// A cursor for use in pagination. + /// + public string ContinuationToken { get; set; } = continuationToken; +} diff --git a/src/Api/Models/Request/Organizations/OrganizationCollectionManagementUpdateRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationCollectionManagementUpdateRequestModel.cs index 94f842ca1e..829840c896 100644 --- a/src/Api/Models/Request/Organizations/OrganizationCollectionManagementUpdateRequestModel.cs +++ b/src/Api/Models/Request/Organizations/OrganizationCollectionManagementUpdateRequestModel.cs @@ -7,12 +7,14 @@ public class OrganizationCollectionManagementUpdateRequestModel { public bool LimitCollectionCreation { get; set; } public bool LimitCollectionDeletion { get; set; } + public bool LimitItemDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } public virtual Organization ToOrganization(Organization existingOrganization, IFeatureService featureService) { existingOrganization.LimitCollectionCreation = LimitCollectionCreation; existingOrganization.LimitCollectionDeletion = LimitCollectionDeletion; + existingOrganization.LimitItemDeletion = LimitItemDeletion; existingOrganization.AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems; return existingOrganization; } diff --git a/src/Api/Models/Response/ProfileResponseModel.cs b/src/Api/Models/Response/ProfileResponseModel.cs index a6ed4ebfa2..82ffb05b0b 100644 --- a/src/Api/Models/Response/ProfileResponseModel.cs +++ b/src/Api/Models/Response/ProfileResponseModel.cs @@ -37,6 +37,7 @@ public class ProfileResponseModel : ResponseModel UsesKeyConnector = user.UsesKeyConnector; AvatarColor = user.AvatarColor; CreationDate = user.CreationDate; + VerifyDevices = user.VerifyDevices; Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o, organizationIdsManagingUser)); Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p)); ProviderOrganizations = @@ -62,6 +63,7 @@ public class ProfileResponseModel : ResponseModel public bool UsesKeyConnector { get; set; } public string AvatarColor { get; set; } public DateTime CreationDate { get; set; } + public bool VerifyDevices { get; set; } public IEnumerable Organizations { get; set; } public IEnumerable Providers { get; set; } public IEnumerable ProviderOrganizations { get; set; } diff --git a/src/Api/Models/Response/TaxRateResponseModel.cs b/src/Api/Models/Response/TaxRateResponseModel.cs deleted file mode 100644 index 2c3335314c..0000000000 --- a/src/Api/Models/Response/TaxRateResponseModel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Bit.Core.Entities; -using Bit.Core.Models.Api; - -namespace Bit.Api.Models.Response; - -public class TaxRateResponseModel : ResponseModel -{ - public TaxRateResponseModel(TaxRate taxRate) - : base("profile") - { - if (taxRate == null) - { - throw new ArgumentNullException(nameof(taxRate)); - } - - Id = taxRate.Id; - Country = taxRate.Country; - State = taxRate.State; - PostalCode = taxRate.PostalCode; - Rate = taxRate.Rate; - } - - public string Id { get; set; } - public string Country { get; set; } - public string State { get; set; } - public string PostalCode { get; set; } - public decimal Rate { get; set; } -} diff --git a/src/Api/Platform/Push/Controllers/PushController.cs b/src/Api/Platform/Push/Controllers/PushController.cs index 4b9f1c3e11..8b9e8b52a0 100644 --- a/src/Api/Platform/Push/Controllers/PushController.cs +++ b/src/Api/Platform/Push/Controllers/PushController.cs @@ -43,7 +43,7 @@ public class PushController : Controller { CheckUsage(); await _pushRegistrationService.CreateOrUpdateRegistrationAsync(model.PushToken, Prefix(model.DeviceId), - Prefix(model.UserId), Prefix(model.Identifier), model.Type); + Prefix(model.UserId), Prefix(model.Identifier), model.Type, model.OrganizationIds.Select(Prefix)); } [HttpPost("delete")] @@ -79,12 +79,12 @@ public class PushController : Controller if (!string.IsNullOrWhiteSpace(model.UserId)) { await _pushNotificationService.SendPayloadToUserAsync(Prefix(model.UserId), - model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId)); + model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); } else if (!string.IsNullOrWhiteSpace(model.OrganizationId)) { await _pushNotificationService.SendPayloadToOrganizationAsync(Prefix(model.OrganizationId), - model.Type.Value, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId)); + model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); } } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 1adf3f67dc..a341257259 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -29,6 +29,7 @@ using Bit.Core.Vault.Entities; using Bit.Api.Auth.Models.Request.WebAuthn; using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Identity.TokenProviders; +using Bit.Core.Tools.ImportFeatures; using Bit.Core.Tools.ReportFeatures; @@ -175,6 +176,7 @@ public class Startup services.AddCoreLocalizationServices(); services.AddBillingOperations(); services.AddReportingServices(); + services.AddImportServices(); // Authorization Handlers services.AddAuthorizationHandlers(); diff --git a/src/Api/Tools/Controllers/ImportCiphersController.cs b/src/Api/Tools/Controllers/ImportCiphersController.cs index 0d07d5bc47..d6104de354 100644 --- a/src/Api/Tools/Controllers/ImportCiphersController.cs +++ b/src/Api/Tools/Controllers/ImportCiphersController.cs @@ -7,7 +7,7 @@ using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Vault.Services; +using Bit.Core.Tools.ImportFeatures.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -17,31 +17,30 @@ namespace Bit.Api.Tools.Controllers; [Authorize("Application")] public class ImportCiphersController : Controller { - private readonly ICipherService _cipherService; private readonly IUserService _userService; private readonly ICurrentContext _currentContext; private readonly ILogger _logger; private readonly GlobalSettings _globalSettings; private readonly ICollectionRepository _collectionRepository; private readonly IAuthorizationService _authorizationService; + private readonly IImportCiphersCommand _importCiphersCommand; public ImportCiphersController( - ICipherService cipherService, IUserService userService, ICurrentContext currentContext, ILogger logger, GlobalSettings globalSettings, ICollectionRepository collectionRepository, IAuthorizationService authorizationService, - IOrganizationRepository organizationRepository) + IImportCiphersCommand importCiphersCommand) { - _cipherService = cipherService; _userService = userService; _currentContext = currentContext; _logger = logger; _globalSettings = globalSettings; _collectionRepository = collectionRepository; _authorizationService = authorizationService; + _importCiphersCommand = importCiphersCommand; } [HttpPost("import")] @@ -57,7 +56,7 @@ public class ImportCiphersController : Controller var userId = _userService.GetProperUserId(User).Value; var folders = model.Folders.Select(f => f.ToFolder(userId)).ToList(); var ciphers = model.Ciphers.Select(c => c.ToCipherDetails(userId, false)).ToList(); - await _cipherService.ImportCiphersAsync(folders, ciphers, model.FolderRelationships); + await _importCiphersCommand.ImportIntoIndividualVaultAsync(folders, ciphers, model.FolderRelationships); } [HttpPost("import-organization")] @@ -65,8 +64,9 @@ public class ImportCiphersController : Controller [FromBody] ImportOrganizationCiphersRequestModel model) { if (!_globalSettings.SelfHosted && - (model.Ciphers.Count() > 7000 || model.CollectionRelationships.Count() > 14000 || - model.Collections.Count() > 2000)) + (model.Ciphers.Count() > _globalSettings.ImportCiphersLimitation.CiphersLimit || + model.CollectionRelationships.Count() > _globalSettings.ImportCiphersLimitation.CollectionRelationshipsLimit || + model.Collections.Count() > _globalSettings.ImportCiphersLimitation.CollectionsLimit)) { throw new BadRequestException("You cannot import this much data at once."); } @@ -85,7 +85,7 @@ public class ImportCiphersController : Controller var userId = _userService.GetProperUserId(User).Value; var ciphers = model.Ciphers.Select(l => l.ToOrganizationCipherDetails(orgId)).ToList(); - await _cipherService.ImportCiphersAsync(collections, ciphers, model.CollectionRelationships, userId); + await _importCiphersCommand.ImportIntoOrganizationalVaultAsync(collections, ciphers, model.CollectionRelationships, userId); } private async Task CheckOrgImportPermission(List collections, Guid orgId) @@ -96,12 +96,6 @@ public class ImportCiphersController : Controller return true; } - //Users allowed to import if they CanCreate Collections - if (!(await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Create)).Succeeded) - { - return false; - } - //Calling Repository instead of Service as we want to get all the collections, regardless of permission //Permissions check will be done later on AuthorizationService var orgCollectionIds = @@ -118,6 +112,12 @@ public class ImportCiphersController : Controller return false; }; + //Users allowed to import if they CanCreate Collections + if (!(await _authorizationService.AuthorizeAsync(User, collections, BulkCollectionOperations.Create)).Succeeded) + { + return false; + } + return true; } } diff --git a/src/Api/Tools/Controllers/OrganizationExportController.cs b/src/Api/Tools/Controllers/OrganizationExportController.cs index 144e1be69e..520746f139 100644 --- a/src/Api/Tools/Controllers/OrganizationExportController.cs +++ b/src/Api/Tools/Controllers/OrganizationExportController.cs @@ -1,16 +1,11 @@ -using Bit.Api.Models.Response; -using Bit.Api.Tools.Authorization; +using Bit.Api.Tools.Authorization; using Bit.Api.Tools.Models.Response; -using Bit.Api.Vault.Models.Response; -using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization; using Bit.Core.Context; -using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Queries; using Bit.Core.Vault.Services; using Microsoft.AspNetCore.Authorization; @@ -56,39 +51,6 @@ public class OrganizationExportController : Controller [HttpGet("export")] public async Task Export(Guid organizationId) - { - if (_featureService.IsEnabled(FeatureFlagKeys.PM11360RemoveProviderExportPermission)) - { - return await Export_vNext(organizationId); - } - - var userId = _userService.GetProperUserId(User).Value; - - IEnumerable orgCollections = await _collectionService.GetOrganizationCollectionsAsync(organizationId); - (IEnumerable orgCiphers, Dictionary> collectionCiphersGroupDict) = await _cipherService.GetOrganizationCiphers(userId, organizationId); - - if (_currentContext.ClientVersion == null || _currentContext.ClientVersion >= new Version("2023.1.0")) - { - var organizationExportResponseModel = new OrganizationExportResponseModel - { - Collections = orgCollections.Select(c => new CollectionResponseModel(c)), - Ciphers = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings, collectionCiphersGroupDict, c.OrganizationUseTotp)) - }; - - return Ok(organizationExportResponseModel); - } - - // Backward compatibility with versions before 2023.1.0 that use ListResponseModel - var organizationExportListResponseModel = new OrganizationExportListResponseModel - { - Collections = GetOrganizationCollectionsResponse(orgCollections), - Ciphers = GetOrganizationCiphersResponse(orgCiphers, collectionCiphersGroupDict) - }; - - return Ok(organizationExportListResponseModel); - } - - private async Task Export_vNext(Guid organizationId) { var canExportAll = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(organizationId), VaultExportOperations.ExportWholeVault); @@ -116,19 +78,4 @@ public class OrganizationExportController : Controller // Unauthorized throw new NotFoundException(); } - - private ListResponseModel GetOrganizationCollectionsResponse(IEnumerable orgCollections) - { - var collections = orgCollections.Select(c => new CollectionResponseModel(c)); - return new ListResponseModel(collections); - } - - private ListResponseModel GetOrganizationCiphersResponse(IEnumerable orgCiphers, - Dictionary> collectionCiphersGroupDict) - { - var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings, - collectionCiphersGroupDict, c.OrganizationUseTotp)); - - return new ListResponseModel(responses); - } } diff --git a/src/Api/Tools/Controllers/SendsController.cs b/src/Api/Tools/Controllers/SendsController.cs index f7f3f692a8..3b5534bed0 100644 --- a/src/Api/Tools/Controllers/SendsController.cs +++ b/src/Api/Tools/Controllers/SendsController.cs @@ -9,7 +9,6 @@ using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Services; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Data; using Bit.Core.Tools.Repositories; @@ -163,32 +162,6 @@ public class SendsController : Controller return new SendResponseModel(send, _globalSettings); } - [HttpPost("file")] - [Obsolete("Deprecated File Send API", false)] - [RequestSizeLimit(Constants.FileSize101mb)] - [DisableFormValueModelBinding] - public async Task PostFile() - { - if (!Request?.ContentType.Contains("multipart/") ?? true) - { - throw new BadRequestException("Invalid content."); - } - - Send send = null; - await Request.GetSendFileAsync(async (stream, fileName, model) => - { - model.ValidateCreation(); - var userId = _userService.GetProperUserId(User).Value; - var (madeSend, madeData) = model.ToSend(userId, fileName, _sendService); - send = madeSend; - await _sendService.SaveFileSendAsync(send, madeData, model.FileLength.GetValueOrDefault(0)); - await _sendService.UploadFileToExistingSendAsync(stream, send); - }); - - return new SendResponseModel(send, _globalSettings); - } - - [HttpPost("file/v2")] public async Task PostFile([FromBody] SendRequestModel model) { diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 59984683e5..5a7d427963 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -424,6 +424,59 @@ public class CiphersController : Controller return false; } + /// + /// TODO: Move this to its own authorization handler or equivalent service - AC-2062 + /// + private async Task CanModifyCipherCollectionsAsync(Guid organizationId, IEnumerable cipherIds) + { + // If the user can edit all ciphers for the organization, just check they all belong to the org + if (await CanEditAllCiphersAsync(organizationId)) + { + // TODO: This can likely be optimized to only query the requested ciphers and then checking they belong to the org + var orgCiphers = (await _cipherRepository.GetManyByOrganizationIdAsync(organizationId)).ToDictionary(c => c.Id); + + // Ensure all requested ciphers are in orgCiphers + if (cipherIds.Any(c => !orgCiphers.ContainsKey(c))) + { + return false; + } + + return true; + } + + // The user cannot access any ciphers for the organization, we're done + if (!await CanAccessOrganizationCiphersAsync(organizationId)) + { + return false; + } + + var userId = _userService.GetProperUserId(User).Value; + // Select all editable ciphers for this user belonging to the organization + var editableOrgCipherList = (await _cipherRepository.GetManyByUserIdAsync(userId, true)) + .Where(c => c.OrganizationId == organizationId && c.UserId == null && c.Edit && c.ViewPassword).ToList(); + + // Special case for unassigned ciphers + if (await CanAccessUnassignedCiphersAsync(organizationId)) + { + var unassignedCiphers = + (await _cipherRepository.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync( + organizationId)); + + // Users that can access unassigned ciphers can also edit them + editableOrgCipherList.AddRange(unassignedCiphers.Select(c => new CipherDetails(c) { Edit = true })); + } + + var editableOrgCiphers = editableOrgCipherList + .ToDictionary(c => c.Id); + + if (cipherIds.Any(c => !editableOrgCiphers.ContainsKey(c))) + { + return false; + } + + return true; + } + /// /// TODO: Move this to its own authorization handler or equivalent service - AC-2062 /// @@ -579,7 +632,7 @@ public class CiphersController : Controller var userId = _userService.GetProperUserId(User).Value; var cipher = await GetByIdAsync(id, userId); if (cipher == null || !cipher.OrganizationId.HasValue || - !await _currentContext.OrganizationUser(cipher.OrganizationId.Value)) + !await _currentContext.OrganizationUser(cipher.OrganizationId.Value) || !cipher.ViewPassword) { throw new NotFoundException(); } @@ -634,7 +687,7 @@ public class CiphersController : Controller [HttpPost("bulk-collections")] public async Task PostBulkCollections([FromBody] CipherBulkUpdateCollectionsRequestModel model) { - if (!await CanEditCiphersAsync(model.OrganizationId, model.CipherIds) || + if (!await CanModifyCipherCollectionsAsync(model.OrganizationId, model.CipherIds) || !await CanEditItemsInCollections(model.OrganizationId, model.CollectionIds)) { throw new NotFoundException(); @@ -1097,7 +1150,7 @@ public class CiphersController : Controller [HttpDelete("{id}/attachment/{attachmentId}")] [HttpPost("{id}/attachment/{attachmentId}/delete")] - public async Task DeleteAttachment(Guid id, string attachmentId) + public async Task DeleteAttachment(Guid id, string attachmentId) { var userId = _userService.GetProperUserId(User).Value; var cipher = await GetByIdAsync(id, userId); @@ -1106,7 +1159,7 @@ public class CiphersController : Controller throw new NotFoundException(); } - await _cipherService.DeleteAttachmentAsync(cipher, attachmentId, userId, false); + return await _cipherService.DeleteAttachmentAsync(cipher, attachmentId, userId, false); } [HttpDelete("{id}/attachment/{attachmentId}/admin")] diff --git a/src/Api/Vault/Controllers/SecurityTaskController.cs b/src/Api/Vault/Controllers/SecurityTaskController.cs index 14ef0e5e4e..88b7aed9c6 100644 --- a/src/Api/Vault/Controllers/SecurityTaskController.cs +++ b/src/Api/Vault/Controllers/SecurityTaskController.cs @@ -1,4 +1,5 @@ using Bit.Api.Models.Response; +using Bit.Api.Vault.Models.Request; using Bit.Api.Vault.Models.Response; using Bit.Core; using Bit.Core.Services; @@ -20,17 +21,20 @@ public class SecurityTaskController : Controller private readonly IGetTaskDetailsForUserQuery _getTaskDetailsForUserQuery; private readonly IMarkTaskAsCompleteCommand _markTaskAsCompleteCommand; private readonly IGetTasksForOrganizationQuery _getTasksForOrganizationQuery; + private readonly ICreateManyTasksCommand _createManyTasksCommand; public SecurityTaskController( IUserService userService, IGetTaskDetailsForUserQuery getTaskDetailsForUserQuery, IMarkTaskAsCompleteCommand markTaskAsCompleteCommand, - IGetTasksForOrganizationQuery getTasksForOrganizationQuery) + IGetTasksForOrganizationQuery getTasksForOrganizationQuery, + ICreateManyTasksCommand createManyTasksCommand) { _userService = userService; _getTaskDetailsForUserQuery = getTaskDetailsForUserQuery; _markTaskAsCompleteCommand = markTaskAsCompleteCommand; _getTasksForOrganizationQuery = getTasksForOrganizationQuery; + _createManyTasksCommand = createManyTasksCommand; } /// @@ -71,4 +75,19 @@ public class SecurityTaskController : Controller var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList(); return new ListResponseModel(response); } + + /// + /// Bulk create security tasks for an organization. + /// + /// + /// + /// A list response model containing the security tasks created for the organization. + [HttpPost("{orgId:guid}/bulk-create")] + public async Task> BulkCreateTasks(Guid orgId, + [FromBody] BulkCreateSecurityTasksRequestModel model) + { + var securityTasks = await _createManyTasksCommand.CreateAsync(orgId, model.Tasks); + var response = securityTasks.Select(x => new SecurityTasksResponseModel(x)).ToList(); + return new ListResponseModel(response); + } } diff --git a/src/Api/Vault/Models/Request/BulkCreateSecurityTasksRequestModel.cs b/src/Api/Vault/Models/Request/BulkCreateSecurityTasksRequestModel.cs new file mode 100644 index 0000000000..6c8c7e03b3 --- /dev/null +++ b/src/Api/Vault/Models/Request/BulkCreateSecurityTasksRequestModel.cs @@ -0,0 +1,8 @@ +using Bit.Core.Vault.Models.Api; + +namespace Bit.Api.Vault.Models.Request; + +public class BulkCreateSecurityTasksRequestModel +{ + public IEnumerable Tasks { get; set; } +} diff --git a/src/Api/appsettings.json b/src/Api/appsettings.json index c04539a9fe..98b210cb1e 100644 --- a/src/Api/appsettings.json +++ b/src/Api/appsettings.json @@ -56,6 +56,11 @@ "publicKey": "SECRET", "privateKey": "SECRET" }, + "importCiphersLimitation": { + "ciphersLimit": 40000, + "collectionRelationshipsLimit": 80000, + "collectionsLimit": 2000 + }, "bitPay": { "production": false, "token": "SECRET", diff --git a/src/Billing/Billing.csproj b/src/Billing/Billing.csproj index b30d987a95..50e372791f 100644 --- a/src/Billing/Billing.csproj +++ b/src/Billing/Billing.csproj @@ -3,6 +3,8 @@ bitwarden-Billing false + + $(WarningsNotAsErrors);CS9113 @@ -10,5 +12,8 @@ + + + diff --git a/src/Billing/BillingSettings.cs b/src/Billing/BillingSettings.cs index 91ea8f1221..ffe73808d4 100644 --- a/src/Billing/BillingSettings.cs +++ b/src/Billing/BillingSettings.cs @@ -12,6 +12,7 @@ public class BillingSettings public virtual FreshDeskSettings FreshDesk { get; set; } = new FreshDeskSettings(); public virtual string FreshsalesApiKey { get; set; } public virtual PayPalSettings PayPal { get; set; } = new PayPalSettings(); + public virtual OnyxSettings Onyx { get; set; } = new OnyxSettings(); public class PayPalSettings { @@ -31,4 +32,10 @@ public class BillingSettings public virtual string UserFieldName { get; set; } public virtual string OrgFieldName { get; set; } } + + public class OnyxSettings + { + public virtual string ApiKey { get; set; } + public virtual string BaseUrl { get; set; } + } } diff --git a/src/Billing/Controllers/BitPayController.cs b/src/Billing/Controllers/BitPayController.cs index 026909aed1..3747631bd0 100644 --- a/src/Billing/Controllers/BitPayController.cs +++ b/src/Billing/Controllers/BitPayController.cs @@ -1,6 +1,7 @@ using System.Globalization; using Bit.Billing.Models; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; @@ -13,6 +14,7 @@ using Microsoft.Extensions.Options; namespace Bit.Billing.Controllers; [Route("bitpay")] +[ApiExplorerSettings(IgnoreApi = true)] public class BitPayController : Controller { private readonly BillingSettings _billingSettings; @@ -24,6 +26,7 @@ public class BitPayController : Controller private readonly IMailService _mailService; private readonly IPaymentService _paymentService; private readonly ILogger _logger; + private readonly IPremiumUserBillingService _premiumUserBillingService; public BitPayController( IOptions billingSettings, @@ -34,7 +37,8 @@ public class BitPayController : Controller IProviderRepository providerRepository, IMailService mailService, IPaymentService paymentService, - ILogger logger) + ILogger logger, + IPremiumUserBillingService premiumUserBillingService) { _billingSettings = billingSettings?.Value; _bitPayClient = bitPayClient; @@ -45,6 +49,7 @@ public class BitPayController : Controller _mailService = mailService; _paymentService = paymentService; _logger = logger; + _premiumUserBillingService = premiumUserBillingService; } [HttpPost("ipn")] @@ -144,10 +149,7 @@ public class BitPayController : Controller if (user != null) { billingEmail = user.BillingEmailAddress(); - if (await _paymentService.CreditAccountAsync(user, tx.Amount)) - { - await _userRepository.ReplaceAsync(user); - } + await _premiumUserBillingService.Credit(user, tx.Amount); } } else if (tx.ProviderId.HasValue) diff --git a/src/Billing/Controllers/FreshdeskController.cs b/src/Billing/Controllers/FreshdeskController.cs index 1b6ddea429..4bf6b7bad4 100644 --- a/src/Billing/Controllers/FreshdeskController.cs +++ b/src/Billing/Controllers/FreshdeskController.cs @@ -1,6 +1,8 @@ using System.ComponentModel.DataAnnotations; +using System.Net.Http.Headers; using System.Reflection; using System.Text; +using System.Text.Json; using System.Web; using Bit.Billing.Models; using Bit.Core.Repositories; @@ -17,7 +19,6 @@ public class FreshdeskController : Controller private readonly BillingSettings _billingSettings; private readonly IUserRepository _userRepository; private readonly IOrganizationRepository _organizationRepository; - private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ILogger _logger; private readonly GlobalSettings _globalSettings; private readonly IHttpClientFactory _httpClientFactory; @@ -25,7 +26,6 @@ public class FreshdeskController : Controller public FreshdeskController( IUserRepository userRepository, IOrganizationRepository organizationRepository, - IOrganizationUserRepository organizationUserRepository, IOptions billingSettings, ILogger logger, GlobalSettings globalSettings, @@ -34,7 +34,6 @@ public class FreshdeskController : Controller _billingSettings = billingSettings?.Value; _userRepository = userRepository; _organizationRepository = organizationRepository; - _organizationUserRepository = organizationUserRepository; _logger = logger; _globalSettings = globalSettings; _httpClientFactory = httpClientFactory; @@ -145,6 +144,121 @@ public class FreshdeskController : Controller } } + [HttpPost("webhook-onyx-ai")] + public async Task PostWebhookOnyxAi([FromQuery, Required] string key, + [FromBody, Required] FreshdeskWebhookModel model) + { + // ensure that the key is from Freshdesk + if (!IsValidRequestFromFreshdesk(key)) + { + return new BadRequestResult(); + } + + // get ticket info from Freshdesk + var getTicketRequest = new HttpRequestMessage(HttpMethod.Get, + string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", model.TicketId)); + var getTicketResponse = await CallFreshdeskApiAsync(getTicketRequest); + + // check if we have a valid response from freshdesk + if (getTicketResponse.StatusCode != System.Net.HttpStatusCode.OK) + { + _logger.LogError("Error getting ticket info from Freshdesk. Ticket Id: {0}. Status code: {1}", + model.TicketId, getTicketResponse.StatusCode); + return BadRequest("Failed to retrieve ticket info from Freshdesk"); + } + + // extract info from the response + var ticketInfo = await ExtractTicketInfoFromResponse(getTicketResponse); + if (ticketInfo == null) + { + return BadRequest("Failed to extract ticket info from Freshdesk response"); + } + + // create the onyx `answer-with-citation` request + var onyxRequestModel = new OnyxAnswerWithCitationRequestModel(ticketInfo.DescriptionText); + var onyxRequest = new HttpRequestMessage(HttpMethod.Post, + string.Format("{0}/query/answer-with-citation", _billingSettings.Onyx.BaseUrl)) + { + Content = JsonContent.Create(onyxRequestModel, mediaType: new MediaTypeHeaderValue("application/json")), + }; + var (_, onyxJsonResponse) = await CallOnyxApi(onyxRequest); + + // the CallOnyxApi will return a null if we have an error response + if (onyxJsonResponse?.Answer == null || !string.IsNullOrEmpty(onyxJsonResponse?.ErrorMsg)) + { + return BadRequest( + string.Format("Failed to get a valid response from Onyx API. Response: {0}", + JsonSerializer.Serialize(onyxJsonResponse ?? new OnyxAnswerWithCitationResponseModel()))); + } + + // add the answer as a note to the ticket + await AddAnswerNoteToTicketAsync(onyxJsonResponse.Answer, model.TicketId); + + return Ok(); + } + + private bool IsValidRequestFromFreshdesk(string key) + { + if (string.IsNullOrWhiteSpace(key) + || !CoreHelpers.FixedTimeEquals(key, _billingSettings.FreshDesk.WebhookKey)) + { + return false; + } + + return true; + } + + private async Task AddAnswerNoteToTicketAsync(string note, string ticketId) + { + // if there is no content, then we don't need to add a note + if (string.IsNullOrWhiteSpace(note)) + { + return; + } + + var noteBody = new Dictionary + { + { "body", $"Onyx AI:
    {note}
" }, + { "private", true } + }; + + var noteRequest = new HttpRequestMessage(HttpMethod.Post, + string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId)) + { + Content = JsonContent.Create(noteBody), + }; + + var addNoteResponse = await CallFreshdeskApiAsync(noteRequest); + if (addNoteResponse.StatusCode != System.Net.HttpStatusCode.Created) + { + _logger.LogError("Error adding note to Freshdesk ticket. Ticket Id: {0}. Status: {1}", + ticketId, addNoteResponse.ToString()); + } + } + + private async Task ExtractTicketInfoFromResponse(HttpResponseMessage getTicketResponse) + { + var responseString = string.Empty; + try + { + responseString = await getTicketResponse.Content.ReadAsStringAsync(); + var ticketInfo = JsonSerializer.Deserialize(responseString, + options: new System.Text.Json.JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }); + + return ticketInfo; + } + catch (System.Exception ex) + { + _logger.LogError("Error deserializing ticket info from Freshdesk response. Response: {0}. Exception {1}", + responseString, ex.ToString()); + } + + return null; + } + private async Task CallFreshdeskApiAsync(HttpRequestMessage request, int retriedCount = 0) { try @@ -169,6 +283,26 @@ public class FreshdeskController : Controller return await CallFreshdeskApiAsync(request, retriedCount++); } + private async Task<(HttpResponseMessage, T)> CallOnyxApi(HttpRequestMessage request) + { + var httpClient = _httpClientFactory.CreateClient("OnyxApi"); + var response = await httpClient.SendAsync(request); + + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + _logger.LogError("Error calling Onyx AI API. Status code: {0}. Response {1}", + response.StatusCode, JsonSerializer.Serialize(response)); + return (null, default); + } + var responseStr = await response.Content.ReadAsStringAsync(); + var responseJson = JsonSerializer.Deserialize(responseStr, options: new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + }); + + return (response, responseJson); + } + private TAttribute GetAttribute(Enum enumValue) where TAttribute : Attribute { return enumValue.GetType().GetMember(enumValue.ToString()).First().GetCustomAttribute(); diff --git a/src/Billing/Controllers/LoginController.cs b/src/Billing/Controllers/LoginController.cs deleted file mode 100644 index c2df41b92c..0000000000 --- a/src/Billing/Controllers/LoginController.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Billing.Controllers; - -public class LoginController : Controller -{ - /* - private readonly PasswordlessSignInManager _signInManager; - - public LoginController( - PasswordlessSignInManager signInManager) - { - _signInManager = signInManager; - } - - public IActionResult Index() - { - return View(); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Index(LoginModel model) - { - if (ModelState.IsValid) - { - var result = await _signInManager.PasswordlessSignInAsync(model.Email, - Url.Action("Confirm", "Login", null, Request.Scheme)); - if (result.Succeeded) - { - return RedirectToAction("Index", "Home"); - } - else - { - ModelState.AddModelError(string.Empty, "Account not found."); - } - } - - return View(model); - } - - public async Task Confirm(string email, string token) - { - var result = await _signInManager.PasswordlessSignInAsync(email, token, false); - if (!result.Succeeded) - { - return View("Error"); - } - - return RedirectToAction("Index", "Home"); - } - */ -} diff --git a/src/Billing/Controllers/PayPalController.cs b/src/Billing/Controllers/PayPalController.cs index 2fc8aab4f2..2afde80601 100644 --- a/src/Billing/Controllers/PayPalController.cs +++ b/src/Billing/Controllers/PayPalController.cs @@ -1,6 +1,7 @@ using System.Text; using Bit.Billing.Models; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; @@ -23,6 +24,7 @@ public class PayPalController : Controller private readonly ITransactionRepository _transactionRepository; private readonly IUserRepository _userRepository; private readonly IProviderRepository _providerRepository; + private readonly IPremiumUserBillingService _premiumUserBillingService; public PayPalController( IOptions billingSettings, @@ -32,7 +34,8 @@ public class PayPalController : Controller IPaymentService paymentService, ITransactionRepository transactionRepository, IUserRepository userRepository, - IProviderRepository providerRepository) + IProviderRepository providerRepository, + IPremiumUserBillingService premiumUserBillingService) { _billingSettings = billingSettings?.Value; _logger = logger; @@ -42,6 +45,7 @@ public class PayPalController : Controller _transactionRepository = transactionRepository; _userRepository = userRepository; _providerRepository = providerRepository; + _premiumUserBillingService = premiumUserBillingService; } [HttpPost("ipn")] @@ -257,10 +261,9 @@ public class PayPalController : Controller { var user = await _userRepository.GetByIdAsync(transaction.UserId.Value); - if (await _paymentService.CreditAccountAsync(user, transaction.Amount)) + if (user != null) { - await _userRepository.ReplaceAsync(user); - + await _premiumUserBillingService.Credit(user, transaction.Amount); billingEmail = user.BillingEmailAddress(); } } diff --git a/src/Billing/Jobs/JobsHostedService.cs b/src/Billing/Jobs/JobsHostedService.cs index d91ca21520..a6e702c662 100644 --- a/src/Billing/Jobs/JobsHostedService.cs +++ b/src/Billing/Jobs/JobsHostedService.cs @@ -32,5 +32,6 @@ public class JobsHostedService : BaseJobsHostedService public static void AddJobsServices(IServiceCollection services) { services.AddTransient(); + services.AddTransient(); } } diff --git a/src/Billing/Jobs/SubscriptionCancellationJob.cs b/src/Billing/Jobs/SubscriptionCancellationJob.cs new file mode 100644 index 0000000000..c46581272e --- /dev/null +++ b/src/Billing/Jobs/SubscriptionCancellationJob.cs @@ -0,0 +1,58 @@ +using Bit.Billing.Services; +using Bit.Core.Repositories; +using Quartz; +using Stripe; + +namespace Bit.Billing.Jobs; + +public class SubscriptionCancellationJob( + IStripeFacade stripeFacade, + IOrganizationRepository organizationRepository) + : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var subscriptionId = context.MergedJobDataMap.GetString("subscriptionId"); + var organizationId = new Guid(context.MergedJobDataMap.GetString("organizationId") ?? string.Empty); + + var organization = await organizationRepository.GetByIdAsync(organizationId); + if (organization == null || organization.Enabled) + { + // Organization was deleted or re-enabled by CS, skip cancellation + return; + } + + var subscription = await stripeFacade.GetSubscription(subscriptionId); + if (subscription?.Status != "unpaid") + { + // Subscription is no longer unpaid, skip cancellation + return; + } + + // Cancel the subscription + await stripeFacade.CancelSubscription(subscriptionId, new SubscriptionCancelOptions()); + + // Void any open invoices + var options = new InvoiceListOptions + { + Status = "open", + Subscription = subscriptionId, + Limit = 100 + }; + var invoices = await stripeFacade.ListInvoices(options); + foreach (var invoice in invoices) + { + await stripeFacade.VoidInvoice(invoice.Id); + } + + while (invoices.HasMore) + { + options.StartingAfter = invoices.Data.Last().Id; + invoices = await stripeFacade.ListInvoices(options); + foreach (var invoice in invoices) + { + await stripeFacade.VoidInvoice(invoice.Id); + } + } + } +} diff --git a/src/Billing/Models/FreshdeskViewTicketModel.cs b/src/Billing/Models/FreshdeskViewTicketModel.cs new file mode 100644 index 0000000000..2aa6eff94d --- /dev/null +++ b/src/Billing/Models/FreshdeskViewTicketModel.cs @@ -0,0 +1,44 @@ +namespace Bit.Billing.Models; + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +public class FreshdeskViewTicketModel +{ + [JsonPropertyName("spam")] + public bool? Spam { get; set; } + + [JsonPropertyName("priority")] + public int? Priority { get; set; } + + [JsonPropertyName("source")] + public int? Source { get; set; } + + [JsonPropertyName("status")] + public int? Status { get; set; } + + [JsonPropertyName("subject")] + public string Subject { get; set; } + + [JsonPropertyName("support_email")] + public string SupportEmail { get; set; } + + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("description")] + public string Description { get; set; } + + [JsonPropertyName("description_text")] + public string DescriptionText { get; set; } + + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + [JsonPropertyName("updated_at")] + public DateTime UpdatedAt { get; set; } + + [JsonPropertyName("tags")] + public List Tags { get; set; } +} diff --git a/src/Billing/Models/OnyxAnswerWithCitationRequestModel.cs b/src/Billing/Models/OnyxAnswerWithCitationRequestModel.cs new file mode 100644 index 0000000000..e7bd29b2f5 --- /dev/null +++ b/src/Billing/Models/OnyxAnswerWithCitationRequestModel.cs @@ -0,0 +1,54 @@ + +using System.Text.Json.Serialization; + +namespace Bit.Billing.Models; + +public class OnyxAnswerWithCitationRequestModel +{ + [JsonPropertyName("messages")] + public List Messages { get; set; } + + [JsonPropertyName("persona_id")] + public int PersonaId { get; set; } = 1; + + [JsonPropertyName("prompt_id")] + public int PromptId { get; set; } = 1; + + [JsonPropertyName("retrieval_options")] + public RetrievalOptions RetrievalOptions { get; set; } + + public OnyxAnswerWithCitationRequestModel(string message) + { + message = message.Replace(Environment.NewLine, " ").Replace('\r', ' ').Replace('\n', ' '); + Messages = new List() { new Message() { MessageText = message } }; + RetrievalOptions = new RetrievalOptions(); + } +} + +public class Message +{ + [JsonPropertyName("message")] + public string MessageText { get; set; } + + [JsonPropertyName("sender")] + public string Sender { get; set; } = "user"; +} + +public class RetrievalOptions +{ + [JsonPropertyName("run_search")] + public string RunSearch { get; set; } = RetrievalOptionsRunSearch.Auto; + + [JsonPropertyName("real_time")] + public bool RealTime { get; set; } = true; + + [JsonPropertyName("limit")] + public int? Limit { get; set; } = 3; +} + +public class RetrievalOptionsRunSearch +{ + public const string Always = "always"; + public const string Never = "never"; + public const string Auto = "auto"; +} diff --git a/src/Billing/Models/OnyxAnswerWithCitationResponseModel.cs b/src/Billing/Models/OnyxAnswerWithCitationResponseModel.cs new file mode 100644 index 0000000000..e85ee9a674 --- /dev/null +++ b/src/Billing/Models/OnyxAnswerWithCitationResponseModel.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace Bit.Billing.Models; + +public class OnyxAnswerWithCitationResponseModel +{ + [JsonPropertyName("answer")] + public string Answer { get; set; } + + [JsonPropertyName("rephrase")] + public string Rephrase { get; set; } + + [JsonPropertyName("citations")] + public List Citations { get; set; } + + [JsonPropertyName("llm_selected_doc_indices")] + public List LlmSelectedDocIndices { get; set; } + + [JsonPropertyName("error_msg")] + public string ErrorMsg { get; set; } +} + +public class Citation +{ + [JsonPropertyName("citation_num")] + public int CitationNum { get; set; } + + [JsonPropertyName("document_id")] + public string DocumentId { get; set; } +} diff --git a/src/Billing/Services/IStripeFacade.cs b/src/Billing/Services/IStripeFacade.cs index f793846a53..77ba9a1ad4 100644 --- a/src/Billing/Services/IStripeFacade.cs +++ b/src/Billing/Services/IStripeFacade.cs @@ -80,12 +80,6 @@ public interface IStripeFacade RequestOptions requestOptions = null, CancellationToken cancellationToken = default); - Task GetTaxRate( - string taxRateId, - TaxRateGetOptions options = null, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default); - Task DeleteCustomerDiscount( string customerId, RequestOptions requestOptions = null, diff --git a/src/Billing/Services/Implementations/CustomerUpdatedHandler.cs b/src/Billing/Services/Implementations/CustomerUpdatedHandler.cs index ec70697c01..6deb0bc330 100644 --- a/src/Billing/Services/Implementations/CustomerUpdatedHandler.cs +++ b/src/Billing/Services/Implementations/CustomerUpdatedHandler.cs @@ -14,19 +14,22 @@ public class CustomerUpdatedHandler : ICustomerUpdatedHandler private readonly ICurrentContext _currentContext; private readonly IStripeEventService _stripeEventService; private readonly IStripeEventUtilityService _stripeEventUtilityService; + private readonly ILogger _logger; public CustomerUpdatedHandler( IOrganizationRepository organizationRepository, IReferenceEventService referenceEventService, ICurrentContext currentContext, IStripeEventService stripeEventService, - IStripeEventUtilityService stripeEventUtilityService) + IStripeEventUtilityService stripeEventUtilityService, + ILogger logger) { - _organizationRepository = organizationRepository; + _organizationRepository = organizationRepository ?? throw new ArgumentNullException(nameof(organizationRepository)); _referenceEventService = referenceEventService; _currentContext = currentContext; _stripeEventService = stripeEventService; _stripeEventUtilityService = stripeEventUtilityService; + _logger = logger; } /// @@ -35,25 +38,76 @@ public class CustomerUpdatedHandler : ICustomerUpdatedHandler /// public async Task HandleAsync(Event parsedEvent) { - var customer = await _stripeEventService.GetCustomer(parsedEvent, true, ["subscriptions"]); - if (customer.Subscriptions == null || !customer.Subscriptions.Any()) + if (parsedEvent == null) { + _logger.LogError("Parsed event was null in CustomerUpdatedHandler"); + throw new ArgumentNullException(nameof(parsedEvent)); + } + + if (_stripeEventService == null) + { + _logger.LogError("StripeEventService was not initialized in CustomerUpdatedHandler"); + throw new InvalidOperationException($"{nameof(_stripeEventService)} is not initialized"); + } + + var customer = await _stripeEventService.GetCustomer(parsedEvent, true, ["subscriptions"]); + if (customer?.Subscriptions == null || !customer.Subscriptions.Any()) + { + _logger.LogWarning("Customer or subscriptions were null or empty in CustomerUpdatedHandler. Customer ID: {CustomerId}", customer?.Id); return; } var subscription = customer.Subscriptions.First(); + if (subscription.Metadata == null) + { + _logger.LogWarning("Subscription metadata was null in CustomerUpdatedHandler. Subscription ID: {SubscriptionId}", subscription.Id); + return; + } + + if (_stripeEventUtilityService == null) + { + _logger.LogError("StripeEventUtilityService was not initialized in CustomerUpdatedHandler"); + throw new InvalidOperationException($"{nameof(_stripeEventUtilityService)} is not initialized"); + } + var (organizationId, _, providerId) = _stripeEventUtilityService.GetIdsFromMetadata(subscription.Metadata); if (!organizationId.HasValue) { + _logger.LogWarning("Organization ID was not found in subscription metadata. Subscription ID: {SubscriptionId}", subscription.Id); return; } + if (_organizationRepository == null) + { + _logger.LogError("OrganizationRepository was not initialized in CustomerUpdatedHandler"); + throw new InvalidOperationException($"{nameof(_organizationRepository)} is not initialized"); + } + var organization = await _organizationRepository.GetByIdAsync(organizationId.Value); + + if (organization == null) + { + _logger.LogWarning("Organization not found. Organization ID: {OrganizationId}", organizationId.Value); + return; + } + organization.BillingEmail = customer.Email; await _organizationRepository.ReplaceAsync(organization); + if (_referenceEventService == null) + { + _logger.LogError("ReferenceEventService was not initialized in CustomerUpdatedHandler"); + throw new InvalidOperationException($"{nameof(_referenceEventService)} is not initialized"); + } + + if (_currentContext == null) + { + _logger.LogError("CurrentContext was not initialized in CustomerUpdatedHandler"); + throw new InvalidOperationException($"{nameof(_currentContext)} is not initialized"); + } + await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.OrganizationEditedInStripe, organization, _currentContext)); } diff --git a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs index b16baea52e..1577e77c9e 100644 --- a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs +++ b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs @@ -1,4 +1,5 @@ using Bit.Billing.Constants; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Enums; using Bit.Core.Context; @@ -17,7 +18,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler { private readonly ILogger _logger; private readonly IStripeEventService _stripeEventService; - private readonly IOrganizationService _organizationService; private readonly IUserService _userService; private readonly IStripeFacade _stripeFacade; private readonly IProviderRepository _providerRepository; @@ -27,6 +27,7 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler private readonly IUserRepository _userRepository; private readonly IStripeEventUtilityService _stripeEventUtilityService; private readonly IPushNotificationService _pushNotificationService; + private readonly IOrganizationEnableCommand _organizationEnableCommand; public PaymentSucceededHandler( ILogger logger, @@ -39,8 +40,8 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler IUserRepository userRepository, IStripeEventUtilityService stripeEventUtilityService, IUserService userService, - IOrganizationService organizationService, - IPushNotificationService pushNotificationService) + IPushNotificationService pushNotificationService, + IOrganizationEnableCommand organizationEnableCommand) { _logger = logger; _stripeEventService = stripeEventService; @@ -52,8 +53,8 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler _userRepository = userRepository; _stripeEventUtilityService = stripeEventUtilityService; _userService = userService; - _organizationService = organizationService; _pushNotificationService = pushNotificationService; + _organizationEnableCommand = organizationEnableCommand; } /// @@ -142,7 +143,7 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler return; } - await _organizationService.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd); + await _organizationEnableCommand.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd); var organization = await _organizationRepository.GetByIdAsync(organizationId.Value); await _pushNotificationService.PushSyncOrganizationStatusAsync(organization); diff --git a/src/Billing/Services/Implementations/StripeEventUtilityService.cs b/src/Billing/Services/Implementations/StripeEventUtilityService.cs index 520205e745..48e81dee61 100644 --- a/src/Billing/Services/Implementations/StripeEventUtilityService.cs +++ b/src/Billing/Services/Implementations/StripeEventUtilityService.cs @@ -296,7 +296,7 @@ public class StripeEventUtilityService : IStripeEventUtilityService btObjIdField = "provider_id"; btObjId = providerId.Value; } - var btInvoiceAmount = invoice.AmountDue / 100M; + var btInvoiceAmount = Math.Round(invoice.AmountDue / 100M, 2); var existingTransactions = organizationId.HasValue ? await _transactionRepository.GetManyByOrganizationIdAsync(organizationId.Value) @@ -318,26 +318,34 @@ public class StripeEventUtilityService : IStripeEventUtilityService Result transactionResult; try { - transactionResult = await _btGateway.Transaction.SaleAsync( - new Braintree.TransactionRequest + var transactionRequest = new Braintree.TransactionRequest + { + Amount = btInvoiceAmount, + CustomerId = customer.Metadata["btCustomerId"], + Options = new Braintree.TransactionOptionsRequest { - Amount = btInvoiceAmount, - CustomerId = customer.Metadata["btCustomerId"], - Options = new Braintree.TransactionOptionsRequest + SubmitForSettlement = true, + PayPal = new Braintree.TransactionOptionsPayPalRequest { - SubmitForSettlement = true, - PayPal = new Braintree.TransactionOptionsPayPalRequest - { - CustomField = - $"{btObjIdField}:{btObjId},region:{_globalSettings.BaseServiceUri.CloudRegion}" - } - }, - CustomFields = new Dictionary - { - [btObjIdField] = btObjId.ToString(), - ["region"] = _globalSettings.BaseServiceUri.CloudRegion + CustomField = + $"{btObjIdField}:{btObjId},region:{_globalSettings.BaseServiceUri.CloudRegion}" } - }); + }, + CustomFields = new Dictionary + { + [btObjIdField] = btObjId.ToString(), + ["region"] = _globalSettings.BaseServiceUri.CloudRegion + } + }; + + _logger.LogInformation("Creating Braintree transaction with Amount: {Amount}, CustomerId: {CustomerId}, " + + "CustomField: {CustomField}, CustomFields: {@CustomFields}", + transactionRequest.Amount, + transactionRequest.CustomerId, + transactionRequest.Options.PayPal.CustomField, + transactionRequest.CustomFields); + + transactionResult = await _btGateway.Transaction.SaleAsync(transactionRequest); } catch (NotFoundException e) { @@ -345,9 +353,19 @@ public class StripeEventUtilityService : IStripeEventUtilityService "Attempted to make a payment with Braintree, but customer did not exist for the given btCustomerId present on the Stripe metadata"); throw; } + catch (Exception e) + { + _logger.LogError(e, "Exception occurred while trying to pay invoice with Braintree"); + throw; + } if (!transactionResult.IsSuccess()) { + _logger.LogWarning("Braintree transaction failed. Error: {ErrorMessage}, Transaction Status: {Status}, Validation Errors: {ValidationErrors}", + transactionResult.Message, + transactionResult.Target?.Status, + string.Join(", ", transactionResult.Errors.DeepAll().Select(e => $"Code: {e.Code}, Message: {e.Message}, Attribute: {e.Attribute}"))); + if (invoice.AttemptCount < 4) { await _mailService.SendPaymentFailedAsync(customer.Email, btInvoiceAmount, true); diff --git a/src/Billing/Services/Implementations/StripeFacade.cs b/src/Billing/Services/Implementations/StripeFacade.cs index 4204946781..91e0c1c33a 100644 --- a/src/Billing/Services/Implementations/StripeFacade.cs +++ b/src/Billing/Services/Implementations/StripeFacade.cs @@ -10,7 +10,6 @@ public class StripeFacade : IStripeFacade private readonly InvoiceService _invoiceService = new(); private readonly PaymentMethodService _paymentMethodService = new(); private readonly SubscriptionService _subscriptionService = new(); - private readonly TaxRateService _taxRateService = new(); private readonly DiscountService _discountService = new(); public async Task GetCharge( @@ -99,13 +98,6 @@ public class StripeFacade : IStripeFacade CancellationToken cancellationToken = default) => await _subscriptionService.CancelAsync(subscriptionId, options, requestOptions, cancellationToken); - public async Task GetTaxRate( - string taxRateId, - TaxRateGetOptions options = null, - RequestOptions requestOptions = null, - CancellationToken cancellationToken = default) => - await _taxRateService.GetAsync(taxRateId, options, requestOptions, cancellationToken); - public async Task DeleteCustomerDiscount( string customerId, RequestOptions requestOptions = null, diff --git a/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs b/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs index 06692ab016..26a1c30c14 100644 --- a/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionDeletedHandler.cs @@ -33,13 +33,16 @@ public class SubscriptionDeletedHandler : ISubscriptionDeletedHandler var subCanceled = subscription.Status == StripeSubscriptionStatus.Canceled; const string providerMigrationCancellationComment = "Cancelled as part of provider migration to Consolidated Billing"; + const string addedToProviderCancellationComment = "Organization was added to Provider"; if (!subCanceled) { return; } - if (organizationId.HasValue && subscription is not { CancellationDetails.Comment: providerMigrationCancellationComment }) + if (organizationId.HasValue && + subscription.CancellationDetails.Comment != providerMigrationCancellationComment && + !subscription.CancellationDetails.Comment.Contains(addedToProviderCancellationComment)) { await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd); } diff --git a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs index 6b4fef43d1..10a1d1a186 100644 --- a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs @@ -1,9 +1,13 @@ using Bit.Billing.Constants; +using Bit.Billing.Jobs; +using Bit.Core; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; +using Quartz; using Stripe; using Event = Stripe.Event; @@ -19,6 +23,9 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler private readonly IUserService _userService; private readonly IPushNotificationService _pushNotificationService; private readonly IOrganizationRepository _organizationRepository; + private readonly ISchedulerFactory _schedulerFactory; + private readonly IFeatureService _featureService; + private readonly IOrganizationEnableCommand _organizationEnableCommand; public SubscriptionUpdatedHandler( IStripeEventService stripeEventService, @@ -28,7 +35,10 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler IOrganizationSponsorshipRenewCommand organizationSponsorshipRenewCommand, IUserService userService, IPushNotificationService pushNotificationService, - IOrganizationRepository organizationRepository) + IOrganizationRepository organizationRepository, + ISchedulerFactory schedulerFactory, + IFeatureService featureService, + IOrganizationEnableCommand organizationEnableCommand) { _stripeEventService = stripeEventService; _stripeEventUtilityService = stripeEventUtilityService; @@ -38,6 +48,9 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler _userService = userService; _pushNotificationService = pushNotificationService; _organizationRepository = organizationRepository; + _schedulerFactory = schedulerFactory; + _featureService = featureService; + _organizationEnableCommand = organizationEnableCommand; } /// @@ -55,6 +68,10 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler when organizationId.HasValue: { await _organizationService.DisableAsync(organizationId.Value, subscription.CurrentPeriodEnd); + if (subscription.Status == StripeSubscriptionStatus.Unpaid) + { + await ScheduleCancellationJobAsync(subscription.Id, organizationId.Value); + } break; } case StripeSubscriptionStatus.Unpaid or StripeSubscriptionStatus.IncompleteExpired: @@ -77,7 +94,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler } case StripeSubscriptionStatus.Active when organizationId.HasValue: { - await _organizationService.EnableAsync(organizationId.Value); + await _organizationEnableCommand.EnableAsync(organizationId.Value); var organization = await _organizationRepository.GetByIdAsync(organizationId.Value); await _pushNotificationService.PushSyncOrganizationStatusAsync(organization); break; @@ -183,4 +200,27 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler await _stripeFacade.DeleteSubscriptionDiscount(subscription.Id); } } + + private async Task ScheduleCancellationJobAsync(string subscriptionId, Guid organizationId) + { + var isResellerManagedOrgAlertEnabled = _featureService.IsEnabled(FeatureFlagKeys.ResellerManagedOrgAlert); + + if (isResellerManagedOrgAlertEnabled) + { + var scheduler = await _schedulerFactory.GetScheduler(); + + var job = JobBuilder.Create() + .WithIdentity($"cancel-sub-{subscriptionId}", "subscription-cancellations") + .UsingJobData("subscriptionId", subscriptionId) + .UsingJobData("organizationId", organizationId.ToString()) + .Build(); + + var trigger = TriggerBuilder.Create() + .WithIdentity($"cancel-trigger-{subscriptionId}", "subscription-cancellations") + .StartAt(DateTimeOffset.UtcNow.AddDays(7)) + .Build(); + + await scheduler.ScheduleJob(job, trigger); + } + } } diff --git a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs index bd496c6974..c52c03b6aa 100644 --- a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs +++ b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs @@ -1,7 +1,6 @@ using Bit.Billing.Constants; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Billing.Constants; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; @@ -161,18 +160,13 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler private async Task TryEnableAutomaticTaxAsync(Subscription subscription) { - var customerGetOptions = new CustomerGetOptions { Expand = ["tax"] }; - var customer = await _stripeFacade.GetCustomer(subscription.CustomerId, customerGetOptions); - - if (subscription.AutomaticTax.Enabled || - customer.Tax?.AutomaticTax != StripeConstants.AutomaticTaxStatus.Supported) + if (subscription.AutomaticTax.Enabled) { return subscription; } var subscriptionUpdateOptions = new SubscriptionUpdateOptions { - DefaultTaxRates = [], AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } }; diff --git a/src/Billing/Startup.cs b/src/Billing/Startup.cs index e3547d943b..e9f2f53488 100644 --- a/src/Billing/Startup.cs +++ b/src/Billing/Startup.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Net.Http.Headers; using Bit.Billing.Services; using Bit.Billing.Services.Implementations; using Bit.Core.Billing.Extensions; @@ -9,6 +10,7 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.SharedWeb.Utilities; using Microsoft.Extensions.DependencyInjection.Extensions; +using Quartz; using Stripe; namespace Bit.Billing; @@ -33,6 +35,7 @@ public class Startup // Settings var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment); services.Configure(Configuration.GetSection("BillingSettings")); + var billingSettings = Configuration.GetSection("BillingSettings").Get(); // Stripe Billing StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey; @@ -96,14 +99,29 @@ public class Startup // Set up HttpClients services.AddHttpClient("FreshdeskApi"); + services.AddHttpClient("OnyxApi", client => + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", billingSettings.Onyx.ApiKey); + }); services.AddScoped(); services.AddScoped(); services.AddScoped(); + // Add Quartz services first + services.AddQuartz(q => + { + q.UseMicrosoftDependencyInjectionJobFactory(); + }); + services.AddQuartzHostedService(); + // Jobs service Jobs.JobsHostedService.AddJobsServices(services); services.AddHostedService(); + + // Swagger + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); } public void Configure( @@ -120,6 +138,11 @@ public class Startup if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Billing API V1"); + }); } app.UseStaticFiles(); diff --git a/src/Billing/appsettings.json b/src/Billing/appsettings.json index 84a67434f5..2a2864b246 100644 --- a/src/Billing/appsettings.json +++ b/src/Billing/appsettings.json @@ -73,6 +73,10 @@ "region": "US", "userFieldName": "cf_user", "orgFieldName": "cf_org" - } + }, + "onyx": { + "apiKey": "SECRET", + "baseUrl": "https://cloud.onyx.app/api" + } } } diff --git a/src/Core/AdminConsole/Entities/Organization.cs b/src/Core/AdminConsole/Entities/Organization.cs index 37e21d7f57..54661e22a7 100644 --- a/src/Core/AdminConsole/Entities/Organization.cs +++ b/src/Core/AdminConsole/Entities/Organization.cs @@ -103,6 +103,12 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, /// public bool AllowAdminAccessToAllCollectionItems { get; set; } + /// + /// If set to true, members can only delete items when they have a Can Manage permission over the collection. + /// If set to false, members can delete items when they have a Can Manage OR Can Edit permission over the collection. + /// + public bool LimitItemDeletion { get; set; } + /// /// Risk Insights is a reporting feature that provides insights into the security of an organization's vault. /// diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs index 6392e483ce..62914f6fa8 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs @@ -23,6 +23,7 @@ public class OrganizationAbility UsePolicies = organization.UsePolicies; LimitCollectionCreation = organization.LimitCollectionCreation; LimitCollectionDeletion = organization.LimitCollectionDeletion; + LimitItemDeletion = organization.LimitItemDeletion; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; UseRiskInsights = organization.UseRiskInsights; } @@ -41,6 +42,7 @@ public class OrganizationAbility public bool UsePolicies { get; set; } public bool LimitCollectionCreation { get; set; } public bool LimitCollectionDeletion { get; set; } + public bool LimitItemDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool UseRiskInsights { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs index e06b6bd66a..18d68af220 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs @@ -56,6 +56,7 @@ public class OrganizationUserOrganizationDetails public int? SmServiceAccounts { get; set; } public bool LimitCollectionCreation { get; set; } public bool LimitCollectionDeletion { get; set; } + public bool LimitItemDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool UseRiskInsights { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyDetails.cs new file mode 100644 index 0000000000..5b5db85f65 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/Organizations/Policies/PolicyDetails.cs @@ -0,0 +1,39 @@ +#nullable enable + +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +/// +/// Represents an OrganizationUser and a Policy which *may* be enforced against them. +/// You may assume that the Policy is enabled and that the organization's plan supports policies. +/// This is consumed by to create requirements for specific policy types. +/// +public class PolicyDetails +{ + public Guid OrganizationUserId { get; set; } + public Guid OrganizationId { get; set; } + public PolicyType PolicyType { get; set; } + public string? PolicyData { get; set; } + public OrganizationUserType OrganizationUserType { get; set; } + public OrganizationUserStatusType OrganizationUserStatus { get; set; } + /// + /// Custom permissions for the organization user, if any. Use + /// to deserialize. + /// + public string? OrganizationUserPermissionsData { get; set; } + /// + /// True if the user is also a ProviderUser for the organization, false otherwise. + /// + public bool IsProvider { get; set; } + + public T GetDataModel() where T : IPolicyDataModel, new() + => CoreHelpers.LoadClassFromJsonData(PolicyData); + + public Permissions GetOrganizationUserCustomPermissions() + => CoreHelpers.LoadClassFromJsonData(OrganizationUserPermissionsData); +} diff --git a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs index bd727f707b..c53ac8745c 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs @@ -146,6 +146,7 @@ public class SelfHostedOrganizationDetails : Organization OwnersNotifiedOfAutoscaling = OwnersNotifiedOfAutoscaling, LimitCollectionCreation = LimitCollectionCreation, LimitCollectionDeletion = LimitCollectionDeletion, + LimitItemDeletion = LimitItemDeletion, AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems, Status = Status }; diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs index 1b2112707c..9d84f60c4c 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs @@ -1,5 +1,6 @@ using System.Net; using System.Text.Json.Serialization; +using Bit.Core.Billing.Enums; using Bit.Core.Enums; using Bit.Core.Utilities; @@ -23,6 +24,7 @@ public class ProviderOrganizationOrganizationDetails public int? OccupiedSeats { get; set; } public int? Seats { get; set; } public string Plan { get; set; } + public PlanType PlanType { get; set; } public OrganizationStatusType Status { get; set; } /// diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs index bd5592edfc..57f176666a 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -42,6 +42,7 @@ public class ProviderUserOrganizationDetails public PlanType PlanType { get; set; } public bool LimitCollectionCreation { get; set; } public bool LimitCollectionDeletion { get; set; } + public bool LimitItemDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } public bool UseRiskInsights { get; set; } public ProviderType ProviderType { get; set; } diff --git a/src/Core/AdminConsole/Models/Mail/DeviceApprovalRequestedViewModel.cs b/src/Core/AdminConsole/Models/Mail/DeviceApprovalRequestedViewModel.cs new file mode 100644 index 0000000000..7f6c932619 --- /dev/null +++ b/src/Core/AdminConsole/Models/Mail/DeviceApprovalRequestedViewModel.cs @@ -0,0 +1,14 @@ +using Bit.Core.Models.Mail; + +namespace Bit.Core.AdminConsole.Models.Mail; + +public class DeviceApprovalRequestedViewModel : BaseMailModel +{ + public Guid OrganizationId { get; set; } + public string UserNameRequestingAccess { get; set; } + + public string Url => string.Format("{0}/organizations/{1}/settings/device-approvals", + WebVaultUrl, + OrganizationId); +} + diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs index c7298e1cd9..0cd5a3295f 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs @@ -6,6 +6,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interface public interface IUpdateOrganizationUserCommand { - Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, + Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId, List? collectionAccess, IEnumerable? groupAccess); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs index c5a4b3da1d..3dd55f9893 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs @@ -1,6 +1,7 @@ #nullable enable using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -49,48 +50,64 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand /// /// Update an organization user. /// - /// The modified user to save. + /// The modified organization user to save. /// The userId of the currently logged in user who is making the change. /// The user's updated collection access. If set to null, this removes all collection access. /// The user's updated group access. If set to null, groups are not updated. /// - public async Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, + public async Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId, List? collectionAccess, IEnumerable? groupAccess) { // Avoid multiple enumeration collectionAccess = collectionAccess?.ToList(); groupAccess = groupAccess?.ToList(); - if (user.Id.Equals(default(Guid))) + if (organizationUser.Id.Equals(default(Guid))) { throw new BadRequestException("Invite the user first."); } - var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id); - if (originalUser == null || user.OrganizationId != originalUser.OrganizationId) + var originalOrganizationUser = await _organizationUserRepository.GetByIdAsync(organizationUser.Id); + if (originalOrganizationUser == null || organizationUser.OrganizationId != originalOrganizationUser.OrganizationId) { throw new NotFoundException(); } + var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId); + if (organization == null) + { + throw new NotFoundException(); + } + + if (organizationUser.UserId.HasValue && organization.PlanType == PlanType.Free && organizationUser.Type is OrganizationUserType.Admin or OrganizationUserType.Owner) + { + // Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this. + var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(organizationUser.UserId.Value); + if (adminCount > 0) + { + throw new BadRequestException("User can only be an admin of one free organization."); + } + } + if (collectionAccess?.Any() == true) { - await ValidateCollectionAccessAsync(originalUser, collectionAccess.ToList()); + await ValidateCollectionAccessAsync(originalOrganizationUser, collectionAccess.ToList()); } if (groupAccess?.Any() == true) { - await ValidateGroupAccessAsync(originalUser, groupAccess.ToList()); + await ValidateGroupAccessAsync(originalOrganizationUser, groupAccess.ToList()); } if (savingUserId.HasValue) { - await _organizationService.ValidateOrganizationUserUpdatePermissions(user.OrganizationId, user.Type, originalUser.Type, user.GetPermissions()); + await _organizationService.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, originalOrganizationUser.Type, organizationUser.GetPermissions()); } - await _organizationService.ValidateOrganizationCustomPermissionsEnabledAsync(user.OrganizationId, user.Type); + await _organizationService.ValidateOrganizationCustomPermissionsEnabledAsync(organizationUser.OrganizationId, organizationUser.Type); - if (user.Type != OrganizationUserType.Owner && - !await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(user.OrganizationId, new[] { user.Id })) + if (organizationUser.Type != OrganizationUserType.Owner && + !await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id })) { throw new BadRequestException("Organization must have at least one confirmed owner."); } @@ -106,26 +123,25 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand // Only autoscale (if required) after all validation has passed so that we know it's a valid request before // updating Stripe - if (!originalUser.AccessSecretsManager && user.AccessSecretsManager) + if (!originalOrganizationUser.AccessSecretsManager && organizationUser.AccessSecretsManager) { - var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1); + var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organizationUser.OrganizationId, 1); if (additionalSmSeatsRequired > 0) { - var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId); var update = new SecretsManagerSubscriptionUpdate(organization, true) - .AdjustSeats(additionalSmSeatsRequired); + .AdjustSeats(additionalSmSeatsRequired); await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update); } } - await _organizationUserRepository.ReplaceAsync(user, collectionAccess); + await _organizationUserRepository.ReplaceAsync(organizationUser, collectionAccess); if (groupAccess != null) { - await _organizationUserRepository.UpdateGroupsAsync(user.Id, groupAccess); + await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupAccess); } - await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated); + await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated); } private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs index df841adf42..94ff3c0059 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/CloudOrganizationSignUpCommand.cs @@ -125,28 +125,8 @@ public class CloudOrganizationSignUpCommand( } else if (plan.Type != PlanType.Free) { - if (featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - var sale = OrganizationSale.From(organization, signup); - await organizationBillingService.Finalize(sale); - } - else - { - if (signup.PaymentMethodType != null) - { - await paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value, - signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats, - signup.PremiumAccessAddon, signup.TaxInfo, signup.IsFromProvider, signup.AdditionalSmSeats.GetValueOrDefault(), - signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial); - } - else - { - await paymentService.PurchaseOrganizationNoPaymentMethod(organization, plan, signup.AdditionalSeats, - signup.PremiumAccessAddon, signup.AdditionalSmSeats.GetValueOrDefault(), - signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial); - } - - } + var sale = OrganizationSale.From(organization, signup); + await organizationBillingService.Finalize(sale); } var ownerId = signup.IsFromProvider ? default : signup.Owner.Id; diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationDeleteCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationDeleteCommand.cs new file mode 100644 index 0000000000..fc4de42bed --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationDeleteCommand.cs @@ -0,0 +1,13 @@ +using Bit.Core.AdminConsole.Entities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; + +public interface IOrganizationDeleteCommand +{ + /// + /// Permanently deletes an organization and performs necessary cleanup. + /// + /// The organization to delete. + /// Thrown when the organization cannot be deleted due to configuration constraints. + Task DeleteAsync(Organization organization); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationEnableCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationEnableCommand.cs new file mode 100644 index 0000000000..522aa04a60 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationEnableCommand.cs @@ -0,0 +1,11 @@ +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; + +public interface IOrganizationEnableCommand +{ + /// + /// Enables an organization that is currently disabled and has a gateway configured. + /// + /// The unique identifier of the organization to enable. + /// When provided, sets the date the organization's subscription will expire. If not provided, no expiration date will be set. + Task EnableAsync(Guid organizationId, DateTime? expirationDate = null); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationInitiateDeleteCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationInitiateDeleteCommand.cs new file mode 100644 index 0000000000..a8d211f245 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/Interfaces/IOrganizationInitiateDeleteCommand.cs @@ -0,0 +1,14 @@ +using Bit.Core.AdminConsole.Entities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; + +public interface IOrganizationInitiateDeleteCommand +{ + /// + /// Initiates a secure deletion process for an organization by requesting confirmation from an organization admin. + /// + /// The organization to be deleted. + /// The email address of the organization admin who will confirm the deletion. + /// Thrown when the specified admin email is invalid or lacks sufficient permissions. + Task InitiateDeleteAsync(Organization organization, string orgAdminEmail); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommand.cs new file mode 100644 index 0000000000..185d5c5ac0 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommand.cs @@ -0,0 +1,69 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Repositories; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Models.Business; +using Bit.Core.Tools.Services; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations; + +public class OrganizationDeleteCommand : IOrganizationDeleteCommand +{ + private readonly IApplicationCacheService _applicationCacheService; + private readonly ICurrentContext _currentContext; + private readonly IOrganizationRepository _organizationRepository; + private readonly IPaymentService _paymentService; + private readonly IReferenceEventService _referenceEventService; + private readonly ISsoConfigRepository _ssoConfigRepository; + + public OrganizationDeleteCommand( + IApplicationCacheService applicationCacheService, + ICurrentContext currentContext, + IOrganizationRepository organizationRepository, + IPaymentService paymentService, + IReferenceEventService referenceEventService, + ISsoConfigRepository ssoConfigRepository) + { + _applicationCacheService = applicationCacheService; + _currentContext = currentContext; + _organizationRepository = organizationRepository; + _paymentService = paymentService; + _referenceEventService = referenceEventService; + _ssoConfigRepository = ssoConfigRepository; + } + + public async Task DeleteAsync(Organization organization) + { + await ValidateDeleteOrganizationAsync(organization); + + if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) + { + try + { + var eop = !organization.ExpirationDate.HasValue || + organization.ExpirationDate.Value >= DateTime.UtcNow; + await _paymentService.CancelSubscriptionAsync(organization, eop); + await _referenceEventService.RaiseEventAsync( + new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext)); + } + catch (GatewayException) { } + } + + await _organizationRepository.DeleteAsync(organization); + await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id); + } + + private async Task ValidateDeleteOrganizationAsync(Organization organization) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); + if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector) + { + throw new BadRequestException("You cannot delete an Organization that is using Key Connector."); + } + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommand.cs new file mode 100644 index 0000000000..660c792563 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommand.cs @@ -0,0 +1,39 @@ +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations; + +public class OrganizationEnableCommand : IOrganizationEnableCommand +{ + private readonly IApplicationCacheService _applicationCacheService; + private readonly IOrganizationRepository _organizationRepository; + + public OrganizationEnableCommand( + IApplicationCacheService applicationCacheService, + IOrganizationRepository organizationRepository) + { + _applicationCacheService = applicationCacheService; + _organizationRepository = organizationRepository; + } + + public async Task EnableAsync(Guid organizationId, DateTime? expirationDate = null) + { + var organization = await _organizationRepository.GetByIdAsync(organizationId); + if (organization is null || organization.Enabled || expirationDate is not null && organization.Gateway is null) + { + return; + } + + organization.Enabled = true; + + if (expirationDate is not null && organization.Gateway is not null) + { + organization.ExpirationDate = expirationDate; + organization.RevisionDate = DateTime.UtcNow; + } + + await _organizationRepository.ReplaceAsync(organization); + await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommand.cs new file mode 100644 index 0000000000..5979adc376 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommand.cs @@ -0,0 +1,49 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Business.Tokenables; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Tokens; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations; + +public class OrganizationInitiateDeleteCommand : IOrganizationInitiateDeleteCommand +{ + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IUserRepository _userRepository; + private readonly IDataProtectorTokenFactory _orgDeleteTokenDataFactory; + private readonly IMailService _mailService; + + public const string OrganizationAdminNotFoundErrorMessage = "Org admin not found."; + + public OrganizationInitiateDeleteCommand( + IOrganizationUserRepository organizationUserRepository, + IUserRepository userRepository, + IDataProtectorTokenFactory orgDeleteTokenDataFactory, + IMailService mailService) + { + _organizationUserRepository = organizationUserRepository; + _userRepository = userRepository; + _orgDeleteTokenDataFactory = orgDeleteTokenDataFactory; + _mailService = mailService; + } + + public async Task InitiateDeleteAsync(Organization organization, string orgAdminEmail) + { + var orgAdmin = await _userRepository.GetByEmailAsync(orgAdminEmail); + if (orgAdmin == null) + { + throw new BadRequestException(OrganizationAdminNotFoundErrorMessage); + } + var orgAdminOrgUser = await _organizationUserRepository.GetDetailsByUserAsync(orgAdmin.Id, organization.Id); + if (orgAdminOrgUser == null || orgAdminOrgUser.Status is not OrganizationUserStatusType.Confirmed || + (orgAdminOrgUser.Type is not OrganizationUserType.Admin and not OrganizationUserType.Owner)) + { + throw new BadRequestException(OrganizationAdminNotFoundErrorMessage); + } + var token = _orgDeleteTokenDataFactory.Protect(new OrgDeleteTokenable(organization, 1)); + await _mailService.SendInitiateDeleteOrganzationEmailAsync(orgAdminEmail, organization, token); + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyRequirementQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyRequirementQuery.cs new file mode 100644 index 0000000000..5736078f22 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/IPolicyRequirementQuery.cs @@ -0,0 +1,18 @@ +#nullable enable + +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies; + +public interface IPolicyRequirementQuery +{ + /// + /// Get a policy requirement for a specific user. + /// The policy requirement represents how one or more policy types should be enforced against the user. + /// It will always return a value even if there are no policies that should be enforced. + /// This should be used for all policy checks. + /// + /// The user that you need to enforce the policy against. + /// The IPolicyRequirement that corresponds to the policy you want to enforce. + Task GetAsync(Guid userId) where T : IPolicyRequirement; +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyRequirementQuery.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyRequirementQuery.cs new file mode 100644 index 0000000000..585d2348ef --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/Implementations/PolicyRequirementQuery.cs @@ -0,0 +1,28 @@ +#nullable enable + +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.Repositories; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; + +public class PolicyRequirementQuery( + IPolicyRepository policyRepository, + IEnumerable> factories) + : IPolicyRequirementQuery +{ + public async Task GetAsync(Guid userId) where T : IPolicyRequirement + { + var factory = factories.OfType>().SingleOrDefault(); + if (factory is null) + { + throw new NotImplementedException("No Policy Requirement found for " + typeof(T)); + } + + return factory(await GetPolicyDetails(userId)); + } + + private Task> GetPolicyDetails(Guid userId) => + policyRepository.GetPolicyDetailsByUserId(userId); +} + diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/IPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/IPolicyRequirement.cs new file mode 100644 index 0000000000..3f331b1130 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/IPolicyRequirement.cs @@ -0,0 +1,24 @@ +#nullable enable + +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +/// +/// Represents the business requirements of how one or more enterprise policies will be enforced against a user. +/// The implementation of this interface will depend on how the policies are enforced in the relevant domain. +/// +public interface IPolicyRequirement; + +/// +/// A factory function that takes a sequence of and transforms them into a single +/// for consumption by the relevant domain. This will receive *all* policy types +/// that may be enforced against a user; when implementing this delegate, you must filter out irrelevant policy types +/// as well as policies that should not be enforced against a user (e.g. due to the user's role or status). +/// +/// +/// See for extension methods to handle common requirements when implementing +/// this delegate. +/// +public delegate T RequirementFactory(IEnumerable policyDetails) + where T : IPolicyRequirement; diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PolicyRequirementHelpers.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PolicyRequirementHelpers.cs new file mode 100644 index 0000000000..fc4cd91a3d --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PolicyRequirementHelpers.cs @@ -0,0 +1,41 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +public static class PolicyRequirementHelpers +{ + /// + /// Filters the PolicyDetails by PolicyType. This is generally required to only get the PolicyDetails that your + /// IPolicyRequirement relates to. + /// + public static IEnumerable GetPolicyType( + this IEnumerable policyDetails, + PolicyType type) + => policyDetails.Where(x => x.PolicyType == type); + + /// + /// Filters the PolicyDetails to remove the specified user roles. This can be used to exempt + /// owners and admins from policy enforcement. + /// + public static IEnumerable ExemptRoles( + this IEnumerable policyDetails, + IEnumerable roles) + => policyDetails.Where(x => !roles.Contains(x.OrganizationUserType)); + + /// + /// Filters the PolicyDetails to remove organization users who are also provider users for the organization. + /// This can be used to exempt provider users from policy enforcement. + /// + public static IEnumerable ExemptProviders(this IEnumerable policyDetails) + => policyDetails.Where(x => !x.IsProvider); + + /// + /// Filters the PolicyDetails to remove the specified organization user statuses. For example, this can be used + /// to exempt users in the invited and revoked statuses from policy enforcement. + /// + public static IEnumerable ExemptStatus( + this IEnumerable policyDetails, IEnumerable status) + => policyDetails.Where(x => !status.Contains(x.OrganizationUserStatus)); +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 4e88976c10..f7b35f2f06 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators; using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services.Implementations; @@ -12,7 +13,14 @@ public static class PolicyServiceCollectionExtensions { services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddPolicyValidators(); + services.AddPolicyRequirements(); + } + + private static void AddPolicyValidators(this IServiceCollection services) + { services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -20,4 +28,34 @@ public static class PolicyServiceCollectionExtensions services.AddScoped(); services.AddScoped(); } + + private static void AddPolicyRequirements(this IServiceCollection services) + { + // Register policy requirement factories here + } + + /// + /// Used to register simple policy requirements where its factory method implements CreateRequirement. + /// This MUST be used rather than calling AddScoped directly, because it will ensure the factory method has + /// the correct type to be injected and then identified by at runtime. + /// + /// The specific PolicyRequirement being registered. + private static void AddPolicyRequirement(this IServiceCollection serviceCollection, RequirementFactory factory) + where T : class, IPolicyRequirement + => serviceCollection.AddPolicyRequirement(_ => factory); + + /// + /// Used to register policy requirements where you need to access additional dependencies (usually to return a + /// curried factory method). + /// This MUST be used rather than calling AddScoped directly, because it will ensure the factory method has + /// the correct type to be injected and then identified by at runtime. + /// + /// + /// A callback that takes IServiceProvider and returns a RequirementFactory for + /// your policy requirement. + /// + private static void AddPolicyRequirement(this IServiceCollection serviceCollection, + Func> factory) + where T : class, IPolicyRequirement + => serviceCollection.AddScoped>(factory); } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs index 6c217cecbe..04984b17be 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidator.cs @@ -111,7 +111,7 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator } await Task.WhenAll(currentActiveRevocableOrganizationUsers.Select(x => - _mailService.SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization.DisplayName(), x.Email))); + _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), x.Email))); } private async Task RemoveNonCompliantUsersAsync(Guid organizationId) diff --git a/src/Core/AdminConsole/Repositories/IGroupRepository.cs b/src/Core/AdminConsole/Repositories/IGroupRepository.cs index 6519b19833..b70331a3f5 100644 --- a/src/Core/AdminConsole/Repositories/IGroupRepository.cs +++ b/src/Core/AdminConsole/Repositories/IGroupRepository.cs @@ -14,11 +14,29 @@ public interface IGroupRepository : IRepository Guid organizationId); Task> GetManyByManyIds(IEnumerable groupIds); Task> GetManyIdsByUserIdAsync(Guid organizationUserId); - Task> GetManyUserIdsByIdAsync(Guid id); + /// + /// Query all OrganizationUserIds who are a member of the specified group. + /// + /// The group id. + /// + /// Whether to use the high-availability database replica. This is for paths with high traffic where immediate data + /// consistency is not required. You generally do not want this. + /// + /// + Task> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false); Task> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId); Task CreateAsync(Group obj, IEnumerable collections); Task ReplaceAsync(Group obj, IEnumerable collections); Task DeleteUserAsync(Guid groupId, Guid organizationUserId); + /// + /// Update a group's members. Replaces all members currently in the group. + /// Ignores members that do not belong to the same organization as the group. + /// Task UpdateUsersAsync(Guid groupId, IEnumerable organizationUserIds); + /// + /// Add members to a group. Gracefully ignores members that are already in the group, + /// duplicate organizationUserIds, and organizationUsers who are not part of the organization. + /// + Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable organizationUserIds); Task DeleteManyAsync(IEnumerable groupIds); } diff --git a/src/Core/AdminConsole/Repositories/IOrganizationRepository.cs b/src/Core/AdminConsole/Repositories/IOrganizationRepository.cs index 5b274d3f88..584d95ffe2 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationRepository.cs +++ b/src/Core/AdminConsole/Repositories/IOrganizationRepository.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Models.Data.Organizations; #nullable enable @@ -22,4 +23,5 @@ public interface IOrganizationRepository : IRepository /// Gets the organizations that have a verified domain matching the user's email domain. /// Task> GetByVerifiedUserEmailDomainAsync(Guid userId); + Task> GetAddableToProviderByUserIdAsync(Guid userId, ProviderType providerType); } diff --git a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs index 516b4614af..8825f9722a 100644 --- a/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs @@ -60,4 +60,12 @@ public interface IOrganizationUserRepository : IRepository> GetManyByOrganizationWithClaimedDomainsAsync(Guid organizationId); Task RevokeManyByIdAsync(IEnumerable organizationUserIds); + + /// + /// Returns a list of OrganizationUsersUserDetails with the specified role. + /// + /// The organization to search within + /// The role to search for + /// A list of OrganizationUsersUserDetails with the specified role + Task> GetManyDetailsByRoleAsync(Guid organizationId, OrganizationUserType role); } diff --git a/src/Core/AdminConsole/Repositories/IPolicyRepository.cs b/src/Core/AdminConsole/Repositories/IPolicyRepository.cs index ad0654dd3c..4c0c03536d 100644 --- a/src/Core/AdminConsole/Repositories/IPolicyRepository.cs +++ b/src/Core/AdminConsole/Repositories/IPolicyRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.Repositories; #nullable enable @@ -8,7 +10,25 @@ namespace Bit.Core.AdminConsole.Repositories; public interface IPolicyRepository : IRepository { + /// + /// Gets all policies of a given type for an organization. + /// + /// + /// WARNING: do not use this to enforce policies against a user! It returns raw data and does not take into account + /// various business rules. Use instead. + /// Task GetByOrganizationIdTypeAsync(Guid organizationId, PolicyType type); Task> GetManyByOrganizationIdAsync(Guid organizationId); Task> GetManyByUserIdAsync(Guid userId); + /// + /// Gets all PolicyDetails for a user for all policy types. + /// + /// + /// Each PolicyDetail represents an OrganizationUser and a Policy which *may* be enforced + /// against them. It only returns PolicyDetails for policies that are enabled and where the organization's plan + /// supports policies. It also excludes "revoked invited" users who are not subject to policy enforcement. + /// This is consumed by to create requirements for specific policy types. + /// You probably do not want to call it directly. + /// + Task> GetPolicyDetailsByUserId(Guid userId); } diff --git a/src/Core/AdminConsole/Services/EventLoggingListenerService.cs b/src/Core/AdminConsole/Services/EventLoggingListenerService.cs new file mode 100644 index 0000000000..60b8789a6b --- /dev/null +++ b/src/Core/AdminConsole/Services/EventLoggingListenerService.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.Hosting; + +namespace Bit.Core.Services; + +public abstract class EventLoggingListenerService : BackgroundService +{ + protected readonly IEventMessageHandler _handler; + + protected EventLoggingListenerService(IEventMessageHandler handler) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + } +} diff --git a/src/Core/AdminConsole/Services/IEventMessageHandler.cs b/src/Core/AdminConsole/Services/IEventMessageHandler.cs new file mode 100644 index 0000000000..83c5e33ecb --- /dev/null +++ b/src/Core/AdminConsole/Services/IEventMessageHandler.cs @@ -0,0 +1,10 @@ +using Bit.Core.Models.Data; + +namespace Bit.Core.Services; + +public interface IEventMessageHandler +{ + Task HandleEventAsync(EventMessage eventMessage); + + Task HandleManyEventsAsync(IEnumerable eventMessages); +} diff --git a/src/Core/AdminConsole/Services/IOrganizationService.cs b/src/Core/AdminConsole/Services/IOrganizationService.cs index 0495c4c76e..683fbe9902 100644 --- a/src/Core/AdminConsole/Services/IOrganizationService.cs +++ b/src/Core/AdminConsole/Services/IOrganizationService.cs @@ -28,12 +28,8 @@ public interface IOrganizationService /// Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey, string privateKey); - Task InitiateDeleteAsync(Organization organization, string orgAdminEmail); - Task DeleteAsync(Organization organization); - Task EnableAsync(Guid organizationId, DateTime? expirationDate); Task DisableAsync(Guid organizationId, DateTime? expirationDate); Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate); - Task EnableAsync(Guid organizationId); Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated); Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type); Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type); diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs new file mode 100644 index 0000000000..4cd71ae77e --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs @@ -0,0 +1,85 @@ +using System.Text; +using System.Text.Json; +using Azure.Messaging.ServiceBus; +using Bit.Core.Models.Data; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Services; + +public class AzureServiceBusEventListenerService : EventLoggingListenerService +{ + private readonly ILogger _logger; + private readonly ServiceBusClient _client; + private readonly ServiceBusProcessor _processor; + + public AzureServiceBusEventListenerService( + IEventMessageHandler handler, + ILogger logger, + GlobalSettings globalSettings, + string subscriptionName) : base(handler) + { + _client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString); + _processor = _client.CreateProcessor(globalSettings.EventLogging.AzureServiceBus.TopicName, subscriptionName, new ServiceBusProcessorOptions()); + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + _processor.ProcessMessageAsync += async args => + { + try + { + using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(args.Message.Body)); + var root = jsonDocument.RootElement; + + if (root.ValueKind == JsonValueKind.Array) + { + var eventMessages = root.Deserialize>(); + await _handler.HandleManyEventsAsync(eventMessages); + } + else if (root.ValueKind == JsonValueKind.Object) + { + var eventMessage = root.Deserialize(); + await _handler.HandleEventAsync(eventMessage); + + } + await args.CompleteMessageAsync(args.Message); + } + catch (Exception exception) + { + _logger.LogError( + exception, + "An error occured while processing message: {MessageId}", + args.Message.MessageId + ); + } + }; + + _processor.ProcessErrorAsync += args => + { + _logger.LogError( + args.Exception, + "An error occurred. Entity Path: {EntityPath}, Error Source: {ErrorSource}", + args.EntityPath, + args.ErrorSource + ); + return Task.CompletedTask; + }; + + await _processor.StartProcessingAsync(cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await _processor.StopProcessingAsync(cancellationToken); + await base.StopAsync(cancellationToken); + } + + public override void Dispose() + { + _processor.DisposeAsync().GetAwaiter().GetResult(); + _client.DisposeAsync().GetAwaiter().GetResult(); + base.Dispose(); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs new file mode 100644 index 0000000000..fc865b327c --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using Azure.Messaging.ServiceBus; +using Bit.Core.Models.Data; +using Bit.Core.Services; +using Bit.Core.Settings; + +namespace Bit.Core.AdminConsole.Services.Implementations; + +public class AzureServiceBusEventWriteService : IEventWriteService, IAsyncDisposable +{ + private readonly ServiceBusClient _client; + private readonly ServiceBusSender _sender; + + public AzureServiceBusEventWriteService(GlobalSettings globalSettings) + { + _client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString); + _sender = _client.CreateSender(globalSettings.EventLogging.AzureServiceBus.TopicName); + } + + public async Task CreateAsync(IEvent e) + { + var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(e)) + { + ContentType = "application/json" + }; + + await _sender.SendMessageAsync(message); + } + + public async Task CreateManyAsync(IEnumerable events) + { + var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(events)) + { + ContentType = "application/json" + }; + + await _sender.SendMessageAsync(message); + } + + public async ValueTask DisposeAsync() + { + await _sender.DisposeAsync(); + await _client.DisposeAsync(); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs b/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs new file mode 100644 index 0000000000..aa545913b1 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs @@ -0,0 +1,19 @@ +using Bit.Core.Models.Data; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Services; + +public class AzureTableStorageEventHandler( + [FromKeyedServices("persistent")] IEventWriteService eventWriteService) + : IEventMessageHandler +{ + public Task HandleEventAsync(EventMessage eventMessage) + { + return eventWriteService.CreateManyAsync(EventTableEntity.IndexEvent(eventMessage)); + } + + public Task HandleManyEventsAsync(IEnumerable eventMessages) + { + return eventWriteService.CreateManyAsync(eventMessages.SelectMany(EventTableEntity.IndexEvent)); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs new file mode 100644 index 0000000000..ee3a2d5db2 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs @@ -0,0 +1,19 @@ +using Bit.Core.Models.Data; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Services; + +public class EventRepositoryHandler( + [FromKeyedServices("persistent")] IEventWriteService eventWriteService) + : IEventMessageHandler +{ + public Task HandleEventAsync(EventMessage eventMessage) + { + return eventWriteService.CreateAsync(eventMessage); + } + + public Task HandleManyEventsAsync(IEnumerable eventMessages) + { + return eventWriteService.CreateManyAsync(eventMessages); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 56467a661a..284c11cc78 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -4,7 +4,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Business; -using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; @@ -68,7 +67,6 @@ public class OrganizationService : IOrganizationService private readonly IProviderUserRepository _providerUserRepository; private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery; private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand; - private readonly IDataProtectorTokenFactory _orgDeleteTokenDataFactory; private readonly IProviderRepository _providerRepository; private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory; private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory; @@ -106,7 +104,6 @@ public class OrganizationService : IOrganizationService IOrgUserInviteTokenableFactory orgUserInviteTokenableFactory, IDataProtectorTokenFactory orgUserInviteTokenDataFactory, IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand, - IDataProtectorTokenFactory orgDeleteTokenDataFactory, IProviderRepository providerRepository, IFeatureService featureService, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, @@ -139,7 +136,6 @@ public class OrganizationService : IOrganizationService _providerUserRepository = providerUserRepository; _countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery; _updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand; - _orgDeleteTokenDataFactory = orgDeleteTokenDataFactory; _providerRepository = providerRepository; _orgUserInviteTokenableFactory = orgUserInviteTokenableFactory; _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; @@ -690,56 +686,6 @@ public class OrganizationService : IOrganizationService } } - public async Task InitiateDeleteAsync(Organization organization, string orgAdminEmail) - { - var orgAdmin = await _userRepository.GetByEmailAsync(orgAdminEmail); - if (orgAdmin == null) - { - throw new BadRequestException("Org admin not found."); - } - var orgAdminOrgUser = await _organizationUserRepository.GetDetailsByUserAsync(orgAdmin.Id, organization.Id); - if (orgAdminOrgUser == null || orgAdminOrgUser.Status != OrganizationUserStatusType.Confirmed || - (orgAdminOrgUser.Type != OrganizationUserType.Admin && orgAdminOrgUser.Type != OrganizationUserType.Owner)) - { - throw new BadRequestException("Org admin not found."); - } - var token = _orgDeleteTokenDataFactory.Protect(new OrgDeleteTokenable(organization, 1)); - await _mailService.SendInitiateDeleteOrganzationEmailAsync(orgAdminEmail, organization, token); - } - - public async Task DeleteAsync(Organization organization) - { - await ValidateDeleteOrganizationAsync(organization); - - if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) - { - try - { - var eop = !organization.ExpirationDate.HasValue || - organization.ExpirationDate.Value >= DateTime.UtcNow; - await _paymentService.CancelSubscriptionAsync(organization, eop); - await _referenceEventService.RaiseEventAsync( - new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext)); - } - catch (GatewayException) { } - } - - await _organizationRepository.DeleteAsync(organization); - await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id); - } - - public async Task EnableAsync(Guid organizationId, DateTime? expirationDate) - { - var org = await GetOrgById(organizationId); - if (org != null && !org.Enabled && org.Gateway.HasValue) - { - org.Enabled = true; - org.ExpirationDate = expirationDate; - org.RevisionDate = DateTime.UtcNow; - await ReplaceAndUpdateCacheAsync(org); - } - } - public async Task DisableAsync(Guid organizationId, DateTime? expirationDate) { var org = await GetOrgById(organizationId); @@ -765,16 +711,6 @@ public class OrganizationService : IOrganizationService } } - public async Task EnableAsync(Guid organizationId) - { - var org = await GetOrgById(organizationId); - if (org != null && !org.Enabled) - { - org.Enabled = true; - await ReplaceAndUpdateCacheAsync(org); - } - } - public async Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated) { if (organization.Id == default(Guid)) @@ -1336,6 +1272,12 @@ public class OrganizationService : IOrganizationService } } + var subscription = await _paymentService.GetSubscriptionAsync(organization); + if (subscription?.Subscription?.Status == StripeConstants.SubscriptionStatus.Canceled) + { + return (false, "You do not have an active subscription. Reinstate your subscription to make changes"); + } + if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && organization.MaxAutoscaleSeats.Value < organization.Seats.Value + seatsToAdd) @@ -1343,12 +1285,6 @@ public class OrganizationService : IOrganizationService return (false, $"Seat limit has been reached."); } - var subscription = await _paymentService.GetSubscriptionAsync(organization); - if (subscription?.Subscription?.Status == StripeConstants.SubscriptionStatus.Canceled) - { - return (false, "Cannot autoscale with a canceled subscription."); - } - return (true, failureReason); } @@ -1978,15 +1914,6 @@ public class OrganizationService : IOrganizationService return true; } - private async Task ValidateDeleteOrganizationAsync(Organization organization) - { - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); - if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector) - { - throw new BadRequestException("You cannot delete an Organization that is using Key Connector."); - } - } - public async Task RevokeUserAsync(OrganizationUser organizationUser, Guid? revokingUserId) { if (revokingUserId.HasValue && organizationUser.UserId == revokingUserId.Value) @@ -2002,6 +1929,11 @@ public class OrganizationService : IOrganizationService await RepositoryRevokeUserAsync(organizationUser); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked); + + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } } public async Task RevokeUserAsync(OrganizationUser organizationUser, @@ -2009,6 +1941,11 @@ public class OrganizationService : IOrganizationService { await RepositoryRevokeUserAsync(organizationUser); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser); + + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } } private async Task RepositoryRevokeUserAsync(OrganizationUser organizationUser) @@ -2074,6 +2011,10 @@ public class OrganizationService : IOrganizationService await _organizationUserRepository.RevokeAsync(organizationUser.Id); organizationUser.Status = OrganizationUserStatusType.Revoked; await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked); + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } result.Add(Tuple.Create(organizationUser, "")); } @@ -2101,12 +2042,22 @@ public class OrganizationService : IOrganizationService await RepositoryRestoreUserAsync(organizationUser); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); + + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } } public async Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser) { await RepositoryRestoreUserAsync(organizationUser); await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, systemUser); + + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } } private async Task RepositoryRestoreUserAsync(OrganizationUser organizationUser) @@ -2165,7 +2116,8 @@ public class OrganizationService : IOrganizationService // Query Two Factor Authentication status for all users in the organization // This is an optimization to avoid querying the Two Factor Authentication status for each user individually - var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(filteredUsers.Select(ou => ou.UserId.Value)); + var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync( + filteredUsers.Where(ou => ou.UserId.HasValue).Select(ou => ou.UserId.Value)); var result = new List>(); @@ -2188,7 +2140,8 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Only owners can restore other owners."); } - var twoFactorIsEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(ou => ou.userId == organizationUser.UserId.Value).twoFactorIsEnabled; + var twoFactorIsEnabled = organizationUser.UserId.HasValue + && organizationUsersTwoFactorEnabled.FirstOrDefault(ou => ou.userId == organizationUser.UserId.Value).twoFactorIsEnabled; await CheckPoliciesBeforeRestoreAsync(organizationUser, twoFactorIsEnabled); var status = GetPriorActiveOrganizationUserStatusType(organizationUser); @@ -2196,6 +2149,10 @@ public class OrganizationService : IOrganizationService await _organizationUserRepository.RestoreAsync(organizationUser.Id, status); organizationUser.Status = status; await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); + if (_featureService.IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) && organizationUser.UserId.HasValue) + { + await _pushNotificationService.PushSyncOrgKeysAsync(organizationUser.UserId.Value); + } result.Add(Tuple.Create(organizationUser, "")); } @@ -2247,7 +2204,7 @@ public class OrganizationService : IOrganizationService if (!userHasTwoFactorEnabled) { var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId, - PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited); + PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Revoked); if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId)) { twoFactorCompliant = false; diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs new file mode 100644 index 0000000000..1ee3fa5ea7 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs @@ -0,0 +1,103 @@ +using System.Text; +using System.Text.Json; +using Bit.Core.Models.Data; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace Bit.Core.Services; + +public class RabbitMqEventListenerService : EventLoggingListenerService +{ + private IChannel _channel; + private IConnection _connection; + private readonly string _exchangeName; + private readonly ConnectionFactory _factory; + private readonly ILogger _logger; + private readonly string _queueName; + + public RabbitMqEventListenerService( + IEventMessageHandler handler, + ILogger logger, + GlobalSettings globalSettings, + string queueName) : base(handler) + { + _factory = new ConnectionFactory + { + HostName = globalSettings.EventLogging.RabbitMq.HostName, + UserName = globalSettings.EventLogging.RabbitMq.Username, + Password = globalSettings.EventLogging.RabbitMq.Password + }; + _exchangeName = globalSettings.EventLogging.RabbitMq.ExchangeName; + _logger = logger; + _queueName = queueName; + } + + public override async Task StartAsync(CancellationToken cancellationToken) + { + _connection = await _factory.CreateConnectionAsync(cancellationToken); + _channel = await _connection.CreateChannelAsync(cancellationToken: cancellationToken); + + await _channel.ExchangeDeclareAsync(exchange: _exchangeName, + type: ExchangeType.Fanout, + durable: true, + cancellationToken: cancellationToken); + await _channel.QueueDeclareAsync(queue: _queueName, + durable: true, + exclusive: false, + autoDelete: false, + arguments: null, + cancellationToken: cancellationToken); + await _channel.QueueBindAsync(queue: _queueName, + exchange: _exchangeName, + routingKey: string.Empty, + cancellationToken: cancellationToken); + await base.StartAsync(cancellationToken); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + var consumer = new AsyncEventingBasicConsumer(_channel); + consumer.ReceivedAsync += async (_, eventArgs) => + { + try + { + using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(eventArgs.Body.Span)); + var root = jsonDocument.RootElement; + + if (root.ValueKind == JsonValueKind.Array) + { + var eventMessages = root.Deserialize>(); + await _handler.HandleManyEventsAsync(eventMessages); + } + else if (root.ValueKind == JsonValueKind.Object) + { + var eventMessage = root.Deserialize(); + await _handler.HandleEventAsync(eventMessage); + + } + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred while processing the message"); + } + }; + + await _channel.BasicConsumeAsync(_queueName, autoAck: true, consumer: consumer, cancellationToken: cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + await _channel.CloseAsync(cancellationToken); + await _connection.CloseAsync(cancellationToken); + await base.StopAsync(cancellationToken); + } + + public override void Dispose() + { + _channel.Dispose(); + _connection.Dispose(); + base.Dispose(); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs new file mode 100644 index 0000000000..86abddec58 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs @@ -0,0 +1,62 @@ +using System.Text.Json; +using Bit.Core.Models.Data; +using Bit.Core.Settings; +using RabbitMQ.Client; + +namespace Bit.Core.Services; +public class RabbitMqEventWriteService : IEventWriteService, IAsyncDisposable +{ + private readonly ConnectionFactory _factory; + private readonly Lazy> _lazyConnection; + private readonly string _exchangeName; + + public RabbitMqEventWriteService(GlobalSettings globalSettings) + { + _factory = new ConnectionFactory + { + HostName = globalSettings.EventLogging.RabbitMq.HostName, + UserName = globalSettings.EventLogging.RabbitMq.Username, + Password = globalSettings.EventLogging.RabbitMq.Password + }; + _exchangeName = globalSettings.EventLogging.RabbitMq.ExchangeName; + + _lazyConnection = new Lazy>(CreateConnectionAsync); + } + + public async Task CreateAsync(IEvent e) + { + var connection = await _lazyConnection.Value; + using var channel = await connection.CreateChannelAsync(); + + await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Fanout, durable: true); + + var body = JsonSerializer.SerializeToUtf8Bytes(e); + + await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body); + } + + public async Task CreateManyAsync(IEnumerable events) + { + var connection = await _lazyConnection.Value; + using var channel = await connection.CreateChannelAsync(); + await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Fanout, durable: true); + + var body = JsonSerializer.SerializeToUtf8Bytes(events); + + await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body); + } + + public async ValueTask DisposeAsync() + { + if (_lazyConnection.IsValueCreated) + { + var connection = await _lazyConnection.Value; + await connection.DisposeAsync(); + } + } + + private async Task CreateConnectionAsync() + { + return await _factory.CreateConnectionAsync(); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs b/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs new file mode 100644 index 0000000000..d152f9011b --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs @@ -0,0 +1,30 @@ +using System.Net.Http.Json; +using Bit.Core.Models.Data; +using Bit.Core.Settings; + +namespace Bit.Core.Services; + +public class WebhookEventHandler( + IHttpClientFactory httpClientFactory, + GlobalSettings globalSettings) + : IEventMessageHandler +{ + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName); + private readonly string _webhookUrl = globalSettings.EventLogging.WebhookUrl; + + public const string HttpClientName = "WebhookEventHandlerHttpClient"; + + public async Task HandleEventAsync(EventMessage eventMessage) + { + var content = JsonContent.Create(eventMessage); + var response = await _httpClient.PostAsync(_webhookUrl, content); + response.EnsureSuccessStatusCode(); + } + + public async Task HandleManyEventsAsync(IEnumerable eventMessages) + { + var content = JsonContent.Create(eventMessages); + var response = await _httpClient.PostAsync(_webhookUrl, content); + response.EnsureSuccessStatusCode(); + } +} diff --git a/src/Core/Auth/Enums/TwoFactorProviderType.cs b/src/Core/Auth/Enums/TwoFactorProviderType.cs index a17b61c3cd..07a52dc429 100644 --- a/src/Core/Auth/Enums/TwoFactorProviderType.cs +++ b/src/Core/Auth/Enums/TwoFactorProviderType.cs @@ -10,4 +10,5 @@ public enum TwoFactorProviderType : byte Remember = 5, OrganizationDuo = 6, WebAuthn = 7, + RecoveryCode = 8, } diff --git a/src/Core/Auth/Models/Api/Request/Accounts/KeysRequestModel.cs b/src/Core/Auth/Models/Api/Request/Accounts/KeysRequestModel.cs index 93832542de..0964fe1a1d 100644 --- a/src/Core/Auth/Models/Api/Request/Accounts/KeysRequestModel.cs +++ b/src/Core/Auth/Models/Api/Request/Accounts/KeysRequestModel.cs @@ -1,26 +1,36 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Entities; +using Bit.Core.Utilities; namespace Bit.Core.Auth.Models.Api.Request.Accounts; public class KeysRequestModel { + [Required] public string PublicKey { get; set; } [Required] public string EncryptedPrivateKey { get; set; } public User ToUser(User existingUser) { - if (string.IsNullOrWhiteSpace(existingUser.PublicKey) && !string.IsNullOrWhiteSpace(PublicKey)) + if (string.IsNullOrWhiteSpace(PublicKey) || string.IsNullOrWhiteSpace(EncryptedPrivateKey)) + { + throw new InvalidOperationException("Public and private keys are required."); + } + + if (string.IsNullOrWhiteSpace(existingUser.PublicKey) && string.IsNullOrWhiteSpace(existingUser.PrivateKey)) { existingUser.PublicKey = PublicKey; - } - - if (string.IsNullOrWhiteSpace(existingUser.PrivateKey)) - { existingUser.PrivateKey = EncryptedPrivateKey; + return existingUser; + } + else if (PublicKey == existingUser.PublicKey && CoreHelpers.FixedTimeEquals(EncryptedPrivateKey, existingUser.PrivateKey)) + { + return existingUser; + } + else + { + throw new InvalidOperationException("Cannot replace existing key(s) with new key(s)."); } - - return existingUser; } } diff --git a/src/Core/Auth/Services/Implementations/AuthRequestService.cs b/src/Core/Auth/Services/Implementations/AuthRequestService.cs index f83c5de1f6..b70a690338 100644 --- a/src/Core/Auth/Services/Implementations/AuthRequestService.cs +++ b/src/Core/Auth/Services/Implementations/AuthRequestService.cs @@ -12,6 +12,7 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; +using Microsoft.Extensions.Logging; #nullable enable @@ -27,6 +28,9 @@ public class AuthRequestService : IAuthRequestService private readonly IPushNotificationService _pushNotificationService; private readonly IEventService _eventService; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IMailService _mailService; + private readonly IFeatureService _featureService; + private readonly ILogger _logger; public AuthRequestService( IAuthRequestRepository authRequestRepository, @@ -36,7 +40,10 @@ public class AuthRequestService : IAuthRequestService ICurrentContext currentContext, IPushNotificationService pushNotificationService, IEventService eventService, - IOrganizationUserRepository organizationRepository) + IOrganizationUserRepository organizationRepository, + IMailService mailService, + IFeatureService featureService, + ILogger logger) { _authRequestRepository = authRequestRepository; _userRepository = userRepository; @@ -46,6 +53,9 @@ public class AuthRequestService : IAuthRequestService _pushNotificationService = pushNotificationService; _eventService = eventService; _organizationUserRepository = organizationRepository; + _mailService = mailService; + _featureService = featureService; + _logger = logger; } public async Task GetAuthRequestAsync(Guid id, Guid userId) @@ -132,6 +142,8 @@ public class AuthRequestService : IAuthRequestService { var createdAuthRequest = await CreateAuthRequestAsync(model, user, organizationUser.OrganizationId); firstAuthRequest ??= createdAuthRequest; + + await NotifyAdminsOfDeviceApprovalRequestAsync(organizationUser, user); } // I know this won't be null because I have already validated that at least one organization exists @@ -276,4 +288,43 @@ public class AuthRequestService : IAuthRequestService { return DateTime.UtcNow > savedDate.Add(allowedLifetime); } + + private async Task NotifyAdminsOfDeviceApprovalRequestAsync(OrganizationUser organizationUser, User user) + { + if (!_featureService.IsEnabled(FeatureFlagKeys.DeviceApprovalRequestAdminNotifications)) + { + _logger.LogWarning("Skipped sending device approval notification to admins - feature flag disabled"); + return; + } + + var adminEmails = await GetAdminAndAccountRecoveryEmailsAsync(organizationUser.OrganizationId); + + await _mailService.SendDeviceApprovalRequestedNotificationEmailAsync( + adminEmails, + organizationUser.OrganizationId, + user.Email, + user.Name); + } + + /// + /// Returns a list of emails for admins and custom users with the ManageResetPassword permission. + /// + /// The organization to search within + private async Task> GetAdminAndAccountRecoveryEmailsAsync(Guid organizationId) + { + var admins = await _organizationUserRepository.GetManyByMinimumRoleAsync( + organizationId, + OrganizationUserType.Admin); + + var customUsers = await _organizationUserRepository.GetManyDetailsByRoleAsync( + organizationId, + OrganizationUserType.Custom); + + return admins.Select(a => a.Email) + .Concat(customUsers + .Where(a => a.GetPermissions().ManageResetPassword) + .Select(a => a.Email)) + .Distinct() + .ToList(); + } } diff --git a/src/Core/Billing/Constants/PlanConstants.cs b/src/Core/Billing/Constants/PlanConstants.cs new file mode 100644 index 0000000000..1ac5b8e750 --- /dev/null +++ b/src/Core/Billing/Constants/PlanConstants.cs @@ -0,0 +1,30 @@ +using Bit.Core.Billing.Enums; + +namespace Bit.Core.Billing.Constants; + +public static class PlanConstants +{ + public static List EnterprisePlanTypes => + [ + PlanType.EnterpriseAnnually2019, + PlanType.EnterpriseAnnually2020, + PlanType.EnterpriseAnnually2023, + PlanType.EnterpriseAnnually, + PlanType.EnterpriseMonthly2019, + PlanType.EnterpriseMonthly2020, + PlanType.EnterpriseMonthly2023, + PlanType.EnterpriseMonthly + ]; + + public static List TeamsPlanTypes => + [ + PlanType.TeamsAnnually2019, + PlanType.TeamsAnnually2020, + PlanType.TeamsAnnually2023, + PlanType.TeamsAnnually, + PlanType.TeamsMonthly2019, + PlanType.TeamsMonthly2020, + PlanType.TeamsMonthly2023, + PlanType.TeamsMonthly + ]; +} diff --git a/src/Core/Billing/Constants/StripeConstants.cs b/src/Core/Billing/Constants/StripeConstants.cs index 7371b8b7e9..e3c2b7245e 100644 --- a/src/Core/Billing/Constants/StripeConstants.cs +++ b/src/Core/Billing/Constants/StripeConstants.cs @@ -31,6 +31,16 @@ public static class StripeConstants public const string TaxIdInvalid = "tax_id_invalid"; } + public static class InvoiceStatus + { + public const string Draft = "draft"; + } + + public static class MetadataKeys + { + public const string OrganizationId = "organizationId"; + } + public static class PaymentBehavior { public const string DefaultIncomplete = "default_incomplete"; diff --git a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs index 1aac7bb1d8..e436102012 100644 --- a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs +++ b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs @@ -22,16 +22,9 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory { new(nameof(OrganizationLicenseConstants.LicenseType), LicenseType.Organization.ToString()), - new Claim(nameof(OrganizationLicenseConstants.LicenseKey), entity.LicenseKey), - new(nameof(OrganizationLicenseConstants.InstallationId), licenseContext.InstallationId.ToString()), new(nameof(OrganizationLicenseConstants.Id), entity.Id.ToString()), - new(nameof(OrganizationLicenseConstants.Name), entity.Name), - new(nameof(OrganizationLicenseConstants.BillingEmail), entity.BillingEmail), new(nameof(OrganizationLicenseConstants.Enabled), entity.Enabled.ToString()), - new(nameof(OrganizationLicenseConstants.Plan), entity.Plan), new(nameof(OrganizationLicenseConstants.PlanType), entity.PlanType.ToString()), - new(nameof(OrganizationLicenseConstants.Seats), entity.Seats.ToString()), - new(nameof(OrganizationLicenseConstants.MaxCollections), entity.MaxCollections.ToString()), new(nameof(OrganizationLicenseConstants.UsePolicies), entity.UsePolicies.ToString()), new(nameof(OrganizationLicenseConstants.UseSso), entity.UseSso.ToString()), new(nameof(OrganizationLicenseConstants.UseKeyConnector), entity.UseKeyConnector.ToString()), @@ -43,32 +36,79 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory { new(nameof(UserLicenseConstants.LicenseType), LicenseType.User.ToString()), 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.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)), new(nameof(UserLicenseConstants.Trial), trial.ToString()), }; + if (entity.Email is not null) + { + claims.Add(new(nameof(UserLicenseConstants.Email), entity.Email)); + } + + if (entity.Name is not null) + { + claims.Add(new(nameof(UserLicenseConstants.Name), entity.Name)); + } + if (entity.LicenseKey is not null) { claims.Add(new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey)); } - if (entity.MaxStorageGb is not null) + if (entity.MaxStorageGb.HasValue) { claims.Add(new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString())); } - if (expires is not null) + if (expires.HasValue) { - claims.Add(new(nameof(UserLicenseConstants.Expires), expires.ToString())); + claims.Add(new(nameof(UserLicenseConstants.Expires), expires.Value.ToString(CultureInfo.InvariantCulture))); } - if (refresh is not null) + if (refresh.HasValue) { - claims.Add(new(nameof(UserLicenseConstants.Refresh), refresh.ToString())); + claims.Add(new(nameof(UserLicenseConstants.Refresh), refresh.Value.ToString(CultureInfo.InvariantCulture))); } return Task.FromResult(claims); diff --git a/src/Core/Billing/Models/AddableOrganization.cs b/src/Core/Billing/Models/AddableOrganization.cs new file mode 100644 index 0000000000..fe6d5458bd --- /dev/null +++ b/src/Core/Billing/Models/AddableOrganization.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Billing.Models; + +public record AddableOrganization( + Guid Id, + string Name, + string Plan, + int Seats, + bool Disabled = false); diff --git a/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs b/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs index df08296083..33b9578d0e 100644 --- a/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs +++ b/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs @@ -5,6 +5,7 @@ namespace Bit.Core.Billing.Models.Mail; public class TrialInitiationVerifyEmail : RegisterVerifyEmail { + public bool IsExistingUser { get; set; } /// /// See comment on . /// @@ -26,8 +27,18 @@ public class TrialInitiationVerifyEmail : RegisterVerifyEmail /// Currently we only support one product type at a time, despite Product being a collection. /// If we receive both PasswordManager and SecretsManager, we'll send the user to the PM trial route /// - private string Route => - Product.Any(p => p == ProductType.PasswordManager) - ? "trial-initiation" - : "secrets-manager-trial-initiation"; + private string Route + { + get + { + if (IsExistingUser) + { + return "create-organization"; + } + + return Product.Any(p => p == ProductType.PasswordManager) + ? "trial-initiation" + : "secrets-manager-trial-initiation"; + } + } } diff --git a/src/Core/Billing/Models/Sales/SubscriptionSetup.cs b/src/Core/Billing/Models/Sales/SubscriptionSetup.cs index cd87b2bb1c..39a80a776a 100644 --- a/src/Core/Billing/Models/Sales/SubscriptionSetup.cs +++ b/src/Core/Billing/Models/Sales/SubscriptionSetup.cs @@ -9,6 +9,7 @@ public class SubscriptionSetup public required Plan Plan { get; set; } public required PasswordManager PasswordManagerOptions { get; set; } public SecretsManager? SecretsManagerOptions { get; set; } + public bool SkipTrial = false; public class PasswordManager { diff --git a/src/Core/Billing/Services/IPremiumUserBillingService.cs b/src/Core/Billing/Services/IPremiumUserBillingService.cs index f74bf6c8da..b3bb580e2d 100644 --- a/src/Core/Billing/Services/IPremiumUserBillingService.cs +++ b/src/Core/Billing/Services/IPremiumUserBillingService.cs @@ -1,10 +1,13 @@ -using Bit.Core.Billing.Models.Sales; +using Bit.Core.Billing.Models; +using Bit.Core.Billing.Models.Sales; using Bit.Core.Entities; namespace Bit.Core.Billing.Services; public interface IPremiumUserBillingService { + Task Credit(User user, decimal amount); + /// /// Establishes the Stripe entities necessary for a Bitwarden using the provided . /// @@ -27,4 +30,9 @@ public interface IPremiumUserBillingService /// /// Task Finalize(PremiumUserSale sale); + + Task UpdatePaymentMethod( + User user, + TokenizedPaymentSource tokenizedPaymentSource, + TaxInformation taxInformation); } diff --git a/src/Core/Billing/Services/IProviderBillingService.cs b/src/Core/Billing/Services/IProviderBillingService.cs index 20e7407628..d6983da03e 100644 --- a/src/Core/Billing/Services/IProviderBillingService.cs +++ b/src/Core/Billing/Services/IProviderBillingService.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Models; using Bit.Core.Billing.Services.Contracts; using Bit.Core.Models.Business; using Stripe; @@ -10,6 +11,11 @@ namespace Bit.Core.Billing.Services; public interface IProviderBillingService { + Task AddExistingOrganization( + Provider provider, + Organization organization, + string key); + /// /// Changes the assigned provider plan for the provider. /// @@ -35,6 +41,10 @@ public interface IProviderBillingService Task GenerateClientInvoiceReport( string invoiceId); + Task> GetAddableOrganizations( + Provider provider, + Guid userId); + /// /// Scales the 's seats for the specified using the provided . /// This operation may autoscale the provider's Stripe depending on the 's seat minimum for the diff --git a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs index ec9770c59e..57a4b1781b 100644 --- a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs +++ b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs @@ -356,11 +356,20 @@ public class OrganizationBillingService( } } + var customerHasTaxInfo = customer is + { + Address: + { + Country: not null and not "", + PostalCode: not null and not "" + } + }; + var subscriptionCreateOptions = new SubscriptionCreateOptions { AutomaticTax = new SubscriptionAutomaticTaxOptions { - Enabled = customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported + Enabled = customerHasTaxInfo }, CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, Customer = customer.Id, @@ -370,7 +379,7 @@ public class OrganizationBillingService( ["organizationId"] = organizationId.ToString() }, OffSession = true, - TrialPeriodDays = plan.TrialPeriodDays + TrialPeriodDays = subscriptionSetup.SkipTrial ? 0 : plan.TrialPeriodDays }; return await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); diff --git a/src/Core/Billing/Services/Implementations/PaymentHistoryService.cs b/src/Core/Billing/Services/Implementations/PaymentHistoryService.cs index 69e1a4cfba..6e984f946e 100644 --- a/src/Core/Billing/Services/Implementations/PaymentHistoryService.cs +++ b/src/Core/Billing/Services/Implementations/PaymentHistoryService.cs @@ -28,7 +28,6 @@ public class PaymentHistoryService( var invoices = await stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions { Customer = subscriber.GatewayCustomerId, - Subscription = subscriber.GatewaySubscriptionId, Limit = pageSize, Status = status, StartingAfter = startAfter diff --git a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs index 306ee88eaf..6b9f32e8f9 100644 --- a/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs +++ b/src/Core/Billing/Services/Implementations/PremiumUserBillingService.cs @@ -1,5 +1,6 @@ using Bit.Core.Billing.Caches; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Sales; using Bit.Core.Entities; using Bit.Core.Enums; @@ -26,6 +27,57 @@ public class PremiumUserBillingService( ISubscriberService subscriberService, IUserRepository userRepository) : IPremiumUserBillingService { + public async Task Credit(User user, decimal amount) + { + var customer = await subscriberService.GetCustomer(user); + + // Negative credit represents a balance and all Stripe denomination is in cents. + var credit = (long)amount * -100; + + if (customer == null) + { + var options = new CustomerCreateOptions + { + Balance = credit, + Description = user.Name, + Email = user.Email, + InvoiceSettings = new CustomerInvoiceSettingsOptions + { + CustomFields = + [ + new CustomerInvoiceSettingsCustomFieldOptions + { + Name = user.SubscriberType(), + Value = user.SubscriberName().Length <= 30 + ? user.SubscriberName() + : user.SubscriberName()[..30] + } + ] + }, + Metadata = new Dictionary + { + ["region"] = globalSettings.BaseServiceUri.CloudRegion, + ["userId"] = user.Id.ToString() + } + }; + + customer = await stripeAdapter.CustomerCreateAsync(options); + + user.Gateway = GatewayType.Stripe; + user.GatewayCustomerId = customer.Id; + await userRepository.ReplaceAsync(user); + } + else + { + var options = new CustomerUpdateOptions + { + Balance = customer.Balance + credit + }; + + await stripeAdapter.CustomerUpdateAsync(customer.Id, options); + } + } + public async Task Finalize(PremiumUserSale sale) { var (user, customerSetup, storage) = sale; @@ -36,6 +88,12 @@ public class PremiumUserBillingService( ? await CreateCustomerAsync(user, customerSetup) : await subscriberService.GetCustomerOrThrow(user, new CustomerGetOptions { Expand = expand }); + /* + * If the customer was previously set up with credit, which does not require a billing location, + * we need to update the customer on the fly before we start the subscription. + */ + customer = await ReconcileBillingLocationAsync(customer, customerSetup.TaxInformation); + var subscription = await CreateSubscriptionAsync(user.Id, customer, storage); switch (customerSetup.TokenizedPaymentSource) @@ -58,10 +116,37 @@ public class PremiumUserBillingService( await userRepository.ReplaceAsync(user); } + public async Task UpdatePaymentMethod( + User user, + TokenizedPaymentSource tokenizedPaymentSource, + TaxInformation taxInformation) + { + if (string.IsNullOrEmpty(user.GatewayCustomerId)) + { + var customer = await CreateCustomerAsync(user, + new CustomerSetup { TokenizedPaymentSource = tokenizedPaymentSource, TaxInformation = taxInformation }); + + user.Gateway = GatewayType.Stripe; + user.GatewayCustomerId = customer.Id; + + await userRepository.ReplaceAsync(user); + } + else + { + await subscriberService.UpdatePaymentSource(user, tokenizedPaymentSource); + await subscriberService.UpdateTaxInformation(user, taxInformation); + } + } + private async Task CreateCustomerAsync( User user, CustomerSetup customerSetup) { + /* + * Creating a Customer via the adding of a payment method or the purchasing of a subscription requires + * an actual payment source. The only time this is not the case is when the Customer is created when the + * User purchases credit. + */ if (customerSetup.TokenizedPaymentSource is not { Type: PaymentMethodType.BankAccount or PaymentMethodType.Card or PaymentMethodType.PayPal, @@ -235,7 +320,7 @@ public class PremiumUserBillingService( { AutomaticTax = new SubscriptionAutomaticTaxOptions { - Enabled = customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported, + Enabled = true }, CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, Customer = customer.Id, @@ -262,4 +347,34 @@ public class PremiumUserBillingService( return subscription; } + + private async Task ReconcileBillingLocationAsync( + Customer customer, + TaxInformation taxInformation) + { + if (customer is { Address: { Country: not null and not "", PostalCode: not null and not "" } }) + { + return customer; + } + + var options = new CustomerUpdateOptions + { + Address = new AddressOptions + { + Line1 = taxInformation.Line1, + Line2 = taxInformation.Line2, + City = taxInformation.City, + PostalCode = taxInformation.PostalCode, + State = taxInformation.State, + Country = taxInformation.Country, + }, + Expand = ["tax"], + Tax = new CustomerTaxOptions + { + ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately + } + }; + + return await stripeAdapter.CustomerUpdateAsync(customer.Id, options); + } } diff --git a/src/Core/Billing/Services/Implementations/SubscriberService.cs b/src/Core/Billing/Services/Implementations/SubscriberService.cs index b2dca19e80..f4cf22ac19 100644 --- a/src/Core/Billing/Services/Implementations/SubscriberService.cs +++ b/src/Core/Billing/Services/Implementations/SubscriberService.cs @@ -661,21 +661,11 @@ public class SubscriberService( } } - if (SubscriberIsEligibleForAutomaticTax(subscriber, customer)) - { - await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId, - new SubscriptionUpdateOptions - { - AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } - }); - } - - return; - - bool SubscriberIsEligibleForAutomaticTax(ISubscriber localSubscriber, Customer localCustomer) - => !string.IsNullOrEmpty(localSubscriber.GatewaySubscriptionId) && - (localCustomer.Subscriptions?.Any(sub => sub.Id == localSubscriber.GatewaySubscriptionId && !sub.AutomaticTax.Enabled) ?? false) && - localCustomer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported; + await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId, + new SubscriptionUpdateOptions + { + AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }, + }); } public async Task VerifyBankAccount( diff --git a/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs b/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs index 6657be085e..385d7ebbd6 100644 --- a/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs +++ b/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs @@ -43,10 +43,7 @@ public class SendTrialInitiationEmailForRegistrationCommand( await PerformConstantTimeOperationsAsync(); - if (!userExists) - { - await mailService.SendTrialInitiationSignupEmailAsync(email, token, productTier, products); - } + await mailService.SendTrialInitiationSignupEmailAsync(userExists, email, token, productTier, products); return null; } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index defcc41e93..91637e3893 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -101,10 +101,24 @@ public static class AuthenticationSchemes public static class FeatureFlagKeys { - public const string BrowserFilelessImport = "browser-fileless-import"; + /* Admin Console Team */ + public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner"; + public const string AccountDeprovisioning = "pm-10308-account-deprovisioning"; + public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint"; + public const string IntegrationPage = "pm-14505-admin-console-integration-page"; + public const string DeviceApprovalRequestAdminNotifications = "pm-15637-device-approval-request-admin-notifications"; + public const string LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission"; + public const string ShortcutDuplicatePatchRequests = "pm-16812-shortcut-duplicate-patch-requests"; + public const string PushSyncOrgKeysOnRevokeRestore = "pm-17168-push-sync-org-keys-on-revoke-restore"; + public const string PolicyRequirements = "pm-14439-policy-requirements"; + + /* Tools Team */ + public const string ItemShare = "item-share"; + public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application"; + public const string EnableRiskInsightsNotifications = "enable-risk-insights-notifications"; + public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair"; public const string UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection"; - public const string ItemShare = "item-share"; public const string DuoRedirect = "duo-redirect"; public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; public const string AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section"; @@ -114,12 +128,9 @@ public static class FeatureFlagKeys public const string RestrictProviderAccess = "restrict-provider-access"; public const string PM4154BulkEncryptionService = "PM-4154-bulk-encryption-service"; public const string VaultBulkManagementAction = "vault-bulk-management-action"; - public const string MemberAccessReport = "ac-2059-member-access-report"; - public const string BlockLegacyUsers = "block-legacy-users"; public const string InlineMenuFieldQualification = "inline-menu-field-qualification"; public const string TwoFactorComponentRefactor = "two-factor-component-refactor"; public const string InlineMenuPositioningImprovements = "inline-menu-positioning-improvements"; - public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner"; public const string DeviceTrustLogging = "pm-8285-device-trust-logging"; public const string SSHKeyItemVaultItem = "ssh-key-vault-item"; public const string SSHAgent = "ssh-agent"; @@ -131,40 +142,38 @@ public static class FeatureFlagKeys public const string DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2"; public const string NativeCarouselFlow = "native-carousel-flow"; 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 NotificationRefresh = "notification-refresh"; public const string PersistPopupView = "persist-popup-view"; public const string CipherKeyEncryption = "cipher-key-encryption"; public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill"; 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 VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint"; public const string PM12275_MultiOrganizationEnterprises = "pm-12275-multi-organization-enterprises"; - public const string GeneratorToolsModernization = "generator-tools-modernization"; public const string NewDeviceVerification = "new-device-verification"; - public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application"; - public const string IntegrationPage = "pm-14505-admin-console-integration-page"; 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 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"; public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form"; public const string InlineMenuTotp = "inline-menu-totp"; - public const string PM12443RemovePagingLogic = "pm-12443-remove-paging-logic"; public const string SelfHostLicenseRefactor = "pm-11516-self-host-license-refactor"; - public const string PromoteProviderServiceUserTool = "pm-15128-promote-provider-service-user-tool"; public const string PrivateKeyRegeneration = "pm-12241-private-key-regeneration"; - public const string AuthenticatorSynciOS = "enable-authenticator-sync-ios"; - 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 Argon2Default = "argon2-default"; public const string UsePricingService = "use-pricing-service"; public const string RecordInstallationLastActivityDate = "installation-last-activity-date"; + public const string AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner"; + public const string SingleTapPasskeyCreation = "single-tap-passkey-creation"; + public const string SingleTapPasskeyAuthentication = "single-tap-passkey-authentication"; + public const string EnablePMAuthenticatorSync = "enable-pm-bwa-sync"; + public const string P15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal"; + public const string AndroidMutualTls = "mutual-tls"; + public const string RecoveryCodeLogin = "pm-17128-recovery-code-login"; + public const string PM3503_MobileAnonAddySelfHostAlias = "anon-addy-self-host-alias"; public static List GetAllKeys() { diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index 2767b5925f..b4a250fe2b 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -169,6 +169,11 @@ public class CurrentContext : ICurrentContext DeviceIdentifier = GetClaimValue(claimsDict, Claims.Device); + if (Enum.TryParse(GetClaimValue(claimsDict, Claims.DeviceType), out DeviceType deviceType)) + { + DeviceType = deviceType; + } + Organizations = GetOrganizations(claimsDict, orgApi); Providers = GetProviders(claimsDict); diff --git a/src/Core/Context/ICurrentContext.cs b/src/Core/Context/ICurrentContext.cs index 3d3a5960b7..9361480229 100644 --- a/src/Core/Context/ICurrentContext.cs +++ b/src/Core/Context/ICurrentContext.cs @@ -43,7 +43,9 @@ public interface ICurrentContext Task AccessEventLogs(Guid orgId); Task AccessImportExport(Guid orgId); Task AccessReports(Guid orgId); + [Obsolete("Deprecated. Use an authorization handler checking the specific permissions required instead.")] Task EditAnyCollection(Guid orgId); + [Obsolete("Deprecated. Use an authorization handler checking the specific permissions required instead.")] Task ViewAllCollections(Guid orgId); Task ManageGroups(Guid orgId); Task ManagePolicies(Guid orgId); diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 44b4729a10..860cf33298 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -3,6 +3,8 @@ false bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + $(WarningsNotAsErrors);CS1570;CS1574;CS8602;CS9113;CS1998;CS8604 @@ -21,8 +23,8 @@ - - + + @@ -40,9 +42,9 @@ - + - + @@ -51,7 +53,6 @@ - @@ -67,12 +68,21 @@ + + + + + + + + + - + diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs index ee1b59990f..d4a4caeb9e 100644 --- a/src/Core/Enums/PushType.cs +++ b/src/Core/Enums/PushType.cs @@ -27,4 +27,7 @@ public enum PushType : byte SyncOrganizations = 17, SyncOrganizationStatusChanged = 18, SyncOrganizationCollectionSettingChanged = 19, + + SyncNotification = 20, + SyncNotificationStatus = 21 } diff --git a/src/Core/Identity/Claims.cs b/src/Core/Identity/Claims.cs index b1223a6e63..65d5eb210a 100644 --- a/src/Core/Identity/Claims.cs +++ b/src/Core/Identity/Claims.cs @@ -6,6 +6,7 @@ public static class Claims public const string SecurityStamp = "sstamp"; public const string Premium = "premium"; public const string Device = "device"; + public const string DeviceType = "devicetype"; public const string OrganizationOwner = "orgowner"; public const string OrganizationAdmin = "orgadmin"; diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs index ad2245e585..f10c47c78f 100644 --- a/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs +++ b/src/Core/MailTemplates/Handlebars/AdminConsole/DomainClaimedByOrganization.html.hbs @@ -1,14 +1,10 @@ {{#>TitleContactUsHtmlLayout}} - - - diff --git a/src/Core/MailTemplates/Handlebars/Layouts/FullUpdated.html.hbs b/src/Core/MailTemplates/Handlebars/Layouts/FullUpdated.html.hbs index f0a8688a41..f5772d61f6 100644 --- a/src/Core/MailTemplates/Handlebars/Layouts/FullUpdated.html.hbs +++ b/src/Core/MailTemplates/Handlebars/Layouts/FullUpdated.html.hbs @@ -107,10 +107,10 @@ .footer-text { width: 100% !important; - } - - .center { - text-align: center !important; + } + + .center { + text-align: center !important; } .templateColumnContainer{ @@ -159,7 +159,7 @@
- As a member of {{OrganizationName}}, your Bitwarden account is claimed and owned by your organization. -
Here's what that means:
    +
  • Your Bitwarden account is owned by {{OrganizationName}}
  • Your administrators can delete your account at any time
  • You cannot leave the organization
diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.html.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.html.hbs new file mode 100644 index 0000000000..a54773a15e --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.html.hbs @@ -0,0 +1,23 @@ +{{#>FullHtmlLayout}} + + + + + + + +
+ {{UserNameRequestingAccess}} has sent a device approval request. Review login requests to allow the member to finish logging in. +
+
+
+ + Review request + +
+
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.text.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.text.hbs new file mode 100644 index 0000000000..e396546646 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/AdminConsole/NotifyAdminDeviceApprovalRequested.text.hbs @@ -0,0 +1,5 @@ +{{#>BasicTextLayout}} +{{UserNameRequestingAccess}} has sent a device approval request. Review login requests to allow the member to finish logging in. + +{{Url}} +{{/BasicTextLayout}} diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.html.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.html.hbs new file mode 100644 index 0000000000..ee7fcf8cad --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.html.hbs @@ -0,0 +1,29 @@ +{{#>FullHtmlLayout}} + + + + + + + + + + +
+ {{UserNameRequestingAccess}} has sent a device approval request. Review login requests to allow the member to finish logging in. +
+ To review requests, log in to your self-hosted instance → navigate to the Admin Console → select Device Approvals +
+
+
+ + Review request + +
+
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.text.hbs b/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.text.hbs new file mode 100644 index 0000000000..e5b412cc87 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/AdminConsole/SelfHostNotifyAdminDeviceApprovalRequested.text.hbs @@ -0,0 +1,7 @@ +{{#>BasicTextLayout}} +{{UserNameRequestingAccess}} has sent a device approval request. Review login requests to allow the member to finish logging in. + +To review requests, log in to your self-hosted instance -> navigate to the Admin Console -> select Device Approvals. + +{{Url}} +{{/BasicTextLayout}} diff --git a/src/Core/MailTemplates/Handlebars/Layouts/Full.html.hbs b/src/Core/MailTemplates/Handlebars/Layouts/Full.html.hbs index 5e4c0eb0ae..7ed9fb7d1a 100644 --- a/src/Core/MailTemplates/Handlebars/Layouts/Full.html.hbs +++ b/src/Core/MailTemplates/Handlebars/Layouts/Full.html.hbs @@ -130,7 +130,7 @@
@@ -148,13 +148,13 @@
@@ -177,13 +177,13 @@ - - - - - - - + + + + + + +
TwitterRedditCommunityForumsGitHubYoutubeLinkedInFacebookTwitterRedditCommunityForumsGitHubYoutubeLinkedInFacebook
diff --git a/src/Core/Models/Api/Request/PushRegistrationRequestModel.cs b/src/Core/Models/Api/Request/PushRegistrationRequestModel.cs index 580c1c3b60..ee787dd083 100644 --- a/src/Core/Models/Api/Request/PushRegistrationRequestModel.cs +++ b/src/Core/Models/Api/Request/PushRegistrationRequestModel.cs @@ -15,4 +15,5 @@ public class PushRegistrationRequestModel public DeviceType Type { get; set; } [Required] public string Identifier { get; set; } + public IEnumerable OrganizationIds { get; set; } } diff --git a/src/Core/Models/Api/Request/PushSendRequestModel.cs b/src/Core/Models/Api/Request/PushSendRequestModel.cs index b85c8fb555..7247e6d25f 100644 --- a/src/Core/Models/Api/Request/PushSendRequestModel.cs +++ b/src/Core/Models/Api/Request/PushSendRequestModel.cs @@ -1,18 +1,18 @@ -using System.ComponentModel.DataAnnotations; +#nullable enable +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; namespace Bit.Core.Models.Api; public class PushSendRequestModel : IValidatableObject { - public string UserId { get; set; } - public string OrganizationId { get; set; } - public string DeviceId { get; set; } - public string Identifier { get; set; } - [Required] - public PushType? Type { get; set; } - [Required] - public object Payload { get; set; } + public string? UserId { get; set; } + public string? OrganizationId { get; set; } + public string? DeviceId { get; set; } + public string? Identifier { get; set; } + public required PushType Type { get; set; } + public required object Payload { get; set; } + public ClientType? ClientType { get; set; } public IEnumerable Validate(ValidationContext validationContext) { diff --git a/src/Core/Models/Business/SubscriptionCreateOptions.cs b/src/Core/Models/Business/SubscriptionCreateOptions.cs index 64626780ef..2d42ee66f7 100644 --- a/src/Core/Models/Business/SubscriptionCreateOptions.cs +++ b/src/Core/Models/Business/SubscriptionCreateOptions.cs @@ -34,11 +34,6 @@ public class OrganizationSubscriptionOptionsBase : SubscriptionCreateOptions AddPremiumAccessAddon(plan, premiumAccessAddon); AddPasswordManagerSeat(plan, additionalSeats); AddAdditionalStorage(plan, additionalStorageGb); - - if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId)) - { - DefaultTaxRates = new List { taxInfo.StripeTaxRateId }; - } } private void AddSecretsManagerSeat(Plan plan, int additionalSmSeats) diff --git a/src/Core/Models/Business/TaxInfo.cs b/src/Core/Models/Business/TaxInfo.cs index b12c5229b3..80a63473a7 100644 --- a/src/Core/Models/Business/TaxInfo.cs +++ b/src/Core/Models/Business/TaxInfo.cs @@ -5,17 +5,10 @@ public class TaxInfo public string TaxIdNumber { get; set; } public string TaxIdType { get; set; } - public string StripeTaxRateId { get; set; } public string BillingAddressLine1 { get; set; } public string BillingAddressLine2 { get; set; } public string BillingAddressCity { get; set; } public string BillingAddressState { get; set; } public string BillingAddressPostalCode { get; set; } public string BillingAddressCountry { get; set; } = "US"; - - public bool HasTaxId - { - get => !string.IsNullOrWhiteSpace(TaxIdNumber) && - !string.IsNullOrWhiteSpace(TaxIdType); - } } diff --git a/src/Core/Models/PushNotification.cs b/src/Core/Models/PushNotification.cs index 9907abcb65..775c3443f2 100644 --- a/src/Core/Models/PushNotification.cs +++ b/src/Core/Models/PushNotification.cs @@ -1,10 +1,12 @@ -using Bit.Core.Enums; +#nullable enable +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Enums; namespace Bit.Core.Models; public class PushNotificationData { - public PushNotificationData(PushType type, T payload, string contextId) + public PushNotificationData(PushType type, T payload, string? contextId) { Type = type; Payload = payload; @@ -13,7 +15,7 @@ public class PushNotificationData public PushType Type { get; set; } public T Payload { get; set; } - public string ContextId { get; set; } + public string? ContextId { get; set; } } public class SyncCipherPushNotification @@ -21,7 +23,7 @@ public class SyncCipherPushNotification public Guid Id { get; set; } public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } - public IEnumerable CollectionIds { get; set; } + public IEnumerable? CollectionIds { get; set; } public DateTime RevisionDate { get; set; } } @@ -45,6 +47,22 @@ public class SyncSendPushNotification public DateTime RevisionDate { get; set; } } +public class NotificationPushNotification +{ + public Guid Id { get; set; } + public Priority Priority { get; set; } + public bool Global { get; set; } + public ClientType ClientType { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public string? Title { get; set; } + public string? Body { get; set; } + public DateTime CreationDate { get; set; } + public DateTime RevisionDate { get; set; } + public DateTime? ReadDate { get; set; } + public DateTime? DeletedDate { get; set; } +} + public class AuthRequestPushNotification { public Guid UserId { get; set; } @@ -62,4 +80,5 @@ public class OrganizationCollectionManagementPushNotification public Guid OrganizationId { get; init; } public bool LimitCollectionCreation { get; init; } public bool LimitCollectionDeletion { get; init; } + public bool LimitItemDeletion { get; init; } } diff --git a/src/Core/NotificationCenter/Commands/CreateNotificationCommand.cs b/src/Core/NotificationCenter/Commands/CreateNotificationCommand.cs index 4f76950a34..3fddafcdc7 100644 --- a/src/Core/NotificationCenter/Commands/CreateNotificationCommand.cs +++ b/src/Core/NotificationCenter/Commands/CreateNotificationCommand.cs @@ -4,6 +4,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -14,14 +15,17 @@ public class CreateNotificationCommand : ICreateNotificationCommand private readonly ICurrentContext _currentContext; private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; + private readonly IPushNotificationService _pushNotificationService; public CreateNotificationCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, - INotificationRepository notificationRepository) + INotificationRepository notificationRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; + _pushNotificationService = pushNotificationService; } public async Task CreateAsync(Notification notification) @@ -31,6 +35,10 @@ public class CreateNotificationCommand : ICreateNotificationCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notification, NotificationOperations.Create); - return await _notificationRepository.CreateAsync(notification); + var newNotification = await _notificationRepository.CreateAsync(notification); + + await _pushNotificationService.PushNotificationAsync(newNotification); + + return newNotification; } } diff --git a/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs b/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs index fcd61ceebc..793da22f81 100644 --- a/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs +++ b/src/Core/NotificationCenter/Commands/CreateNotificationStatusCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public CreateNotificationStatusCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task CreateAsync(NotificationStatus notificationStatus) @@ -42,6 +46,10 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - return await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); + + return newNotificationStatus; } } diff --git a/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs b/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs index 2ca7aa9051..256702c10c 100644 --- a/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs +++ b/src/Core/NotificationCenter/Commands/MarkNotificationDeletedCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public MarkNotificationDeletedCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task MarkDeletedAsync(Guid notificationId) @@ -59,7 +63,9 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); } else { @@ -69,6 +75,8 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand notificationStatus.DeletedDate = DateTime.UtcNow; await _notificationStatusRepository.UpdateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, notificationStatus); } } } diff --git a/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs b/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs index 400e44463a..9c9d1d48a2 100644 --- a/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs +++ b/src/Core/NotificationCenter/Commands/MarkNotificationReadCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -16,16 +17,19 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; private readonly INotificationStatusRepository _notificationStatusRepository; + private readonly IPushNotificationService _pushNotificationService; public MarkNotificationReadCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, INotificationRepository notificationRepository, - INotificationStatusRepository notificationStatusRepository) + INotificationStatusRepository notificationStatusRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; _notificationStatusRepository = notificationStatusRepository; + _pushNotificationService = pushNotificationService; } public async Task MarkReadAsync(Guid notificationId) @@ -59,7 +63,9 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, NotificationStatusOperations.Create); - await _notificationStatusRepository.CreateAsync(notificationStatus); + var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, newNotificationStatus); } else { @@ -69,6 +75,8 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand notificationStatus.ReadDate = DateTime.UtcNow; await _notificationStatusRepository.UpdateAsync(notificationStatus); + + await _pushNotificationService.PushNotificationStatusAsync(notification, notificationStatus); } } } diff --git a/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs b/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs index f049478178..471786aac6 100644 --- a/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs +++ b/src/Core/NotificationCenter/Commands/UpdateNotificationCommand.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -15,14 +16,17 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand private readonly ICurrentContext _currentContext; private readonly IAuthorizationService _authorizationService; private readonly INotificationRepository _notificationRepository; + private readonly IPushNotificationService _pushNotificationService; public UpdateNotificationCommand(ICurrentContext currentContext, IAuthorizationService authorizationService, - INotificationRepository notificationRepository) + INotificationRepository notificationRepository, + IPushNotificationService pushNotificationService) { _currentContext = currentContext; _authorizationService = authorizationService; _notificationRepository = notificationRepository; + _pushNotificationService = pushNotificationService; } public async Task UpdateAsync(Notification notificationToUpdate) @@ -43,5 +47,7 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand notification.RevisionDate = DateTime.UtcNow; await _notificationRepository.ReplaceAsync(notification); + + await _pushNotificationService.PushNotificationAsync(notification); } } diff --git a/src/Core/NotificationCenter/Entities/Notification.cs b/src/Core/NotificationCenter/Entities/Notification.cs index 7ab3187524..ad43299f55 100644 --- a/src/Core/NotificationCenter/Entities/Notification.cs +++ b/src/Core/NotificationCenter/Entities/Notification.cs @@ -15,11 +15,11 @@ public class Notification : ITableObject public ClientType ClientType { get; set; } public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } - [MaxLength(256)] - public string? Title { get; set; } - public string? Body { get; set; } + [MaxLength(256)] public string? Title { get; set; } + [MaxLength(3000)] public string? Body { get; set; } public DateTime CreationDate { get; set; } public DateTime RevisionDate { get; set; } + public Guid? TaskId { get; set; } public void SetNewId() { diff --git a/src/Core/NotificationHub/INotificationHubPool.cs b/src/Core/NotificationHub/INotificationHubPool.cs index 7c383d7b96..18bae98bc6 100644 --- a/src/Core/NotificationHub/INotificationHubPool.cs +++ b/src/Core/NotificationHub/INotificationHubPool.cs @@ -4,6 +4,6 @@ namespace Bit.Core.NotificationHub; public interface INotificationHubPool { - NotificationHubClient ClientFor(Guid comb); + INotificationHubClient ClientFor(Guid comb); INotificationHubProxy AllClients { get; } } diff --git a/src/Core/NotificationHub/NotificationHubPool.cs b/src/Core/NotificationHub/NotificationHubPool.cs index 7448aad5bd..8993ee2b8e 100644 --- a/src/Core/NotificationHub/NotificationHubPool.cs +++ b/src/Core/NotificationHub/NotificationHubPool.cs @@ -43,7 +43,7 @@ public class NotificationHubPool : INotificationHubPool /// /// /// Thrown when no notification hub is found for a given comb. - public NotificationHubClient ClientFor(Guid comb) + public INotificationHubClient ClientFor(Guid comb) { var possibleConnections = _connections.Where(c => c.RegistrationEnabled(comb)).ToArray(); if (possibleConnections.Length == 0) diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs index d90ebdd744..7baf0352ee 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +#nullable enable +using System.Text.Json; using System.Text.RegularExpressions; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; @@ -6,12 +7,14 @@ using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.Models.Data; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Notification = Bit.Core.NotificationCenter.Entities.Notification; namespace Bit.Core.NotificationHub; @@ -50,7 +53,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -135,11 +138,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); } @@ -184,31 +183,89 @@ public class NotificationHubPushNotificationService : IPushNotificationService await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); } + public async Task PushNotificationAsync(Notification notification) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate + }; + + if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, + true, notification.ClientType); + } + } + + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationStatus, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationStatus, message, + true, notification.ClientType); + } + } + private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendPayloadToUserAsync(authRequest.UserId, type, message, true); } - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, + ClientType? clientType = null) { - await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext)); + await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext), + clientType: clientType); } - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext) + private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, + bool excludeCurrentContext, ClientType? clientType = null) { - await SendPayloadToUserAsync(orgId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext)); + await SendPayloadToOrganizationAsync(orgId.ToString(), type, payload, + GetContextIdentifier(excludeCurrentContext), clientType: clientType); } - public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { - var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier); + var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType); await SendPayloadAsync(tag, type, payload); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { @@ -216,10 +273,10 @@ public class NotificationHubPushNotificationService : IPushNotificationService } } - public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { - var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier); + var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType); await SendPayloadAsync(tag, type, payload); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) { @@ -246,30 +303,36 @@ public class NotificationHubPushNotificationService : IPushNotificationService { OrganizationId = organization.Id, LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion }, false ); - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - private string BuildTag(string tag, string identifier) + private string BuildTag(string tag, string? identifier, ClientType? clientType) { if (!string.IsNullOrWhiteSpace(identifier)) { tag += $" && !deviceIdentifier:{SanitizeTagInput(identifier)}"; } + if (clientType.HasValue && clientType.Value != ClientType.All) + { + tag += $" && clientType:{clientType}"; + } + return $"({tag})"; } @@ -278,8 +341,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService var results = await _notificationHubPool.AllClients.SendTemplateNotificationAsync( new Dictionary { - { "type", ((byte)type).ToString() }, - { "payload", JsonSerializer.Serialize(payload) } + { "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) } }, tag); if (_enableTracing) @@ -290,7 +352,9 @@ public class NotificationHubPushNotificationService : IPushNotificationService { continue; } - _logger.LogInformation("Azure Notification Hub Tracking ID: {Id} | {Type} push notification with {Success} successes and {Failure} failures with a payload of {@Payload} and result of {@Results}", + + _logger.LogInformation( + "Azure Notification Hub Tracking ID: {Id} | {Type} push notification with {Success} successes and {Failure} failures with a payload of {@Payload} and result of {@Results}", outcome.TrackingId, type, outcome.Success, outcome.Failure, payload, outcome.Results); } } diff --git a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs b/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs index 180b2b641b..0c9bbea425 100644 --- a/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushRegistrationService.cs @@ -2,36 +2,26 @@ using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; -using Bit.Core.Settings; +using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; -using Microsoft.Extensions.Logging; namespace Bit.Core.NotificationHub; public class NotificationHubPushRegistrationService : IPushRegistrationService { private readonly IInstallationDeviceRepository _installationDeviceRepository; - private readonly GlobalSettings _globalSettings; private readonly INotificationHubPool _notificationHubPool; - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; public NotificationHubPushRegistrationService( IInstallationDeviceRepository installationDeviceRepository, - GlobalSettings globalSettings, - INotificationHubPool notificationHubPool, - IServiceProvider serviceProvider, - ILogger logger) + INotificationHubPool notificationHubPool) { _installationDeviceRepository = installationDeviceRepository; - _globalSettings = globalSettings; _notificationHubPool = notificationHubPool; - _serviceProvider = serviceProvider; - _logger = logger; } public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, - string identifier, DeviceType type) + string identifier, DeviceType type, IEnumerable organizationIds) { if (string.IsNullOrWhiteSpace(pushToken)) { @@ -45,16 +35,21 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService Templates = new Dictionary() }; - installation.Tags = new List - { - $"userId:{userId}" - }; + var clientType = DeviceTypes.ToClientType(type); + + installation.Tags = new List { $"userId:{userId}", $"clientType:{clientType}" }; if (!string.IsNullOrWhiteSpace(identifier)) { installation.Tags.Add("deviceIdentifier:" + identifier); } + var organizationIdsList = organizationIds.ToList(); + foreach (var organizationId in organizationIdsList) + { + installation.Tags.Add($"organizationId:{organizationId}"); + } + string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null; switch (type) { @@ -84,10 +79,12 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService break; } - BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier); - BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier); + BuildInstallationTemplate(installation, "payload", payloadTemplate, userId, identifier, clientType, + organizationIdsList); + BuildInstallationTemplate(installation, "message", messageTemplate, userId, identifier, clientType, + organizationIdsList); BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, - userId, identifier); + userId, identifier, clientType, organizationIdsList); await ClientFor(GetComb(deviceId)).CreateOrUpdateInstallationAsync(installation); if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) @@ -97,7 +94,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody, - string userId, string identifier) + string userId, string identifier, ClientType clientType, List organizationIds) { if (templateBody == null) { @@ -111,8 +108,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService Body = templateBody, Tags = new List { - fullTemplateId, - $"{fullTemplateId}_userId:{userId}" + fullTemplateId, $"{fullTemplateId}_userId:{userId}", $"clientType:{clientType}" } }; @@ -121,6 +117,11 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{identifier}"); } + foreach (var organizationId in organizationIds) + { + template.Tags.Add($"organizationId:{organizationId}"); + } + installation.Templates.Add(fullTemplateId, template); } @@ -197,7 +198,7 @@ public class NotificationHubPushRegistrationService : IPushRegistrationService } } - private NotificationHubClient ClientFor(Guid deviceId) + private INotificationHubClient ClientFor(Guid deviceId) { return _notificationHubPool.ClientFor(deviceId); } diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 5586273520..7db514887c 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfa using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; @@ -52,6 +53,8 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationDomainCommandsQueries(); services.AddOrganizationSignUpCommands(); + services.AddOrganizationDeleteCommands(); + services.AddOrganizationEnableCommands(); services.AddOrganizationAuthCommands(); services.AddOrganizationUserCommands(); services.AddOrganizationUserCommandsQueries(); @@ -61,6 +64,15 @@ public static class OrganizationServiceCollectionExtensions private static IServiceCollection AddOrganizationSignUpCommands(this IServiceCollection services) => services.AddScoped(); + private static void AddOrganizationDeleteCommands(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + } + + private static void AddOrganizationEnableCommands(this IServiceCollection services) => + services.AddScoped(); + private static void AddOrganizationConnectionCommands(this IServiceCollection services) { services.AddScoped(); diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs index 7f463460dd..19af8121e7 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs @@ -224,27 +224,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { - if (_featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI)) - { - var sale = OrganizationSale.From(organization, upgrade); - await _organizationBillingService.Finalize(sale); - } - else - { - try - { - paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization, - newPlan, upgrade); - success = string.IsNullOrWhiteSpace(paymentIntentClientSecret); - } - catch - { - await _paymentService.CancelAndRecoverChargesAsync(organization); - organization.GatewayCustomerId = null; - await _organizationService.ReplaceAndUpdateCacheAsync(organization); - throw; - } - } + var sale = OrganizationSale.From(organization, upgrade); + await _organizationBillingService.Finalize(sale); } else { diff --git a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs index 0503a22a60..c32212c6b2 100644 --- a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs @@ -1,30 +1,30 @@ -using System.Text.Json; +#nullable enable +using System.Text.Json; using Azure.Storage.Queues; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; -using Bit.Core.Settings; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; namespace Bit.Core.Platform.Push.Internal; public class AzureQueuePushNotificationService : IPushNotificationService { private readonly QueueClient _queueClient; - private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; public AzureQueuePushNotificationService( - GlobalSettings globalSettings, + [FromKeyedServices("notifications")] QueueClient queueClient, IHttpContextAccessor httpContextAccessor) { - _queueClient = new QueueClient(globalSettings.Notifications.ConnectionString, "notifications"); - _globalSettings = globalSettings; + _queueClient = queueClient; _httpContextAccessor = httpContextAccessor; } @@ -43,7 +43,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -129,11 +129,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendMessageAsync(type, message, excludeCurrentContext); } @@ -150,11 +146,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendMessageAsync(type, message, true); } @@ -174,6 +166,46 @@ public class AzureQueuePushNotificationService : IPushNotificationService await PushSendAsync(send, PushType.SyncSendDelete); } + public async Task PushNotificationAsync(Notification notification) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate + }; + + await SendMessageAsync(PushType.SyncNotification, message, true); + } + + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + await SendMessageAsync(PushType.SyncNotificationStatus, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -197,27 +229,27 @@ public class AzureQueuePushNotificationService : IPushNotificationService await _queueClient.SendMessageAsync(message); } - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); @@ -239,6 +271,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService { OrganizationId = organization.Id, LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion }, false); } diff --git a/src/Core/Platform/Push/Services/IPushNotificationService.cs b/src/Core/Platform/Push/Services/IPushNotificationService.cs index b015c17df2..1c7fdc659b 100644 --- a/src/Core/Platform/Push/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/IPushNotificationService.cs @@ -1,6 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -23,11 +25,14 @@ public interface IPushNotificationService Task PushSyncSendCreateAsync(Send send); Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendDeleteAsync(Send send); + Task PushNotificationAsync(Notification notification); + Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus); Task PushAuthRequestAsync(AuthRequest authRequest); Task PushAuthRequestResponseAsync(AuthRequest authRequest); - Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, string deviceId = null); - Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null); Task PushSyncOrganizationStatusAsync(Organization organization); Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization); + Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null); + Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null); } diff --git a/src/Core/Platform/Push/Services/IPushRegistrationService.cs b/src/Core/Platform/Push/Services/IPushRegistrationService.cs index 482e7ae1c4..0c4271f061 100644 --- a/src/Core/Platform/Push/Services/IPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/IPushRegistrationService.cs @@ -5,7 +5,7 @@ namespace Bit.Core.Platform.Push; public interface IPushRegistrationService { Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, - string identifier, DeviceType type); + string identifier, DeviceType type, IEnumerable organizationIds); Task DeleteRegistrationAsync(string deviceId); Task AddUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId); Task DeleteUserRegistrationOrganizationAsync(IEnumerable deviceIds, string organizationId); diff --git a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs index f1a5700013..9b4e66ae1a 100644 --- a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs @@ -1,6 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -23,7 +25,7 @@ public class MultiServicePushNotificationService : IPushNotificationService _logger = logger; _logger.LogInformation("Hub services: {Services}", _services.Count()); - globalSettings?.NotificationHubPool?.NotificationHubs?.ForEach(hub => + globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub => { _logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); }); @@ -131,20 +133,6 @@ public class MultiServicePushNotificationService : IPushNotificationService return Task.FromResult(0); } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) - { - PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId)); - return Task.FromResult(0); - } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) - { - PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId)); - return Task.FromResult(0); - } - public Task PushSyncOrganizationStatusAsync(Organization organization) { PushToServices((s) => s.PushSyncOrganizationStatusAsync(organization)); @@ -157,14 +145,44 @@ public class MultiServicePushNotificationService : IPushNotificationService return Task.CompletedTask; } + public Task PushNotificationAsync(Notification notification) + { + PushToServices((s) => s.PushNotificationAsync(notification)); + return Task.CompletedTask; + } + + public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + PushToServices((s) => s.PushNotificationStatusAsync(notification, notificationStatus)); + return Task.CompletedTask; + } + + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) + { + PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType)); + return Task.FromResult(0); + } + + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) + { + PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType)); + return Task.FromResult(0); + } + private void PushToServices(Func pushFunc) { - if (_services != null) + if (!_services.Any()) { - foreach (var service in _services) - { - pushFunc(service); - } + _logger.LogWarning("No services found to push notification"); + return; + } + + foreach (var service in _services) + { + _logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); + pushFunc(service); } } } diff --git a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs index 4a185bee1a..57c446c5e5 100644 --- a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs @@ -1,6 +1,8 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -83,8 +85,8 @@ public class NoopPushNotificationService : IPushNotificationService return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } @@ -106,9 +108,14 @@ public class NoopPushNotificationService : IPushNotificationService return Task.FromResult(0); } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { return Task.FromResult(0); } + + public Task PushNotificationAsync(Notification notification) => Task.CompletedTask; + + public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) => + Task.CompletedTask; } diff --git a/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs b/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs index 6d1716a6ce..6bcf9e893a 100644 --- a/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushRegistrationService.cs @@ -10,7 +10,7 @@ public class NoopPushRegistrationService : IPushRegistrationService } public Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, - string identifier, DeviceType type) + string identifier, DeviceType type, IEnumerable organizationIds) { return Task.FromResult(0); } diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index 849ae1b765..7a557e8978 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -1,8 +1,10 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tools.Entities; @@ -15,7 +17,6 @@ namespace Bit.Core.Platform.Push; public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService { - private readonly GlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; public NotificationsApiPushNotificationService( @@ -32,7 +33,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService globalSettings.InternalIdentityKey, logger) { - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; } @@ -51,7 +51,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -183,6 +183,46 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await PushSendAsync(send, PushType.SyncSendDelete); } + public async Task PushNotificationAsync(Notification notification) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate + }; + + await SendMessageAsync(PushType.SyncNotification, message, true); + } + + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) + { + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; + + await SendMessageAsync(PushType.SyncNotificationStatus, message, true); + } + private async Task PushSendAsync(Send send, PushType type) { if (send.UserId.HasValue) @@ -205,27 +245,27 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService await SendAsync(HttpMethod.Post, "send", request); } - private string GetContextIdentifier(bool excludeCurrentContext) + private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) { return null; } - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + var currentContext = + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; return currentContext?.DeviceIdentifier; } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); } - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) { // Noop return Task.FromResult(0); @@ -248,6 +288,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService { OrganizationId = organization.Id, LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion }, false); } diff --git a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs index e41244a1b8..09f42fd0d1 100644 --- a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs @@ -1,10 +1,12 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.IdentityServer; using Bit.Core.Models; using Bit.Core.Models.Api; +using Bit.Core.NotificationCenter.Entities; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; @@ -54,7 +56,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); } - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable collectionIds) + private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -138,11 +140,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) { - var message = new UserPushNotification - { - UserId = userId, - Date = DateTime.UtcNow - }; + var message = new UserPushNotification { UserId = userId, Date = DateTime.UtcNow }; await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); } @@ -189,69 +187,67 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; + var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; await SendPayloadToUserAsync(authRequest.UserId, type, message, true); } - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext) + public async Task PushNotificationAsync(Notification notification) { - var request = new PushSendRequestModel + var message = new NotificationPushNotification { - UserId = userId.ToString(), - Type = type, - Payload = payload + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate }; - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext) - { - var request = new PushSendRequestModel + if (notification.UserId.HasValue) { - OrganizationId = orgId.ToString(), - Type = type, - Payload = payload - }; - - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) - { - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; - if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) { - var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); - if (device != null) - { - request.DeviceId = device.Id.ToString(); - } - if (addIdentifier) - { - request.Identifier = currentContext.DeviceIdentifier; - } + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, + true, notification.ClientType); } } - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, - string deviceId = null) + public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) { - throw new NotImplementedException(); - } + var message = new NotificationPushNotification + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate + }; - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, - string deviceId = null) - { - throw new NotImplementedException(); + if (notification.UserId.HasValue) + { + await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationStatus, message, true, + notification.ClientType); + } + else if (notification.OrganizationId.HasValue) + { + await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationStatus, message, + true, notification.ClientType); + } } public async Task PushSyncOrganizationStatusAsync(Organization organization) @@ -273,8 +269,70 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti { OrganizationId = organization.Id, LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion }, false ); + + private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, + ClientType? clientType = null) + { + var request = new PushSendRequestModel + { + UserId = userId.ToString(), + Type = type, + Payload = payload, + ClientType = clientType + }; + + await AddCurrentContextAsync(request, excludeCurrentContext); + await SendAsync(HttpMethod.Post, "push/send", request); + } + + private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, + bool excludeCurrentContext, ClientType? clientType = null) + { + var request = new PushSendRequestModel + { + OrganizationId = orgId.ToString(), + Type = type, + Payload = payload, + ClientType = clientType + }; + + await AddCurrentContextAsync(request, excludeCurrentContext); + await SendAsync(HttpMethod.Post, "push/send", request); + } + + private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) + { + var currentContext = + _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; + if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) + { + var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); + if (device != null) + { + request.DeviceId = device.Id.ToString(); + } + + if (addIdentifier) + { + request.Identifier = currentContext.DeviceIdentifier; + } + } + } + + public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) + { + throw new NotImplementedException(); + } + + public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, + string? deviceId = null, ClientType? clientType = null) + { + throw new NotImplementedException(); + } } diff --git a/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs b/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs index 79b033e877..b838fbde59 100644 --- a/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushRegistrationService.cs @@ -25,7 +25,7 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi } public async Task CreateOrUpdateRegistrationAsync(string pushToken, string deviceId, string userId, - string identifier, DeviceType type) + string identifier, DeviceType type, IEnumerable organizationIds) { var requestModel = new PushRegistrationRequestModel { @@ -33,7 +33,8 @@ public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegi Identifier = identifier, PushToken = pushToken, Type = type, - UserId = userId + UserId = userId, + OrganizationIds = organizationIds }; await SendAsync(HttpMethod.Post, "push/register", requestModel); } diff --git a/src/Core/Repositories/ITaxRateRepository.cs b/src/Core/Repositories/ITaxRateRepository.cs deleted file mode 100644 index c4d9e41238..0000000000 --- a/src/Core/Repositories/ITaxRateRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Bit.Core.Entities; - -#nullable enable - -namespace Bit.Core.Repositories; - -public interface ITaxRateRepository : IRepository -{ - Task> SearchAsync(int skip, int count); - Task> GetAllActiveAsync(); - Task ArchiveAsync(TaxRate model); - Task> GetByLocationAsync(TaxRate taxRate); -} diff --git a/src/Core/Services/ICollectionService.cs b/src/Core/Services/ICollectionService.cs index 27c4118197..c116e5f076 100644 --- a/src/Core/Services/ICollectionService.cs +++ b/src/Core/Services/ICollectionService.cs @@ -7,6 +7,4 @@ public interface ICollectionService { Task SaveAsync(Collection collection, IEnumerable groups = null, IEnumerable users = null); Task DeleteUserAsync(Collection collection, Guid organizationUserId); - [Obsolete("Pre-Flexible Collections logic.")] - Task> GetOrganizationCollectionsAsync(Guid organizationId); } diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 0f69d8daaf..92d05ddb7d 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -14,6 +14,7 @@ public interface IMailService Task SendVerifyEmailEmailAsync(string email, Guid userId, string token); Task SendRegistrationVerificationEmailAsync(string email, string token); Task SendTrialInitiationSignupEmailAsync( + bool isExistingUser, string email, string token, ProductTierType productTier, @@ -36,7 +37,7 @@ public interface IMailService Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable adminEmails, bool hasAccessSecretsManager = false); Task SendOrganizationConfirmedEmailAsync(string organizationName, string email, bool hasAccessSecretsManager = false); Task SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(string organizationName, string email); - Task SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(string organizationName, string email); + Task SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(string organizationName, string email); Task SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(string organizationName, string email); Task SendPasswordlessSignInAsync(string returnUrl, string token, string email); Task SendInvoiceUpcoming( @@ -96,5 +97,6 @@ public interface IMailService Task SendFamiliesForEnterpriseRemoveSponsorshipsEmailAsync(string email, string offerAcceptanceDate, string organizationId, string organizationName); Task SendClaimedDomainUserEmailAsync(ManagedUserDomainClaimedEmails emailList); + Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable adminEmails, Guid organizationId, string email, string userName); } diff --git a/src/Core/Services/IPaymentService.cs b/src/Core/Services/IPaymentService.cs index 7d0f9d3c63..5bd2bede33 100644 --- a/src/Core/Services/IPaymentService.cs +++ b/src/Core/Services/IPaymentService.cs @@ -54,9 +54,6 @@ public interface IPaymentService Task GetSubscriptionAsync(ISubscriber subscriber); Task GetTaxInfoAsync(ISubscriber subscriber); Task SaveTaxInfoAsync(ISubscriber subscriber, TaxInfo taxInfo); - Task CreateTaxRateAsync(TaxRate taxRate); - Task UpdateTaxRateAsync(TaxRate taxRate); - Task ArchiveTaxRateAsync(TaxRate taxRate); Task AddSecretsManagerToSubscription(Organization org, Plan plan, int additionalSmSeats, int additionalServiceAccount); Task RisksSubscriptionFailure(Organization organization); diff --git a/src/Core/Services/IStripeAdapter.cs b/src/Core/Services/IStripeAdapter.cs index ef2e3ab766..cb95732a6e 100644 --- a/src/Core/Services/IStripeAdapter.cs +++ b/src/Core/Services/IStripeAdapter.cs @@ -43,9 +43,6 @@ public interface IStripeAdapter IAsyncEnumerable PaymentMethodListAutoPagingAsync(Stripe.PaymentMethodListOptions options); Task PaymentMethodAttachAsync(string id, Stripe.PaymentMethodAttachOptions options = null); Task PaymentMethodDetachAsync(string id, Stripe.PaymentMethodDetachOptions options = null); - Task PlanGetAsync(string id, Stripe.PlanGetOptions options = null); - Task TaxRateCreateAsync(Stripe.TaxRateCreateOptions options); - Task TaxRateUpdateAsync(string id, Stripe.TaxRateUpdateOptions options); Task TaxIdCreateAsync(string id, Stripe.TaxIdCreateOptions options); Task TaxIdDeleteAsync(string customerId, string taxIdId, Stripe.TaxIdDeleteOptions options = null); Task> ChargeListAsync(Stripe.ChargeListOptions options); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 0886d18897..d1c61e4418 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -22,7 +22,6 @@ public interface IUserService Task CreateUserAsync(User user, string masterPasswordHash); Task SendMasterPasswordHintAsync(string email); Task SendTwoFactorEmailAsync(User user); - Task VerifyTwoFactorEmailAsync(User user, string token); Task StartWebAuthnRegistrationAsync(User user); Task DeleteWebAuthnKeyAsync(User user, int id); Task CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse); @@ -41,8 +40,6 @@ public interface IUserService Task RefreshSecurityStampAsync(User user, string masterPasswordHash); Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type, bool setEnabled = true, bool logEvent = true); Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type); - Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode); - Task GenerateUserTokenAsync(User user, string tokenProvider, string purpose); Task DeleteAsync(User user); Task DeleteAsync(User user, string token); Task SendDeleteConfirmationAsync(string email); @@ -55,9 +52,7 @@ public interface IUserService Task CancelPremiumAsync(User user, bool? endOfPeriod = null); Task ReinstatePremiumAsync(User user); Task EnablePremiumAsync(Guid userId, DateTime? expirationDate); - Task EnablePremiumAsync(User user, DateTime? expirationDate); Task DisablePremiumAsync(Guid userId, DateTime? expirationDate); - Task DisablePremiumAsync(User user, DateTime? expirationDate); Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); Task GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null, int? version = null); @@ -91,9 +86,26 @@ public interface IUserService void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true); + [Obsolete("To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175.")] + Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode); + /// - /// Returns true if the user is a legacy user. Legacy users use their master key as their encryption key. - /// We force these users to the web to migrate their encryption scheme. + /// This method is used by the TwoFactorAuthenticationValidator to recover two + /// factor for a user. This allows users to be logged in after a successful recovery + /// attempt. + /// + /// This method logs the event, sends an email to the user, and removes two factor + /// providers on the user account. This means that a user will have to accomplish + /// new device verification on their account on new logins, if it is enabled for their user. + /// + /// recovery code associated with the user logging in + /// The user to refresh the 2FA and Recovery Code on. + /// true if the recovery code is valid; false otherwise + Task RecoverTwoFactorAsync(User user, string recoveryCode); + + /// + /// Returns true if the user is a legacy user. Legacy users use their master key as their + /// encryption key. We force these users to the web to migrate their encryption scheme. /// Task IsLegacyUser(string userId); @@ -101,7 +113,8 @@ public interface IUserService /// Indicates if the user is managed by any organization. ///
/// - /// A user is considered managed by an organization if their email domain matches one of the verified domains of that organization, and the user is a member of it. + /// A user is considered managed by an organization if their email domain matches one of the + /// verified domains of that organization, and the user is a member of it. /// The organization must be enabled and able to have verified domains. /// /// diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index e779ac289f..f6e9735f4e 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -95,31 +95,4 @@ public class CollectionService : ICollectionService await _collectionRepository.DeleteUserAsync(collection.Id, organizationUserId); await _eventService.LogOrganizationUserEventAsync(orgUser, Enums.EventType.OrganizationUser_Updated); } - - public async Task> GetOrganizationCollectionsAsync(Guid organizationId) - { - if ( - !await _currentContext.ViewAllCollections(organizationId) && - !await _currentContext.ManageUsers(organizationId) && - !await _currentContext.ManageGroups(organizationId) && - !await _currentContext.AccessImportExport(organizationId) - ) - { - throw new NotFoundException(); - } - - IEnumerable orgCollections; - if (await _currentContext.ViewAllCollections(organizationId) || await _currentContext.AccessImportExport(organizationId)) - { - // Admins, Owners, Providers and Custom (with collection management or import/export permissions) can access all items even if not assigned to them - orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(organizationId); - } - else - { - var collections = await _collectionRepository.GetManyByUserIdAsync(_currentContext.UserId.Value); - orgCollections = collections.Where(c => c.OrganizationId == organizationId); - } - - return orgCollections; - } } diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index afbc574417..28823eeda7 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -1,6 +1,7 @@ using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Auth.Utilities; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Platform.Push; using Bit.Core.Repositories; @@ -11,13 +12,16 @@ public class DeviceService : IDeviceService { private readonly IDeviceRepository _deviceRepository; private readonly IPushRegistrationService _pushRegistrationService; + private readonly IOrganizationUserRepository _organizationUserRepository; public DeviceService( IDeviceRepository deviceRepository, - IPushRegistrationService pushRegistrationService) + IPushRegistrationService pushRegistrationService, + IOrganizationUserRepository organizationUserRepository) { _deviceRepository = deviceRepository; _pushRegistrationService = pushRegistrationService; + _organizationUserRepository = organizationUserRepository; } public async Task SaveAsync(Device device) @@ -32,8 +36,13 @@ public class DeviceService : IDeviceService await _deviceRepository.ReplaceAsync(device); } + var organizationIdsString = + (await _organizationUserRepository.GetManyDetailsByUserAsync(device.UserId, + OrganizationUserStatusType.Confirmed)) + .Select(ou => ou.OrganizationId.ToString()); + await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device.PushToken, device.Id.ToString(), - device.UserId.ToString(), device.Identifier, device.Type); + device.UserId.ToString(), device.Identifier, device.Type, organizationIdsString); } public async Task ClearTokenAsync(Device device) diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index deae80c056..d18a29b13a 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -2,6 +2,7 @@ using System.Reflection; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; +using Bit.Core.AdminConsole.Models.Mail; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Mail; using Bit.Core.Billing.Enums; @@ -73,6 +74,7 @@ public class HandlebarsMailService : IMailService } public async Task SendTrialInitiationSignupEmailAsync( + bool isExistingUser, string email, string token, ProductTierType productTier, @@ -81,6 +83,7 @@ public class HandlebarsMailService : IMailService var message = CreateDefaultMessage("Verify your email", email); var model = new TrialInitiationVerifyEmail { + IsExistingUser = isExistingUser, Token = WebUtility.UrlEncode(token), Email = WebUtility.UrlEncode(email), WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, @@ -295,7 +298,7 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } - public async Task SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(string organizationName, string email) + public async Task SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(string organizationName, string email) { var message = CreateDefaultMessage($"You have been revoked from {organizationName}", email); var model = new OrganizationUserRevokedForPolicyTwoFactorViewModel @@ -472,7 +475,7 @@ public class HandlebarsMailService : IMailService "AdminConsole.DomainClaimedByOrganization", new ClaimedDomainUserNotificationViewModel { - TitleFirst = $"Hey {emailAddress}, your account is owned by {org.DisplayName()}", + TitleFirst = $"Your Bitwarden account is claimed by {org.DisplayName()}", OrganizationName = CoreHelpers.SanitizeForEmail(org.DisplayName(), false) }); } @@ -863,7 +866,7 @@ public class HandlebarsMailService : IMailService var message = CreateDefaultMessage($"Join {providerName}", email); var model = new ProviderUserInvitedViewModel { - ProviderName = CoreHelpers.SanitizeForEmail(providerName), + ProviderName = CoreHelpers.SanitizeForEmail(providerName, false), Email = WebUtility.UrlEncode(providerUser.Email), ProviderId = providerUser.ProviderId.ToString(), ProviderUserId = providerUser.Id.ToString(), @@ -1168,6 +1171,23 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable adminEmails, Guid organizationId, string email, string userName) + { + var templateName = _globalSettings.SelfHosted ? + "AdminConsole.SelfHostNotifyAdminDeviceApprovalRequested" : + "AdminConsole.NotifyAdminDeviceApprovalRequested"; + var message = CreateDefaultMessage("Review SSO login request for new device", adminEmails); + var model = new DeviceApprovalRequestedViewModel + { + WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, + UserNameRequestingAccess = GetUserIdentifier(email, userName), + OrganizationId = organizationId, + }; + await AddMessageContentAsync(message, templateName, model); + message.Category = "DeviceApprovalRequested"; + await _mailDeliveryService.SendEmailAsync(message); + } + private static string GetUserIdentifier(string email, string userName) { return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false); diff --git a/src/Core/Services/Implementations/StripeAdapter.cs b/src/Core/Services/Implementations/StripeAdapter.cs index f4f8efe75f..f7f4fea066 100644 --- a/src/Core/Services/Implementations/StripeAdapter.cs +++ b/src/Core/Services/Implementations/StripeAdapter.cs @@ -9,7 +9,6 @@ public class StripeAdapter : IStripeAdapter private readonly Stripe.SubscriptionService _subscriptionService; private readonly Stripe.InvoiceService _invoiceService; private readonly Stripe.PaymentMethodService _paymentMethodService; - private readonly Stripe.TaxRateService _taxRateService; private readonly Stripe.TaxIdService _taxIdService; private readonly Stripe.ChargeService _chargeService; private readonly Stripe.RefundService _refundService; @@ -27,7 +26,6 @@ public class StripeAdapter : IStripeAdapter _subscriptionService = new Stripe.SubscriptionService(); _invoiceService = new Stripe.InvoiceService(); _paymentMethodService = new Stripe.PaymentMethodService(); - _taxRateService = new Stripe.TaxRateService(); _taxIdService = new Stripe.TaxIdService(); _chargeService = new Stripe.ChargeService(); _refundService = new Stripe.RefundService(); @@ -196,16 +194,6 @@ public class StripeAdapter : IStripeAdapter return _planService.GetAsync(id, options); } - public Task TaxRateCreateAsync(Stripe.TaxRateCreateOptions options) - { - return _taxRateService.CreateAsync(options); - } - - public Task TaxRateUpdateAsync(string id, Stripe.TaxRateUpdateOptions options) - { - return _taxRateService.UpdateAsync(id, options); - } - public Task TaxIdCreateAsync(string id, Stripe.TaxIdCreateOptions options) { return _taxIdService.CreateAsync(id, options); diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index d6b711f366..5d80659876 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -19,7 +19,6 @@ using Microsoft.Extensions.Logging; using Stripe; using PaymentMethod = Stripe.PaymentMethod; using StaticStore = Bit.Core.Models.StaticStore; -using TaxRate = Bit.Core.Entities.TaxRate; namespace Bit.Core.Services; @@ -33,7 +32,6 @@ public class StripePaymentService : IPaymentService private readonly ITransactionRepository _transactionRepository; private readonly ILogger _logger; private readonly Braintree.IBraintreeGateway _btGateway; - private readonly ITaxRateRepository _taxRateRepository; private readonly IStripeAdapter _stripeAdapter; private readonly IGlobalSettings _globalSettings; private readonly IFeatureService _featureService; @@ -43,7 +41,6 @@ public class StripePaymentService : IPaymentService public StripePaymentService( ITransactionRepository transactionRepository, ILogger logger, - ITaxRateRepository taxRateRepository, IStripeAdapter stripeAdapter, Braintree.IBraintreeGateway braintreeGateway, IGlobalSettings globalSettings, @@ -53,7 +50,6 @@ public class StripePaymentService : IPaymentService { _transactionRepository = transactionRepository; _logger = logger; - _taxRateRepository = taxRateRepository; _stripeAdapter = stripeAdapter; _btGateway = braintreeGateway; _globalSettings = globalSettings; @@ -123,7 +119,7 @@ public class StripePaymentService : IPaymentService Subscription subscription; try { - if (taxInfo.TaxIdNumber != null && taxInfo.TaxIdType == null) + if (!string.IsNullOrWhiteSpace(taxInfo.TaxIdNumber)) { taxInfo.TaxIdType = _taxService.GetStripeTaxCode(taxInfo.BillingAddressCountry, taxInfo.TaxIdNumber); @@ -171,7 +167,7 @@ public class StripePaymentService : IPaymentService City = taxInfo?.BillingAddressCity, State = taxInfo?.BillingAddressState, }, - TaxIdData = taxInfo.HasTaxId + TaxIdData = !string.IsNullOrWhiteSpace(taxInfo.TaxIdNumber) ? [new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber }] : null }; @@ -181,11 +177,7 @@ public class StripePaymentService : IPaymentService customer = await _stripeAdapter.CustomerCreateAsync(customerCreateOptions); subCreateOptions.AddExpand("latest_invoice.payment_intent"); subCreateOptions.Customer = customer.Id; - - if (CustomerHasTaxLocationVerified(customer)) - { - subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; - } + subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; subscription = await _stripeAdapter.SubscriptionCreateAsync(subCreateOptions); if (subscription.Status == "incomplete" && subscription.LatestInvoice?.PaymentIntent != null) @@ -366,13 +358,10 @@ public class StripePaymentService : IPaymentService customer = await _stripeAdapter.CustomerUpdateAsync(org.GatewayCustomerId, customerUpdateOptions); } - var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade); - - if (CustomerHasTaxLocationVerified(customer)) + var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade) { - subCreateOptions.DefaultTaxRates = []; - subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; - } + AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } + }; var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions); @@ -531,6 +520,10 @@ public class StripePaymentService : IPaymentService var customerCreateOptions = new CustomerCreateOptions { + Tax = new CustomerTaxOptions + { + ValidateLocation = StripeConstants.ValidateTaxLocationTiming.Immediately + }, Description = user.Name, Email = user.Email, Metadata = stripeCustomerMetadata, @@ -568,6 +561,7 @@ public class StripePaymentService : IPaymentService var subCreateOptions = new SubscriptionCreateOptions { + AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }, Customer = customer.Id, Items = [], Metadata = new Dictionary @@ -587,16 +581,10 @@ public class StripePaymentService : IPaymentService subCreateOptions.Items.Add(new SubscriptionItemOptions { Plan = StoragePlanId, - Quantity = additionalStorageGb + Quantity = additionalStorageGb, }); } - if (CustomerHasTaxLocationVerified(customer)) - { - subCreateOptions.DefaultTaxRates = []; - subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; - } - var subscription = await ChargeForNewSubscriptionAsync(user, customer, createdStripeCustomer, stripePaymentMethod, paymentMethodType, subCreateOptions, braintreeCustomer); @@ -634,10 +622,7 @@ public class StripePaymentService : IPaymentService SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items) }); - if (CustomerHasTaxLocationVerified(customer)) - { - previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true }; - } + previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true }; if (previewInvoice.AmountDue > 0) { @@ -695,14 +680,12 @@ public class StripePaymentService : IPaymentService Customer = customer.Id, SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items), SubscriptionDefaultTaxRates = subCreateOptions.DefaultTaxRates, + AutomaticTax = new InvoiceAutomaticTaxOptions + { + Enabled = true + } }; - if (CustomerHasTaxLocationVerified(customer)) - { - upcomingInvoiceOptions.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true }; - upcomingInvoiceOptions.SubscriptionDefaultTaxRates = []; - } - var previewInvoice = await _stripeAdapter.InvoiceUpcomingAsync(upcomingInvoiceOptions); if (previewInvoice.AmountDue > 0) @@ -821,7 +804,11 @@ public class StripePaymentService : IPaymentService Items = updatedItemOptions, ProrationBehavior = invoiceNow ? Constants.AlwaysInvoice : Constants.CreateProrations, DaysUntilDue = daysUntilDue ?? 1, - CollectionMethod = "send_invoice" + CollectionMethod = "send_invoice", + AutomaticTax = new SubscriptionAutomaticTaxOptions + { + Enabled = true + } }; if (!invoiceNow && isAnnualPlan && sub.Status.Trim() != "trialing") { @@ -829,13 +816,6 @@ public class StripePaymentService : IPaymentService new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" }; } - if (sub.AutomaticTax.Enabled != true && - CustomerHasTaxLocationVerified(sub.Customer)) - { - subUpdateOptions.DefaultTaxRates = []; - subUpdateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; - } - if (!subscriptionUpdate.UpdateNeeded(sub)) { // No need to update subscription, quantity matches @@ -1520,13 +1500,11 @@ public class StripePaymentService : IPaymentService if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) && customer.Subscriptions.Any(sub => sub.Id == subscriber.GatewaySubscriptionId && - !sub.AutomaticTax.Enabled) && - CustomerHasTaxLocationVerified(customer)) + !sub.AutomaticTax.Enabled)) { var subscriptionUpdateOptions = new SubscriptionUpdateOptions { - AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }, - DefaultTaxRates = [] + AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } }; _ = await _stripeAdapter.SubscriptionUpdateAsync( @@ -1778,50 +1756,6 @@ public class StripePaymentService : IPaymentService } } - public async Task CreateTaxRateAsync(TaxRate taxRate) - { - var stripeTaxRateOptions = new TaxRateCreateOptions() - { - DisplayName = $"{taxRate.Country} - {taxRate.PostalCode}", - Inclusive = false, - Percentage = taxRate.Rate, - Active = true - }; - var stripeTaxRate = await _stripeAdapter.TaxRateCreateAsync(stripeTaxRateOptions); - taxRate.Id = stripeTaxRate.Id; - await _taxRateRepository.CreateAsync(taxRate); - return taxRate; - } - - public async Task UpdateTaxRateAsync(TaxRate taxRate) - { - if (string.IsNullOrWhiteSpace(taxRate.Id)) - { - return; - } - - await ArchiveTaxRateAsync(taxRate); - await CreateTaxRateAsync(taxRate); - } - - public async Task ArchiveTaxRateAsync(TaxRate taxRate) - { - if (string.IsNullOrWhiteSpace(taxRate.Id)) - { - return; - } - - var updatedStripeTaxRate = await _stripeAdapter.TaxRateUpdateAsync( - taxRate.Id, - new TaxRateUpdateOptions() { Active = false } - ); - if (!updatedStripeTaxRate.Active) - { - taxRate.Active = false; - await _taxRateRepository.ArchiveAsync(taxRate); - } - } - public async Task AddSecretsManagerToSubscription( Organization org, StaticStore.Plan plan, @@ -1918,7 +1852,6 @@ public class StripePaymentService : IPaymentService Enabled = true, }, Currency = "usd", - Discounts = new List(), SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = @@ -1969,29 +1902,23 @@ public class StripePaymentService : IPaymentService ]; } - if (gatewayCustomerId != null) + if (!string.IsNullOrWhiteSpace(gatewayCustomerId)) { var gatewayCustomer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId); if (gatewayCustomer.Discount != null) { - options.Discounts.Add(new InvoiceDiscountOptions - { - Discount = gatewayCustomer.Discount.Id - }); + options.Coupon = gatewayCustomer.Discount.Coupon.Id; } + } - if (gatewaySubscriptionId != null) + if (!string.IsNullOrWhiteSpace(gatewaySubscriptionId)) + { + var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId); + + if (gatewaySubscription?.Discount != null) { - var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId); - - if (gatewaySubscription?.Discount != null) - { - options.Discounts.Add(new InvoiceDiscountOptions - { - Discount = gatewaySubscription.Discount.Id - }); - } + options.Coupon ??= gatewaySubscription.Discount.Coupon.Id; } } @@ -2042,7 +1969,6 @@ public class StripePaymentService : IPaymentService Enabled = true, }, Currency = "usd", - Discounts = new List(), SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = @@ -2106,7 +2032,7 @@ public class StripePaymentService : IPaymentService } } - if (!string.IsNullOrEmpty(parameters.TaxInformation.TaxId)) + if (!string.IsNullOrWhiteSpace(parameters.TaxInformation.TaxId)) { var taxIdType = _taxService.GetStripeTaxCode( options.CustomerDetails.Address.Country, @@ -2129,26 +2055,23 @@ public class StripePaymentService : IPaymentService ]; } - if (gatewayCustomerId != null) + if (!string.IsNullOrWhiteSpace(gatewayCustomerId)) { var gatewayCustomer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId); if (gatewayCustomer.Discount != null) { - options.Discounts.Add(new InvoiceDiscountOptions - { - Discount = gatewayCustomer.Discount.Id - }); + options.Coupon = gatewayCustomer.Discount.Coupon.Id; } + } + if (!string.IsNullOrWhiteSpace(gatewaySubscriptionId)) + { var gatewaySubscription = await _stripeAdapter.SubscriptionGetAsync(gatewaySubscriptionId); if (gatewaySubscription?.Discount != null) { - options.Discounts.Add(new InvoiceDiscountOptions - { - Discount = gatewaySubscription.Discount.Id - }); + options.Coupon ??= gatewaySubscription.Discount.Coupon.Id; } } @@ -2328,14 +2251,6 @@ public class StripePaymentService : IPaymentService } } - /// - /// Determines if a Stripe customer supports automatic tax - /// - /// - /// - private static bool CustomerHasTaxLocationVerified(Customer customer) => - customer?.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported; - // We are taking only first 30 characters of the SubscriberName because stripe provide // for 30 characters for custom_fields,see the link: https://stripe.com/docs/api/invoices/create private static string GetFirstThirtyCharacters(string subscriberName) diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 4944dfe9e7..e04290a686 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Auth.Models.Business.Tokenables; +using Bit.Core.Billing.Models; using Bit.Core.Billing.Models.Sales; using Bit.Core.Billing.Services; using Bit.Core.Context; @@ -314,7 +315,7 @@ public class UserService : UserManager, IUserService, IDisposable return; } - var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount"); + var token = await GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount"); await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token); } @@ -867,6 +868,10 @@ public class UserService : UserManager, IUserService, IDisposable } } + /// + /// To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175. + /// + [Obsolete("Two Factor recovery is handled in the TwoFactorAuthenticationValidator.")] public async Task RecoverTwoFactorAsync(string email, string secret, string recoveryCode) { var user = await _userRepository.GetByEmailAsync(email); @@ -896,6 +901,25 @@ public class UserService : UserManager, IUserService, IDisposable return true; } + public async Task RecoverTwoFactorAsync(User user, string recoveryCode) + { + if (!CoreHelpers.FixedTimeEquals( + user.TwoFactorRecoveryCode, + recoveryCode.Replace(" ", string.Empty).Trim().ToLower())) + { + return false; + } + + user.TwoFactorProviders = null; + user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false); + await SaveUserAsync(user); + await _mailService.SendRecoverTwoFactorEmail(user.Email, DateTime.UtcNow, _currentContext.IpAddress); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa); + await CheckPoliciesOnTwoFactorRemovalAsync(user); + + return true; + } + public async Task> SignUpPremiumAsync(User user, string paymentToken, PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license, TaxInfo taxInfo) @@ -933,18 +957,8 @@ public class UserService : UserManager, IUserService, IDisposable } else { - var deprecateStripeSourcesAPI = _featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI); - - if (deprecateStripeSourcesAPI) - { - var sale = PremiumUserSale.From(user, paymentMethodType, paymentToken, taxInfo, additionalStorageGb); - await _premiumUserBillingService.Finalize(sale); - } - else - { - paymentIntentClientSecret = await _paymentService.PurchasePremiumAsync(user, paymentMethodType, - paymentToken, additionalStorageGb, taxInfo); - } + var sale = PremiumUserSale.From(user, paymentMethodType, paymentToken, taxInfo, additionalStorageGb); + await _premiumUserBillingService.Finalize(sale); } user.Premium = true; @@ -1054,11 +1068,11 @@ public class UserService : UserManager, IUserService, IDisposable throw new BadRequestException("Invalid token."); } - var updated = await _paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, taxInfo: taxInfo); - if (updated) - { - await SaveUserAsync(user); - } + var tokenizedPaymentSource = new TokenizedPaymentSource(paymentMethodType, paymentToken); + var taxInformation = TaxInformation.From(taxInfo); + + await _premiumUserBillingService.UpdatePaymentMethod(user, tokenizedPaymentSource, taxInformation); + await SaveUserAsync(user); } public async Task CancelPremiumAsync(User user, bool? endOfPeriod = null) @@ -1090,7 +1104,7 @@ public class UserService : UserManager, IUserService, IDisposable await EnablePremiumAsync(user, expirationDate); } - public async Task EnablePremiumAsync(User user, DateTime? expirationDate) + private async Task EnablePremiumAsync(User user, DateTime? expirationDate) { if (user != null && !user.Premium && user.Gateway.HasValue) { @@ -1107,7 +1121,7 @@ public class UserService : UserManager, IUserService, IDisposable await DisablePremiumAsync(user, expirationDate); } - public async Task DisablePremiumAsync(User user, DateTime? expirationDate) + private async Task DisablePremiumAsync(User user, DateTime? expirationDate) { if (user != null && user.Premium) { @@ -1372,19 +1386,18 @@ public class UserService : UserManager, IUserService, IDisposable private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user) { var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication); - var organizationsManagingUser = await GetOrganizationsManagingUserAsync(user.Id); var removeOrgUserTasks = twoFactorPolicies.Select(async p => { var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId); - if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && organizationsManagingUser.Any(o => o.Id == p.OrganizationId)) + if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) { await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync( new RevokeOrganizationUsersRequest( p.OrganizationId, - [new OrganizationUserUserDetails { UserId = user.Id, OrganizationId = p.OrganizationId }], + [new OrganizationUserUserDetails { Id = p.OrganizationUserId, OrganizationId = p.OrganizationId }], new SystemUser(EventSystemUser.TwoFactorDisabled))); - await _mailService.SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization.DisplayName(), user.Email); + await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email); } else { diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 4ce188c86b..d6b330294d 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -26,6 +26,7 @@ public class NoopMailService : IMailService } public Task SendTrialInitiationSignupEmailAsync( + bool isExistingUser, string email, string token, ProductTierType productTier, @@ -80,7 +81,7 @@ public class NoopMailService : IMailService return Task.FromResult(0); } - public Task SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(string organizationName, string email) => + public Task SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(string organizationName, string email) => Task.CompletedTask; public Task SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(string organizationName, string email) => @@ -316,5 +317,10 @@ public class NoopMailService : IMailService return Task.FromResult(0); } public Task SendClaimedDomainUserEmailAsync(ManagedUserDomainClaimedEmails emailList) => Task.CompletedTask; + + public Task SendDeviceApprovalRequestedNotificationEmailAsync(IEnumerable adminEmails, Guid organizationId, string email, string userName) + { + return Task.FromResult(0); + } } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 420151a34f..a1c7a4fac6 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -53,6 +53,7 @@ public class GlobalSettings : IGlobalSettings public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings(); public virtual SqlSettings MySql { get; set; } = new SqlSettings(); public virtual SqlSettings Sqlite { get; set; } = new SqlSettings() { ConnectionString = "Data Source=:memory:" }; + public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings(); public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings(); @@ -69,6 +70,7 @@ public class GlobalSettings : IGlobalSettings public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings(); public virtual DuoSettings Duo { get; set; } = new DuoSettings(); public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings(); + public virtual ImportCiphersLimitationSettings ImportCiphersLimitation { get; set; } = new ImportCiphersLimitationSettings(); public virtual BitPaySettings BitPay { get; set; } = new BitPaySettings(); public virtual AmazonSettings Amazon { get; set; } = new AmazonSettings(); public virtual ServiceBusSettings ServiceBus { get; set; } = new ServiceBusSettings(); @@ -82,6 +84,7 @@ public class GlobalSettings : IGlobalSettings public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings(); public virtual string DevelopmentDirectory { get; set; } public virtual bool EnableEmailVerification { get; set; } + public virtual string KdfDefaultHashKey { get; set; } public virtual string PricingUri { get; set; } public string BuildExternalUri(string explicitValue, string name) @@ -255,6 +258,66 @@ public class GlobalSettings : IGlobalSettings } } + public class EventLoggingSettings + { + public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings(); + public virtual string WebhookUrl { get; set; } + public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings(); + + public class AzureServiceBusSettings + { + private string _connectionString; + private string _topicName; + + public virtual string EventRepositorySubscriptionName { get; set; } = "events-write-subscription"; + public virtual string WebhookSubscriptionName { get; set; } = "events-webhook-subscription"; + + public string ConnectionString + { + get => _connectionString; + set => _connectionString = value.Trim('"'); + } + + public string TopicName + { + get => _topicName; + set => _topicName = value.Trim('"'); + } + } + + public class RabbitMqSettings + { + private string _hostName; + private string _username; + private string _password; + private string _exchangeName; + + public virtual string EventRepositoryQueueName { get; set; } = "events-write-queue"; + public virtual string WebhookQueueName { get; set; } = "events-webhook-queue"; + + public string HostName + { + get => _hostName; + set => _hostName = value.Trim('"'); + } + public string Username + { + get => _username; + set => _username = value.Trim('"'); + } + public string Password + { + get => _password; + set => _password = value.Trim('"'); + } + public string ExchangeName + { + get => _exchangeName; + set => _exchangeName = value.Trim('"'); + } + } + } + public class ConnectionStringSettings : IConnectionStringSettings { private string _connectionString; @@ -481,6 +544,13 @@ public class GlobalSettings : IGlobalSettings public string PrivateKey { get; set; } } + public class ImportCiphersLimitationSettings + { + public int CiphersLimit { get; set; } + public int CollectionRelationshipsLimit { get; set; } + public int CollectionsLimit { get; set; } + } + public class BitPaySettings { public bool Production { get; set; } diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index afe35ed34b..b89df8abf5 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -27,4 +27,5 @@ public interface IGlobalSettings string DatabaseProvider { get; set; } GlobalSettings.SqlSettings SqlServer { get; set; } string DevelopmentDirectory { get; set; } + GlobalSettings.EventLoggingSettings EventLogging { get; set; } } diff --git a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs new file mode 100644 index 0000000000..646121db52 --- /dev/null +++ b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs @@ -0,0 +1,199 @@ +using Bit.Core.AdminConsole.Enums; +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.Tools.Enums; +using Bit.Core.Tools.ImportFeatures.Interfaces; +using Bit.Core.Tools.Models.Business; +using Bit.Core.Tools.Services; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Models.Data; +using Bit.Core.Vault.Repositories; + +namespace Bit.Core.Tools.ImportFeatures; + +public class ImportCiphersCommand : IImportCiphersCommand +{ + private readonly ICipherRepository _cipherRepository; + private readonly IFolderRepository _folderRepository; + private readonly IPushNotificationService _pushService; + private readonly IPolicyService _policyService; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ICollectionRepository _collectionRepository; + private readonly IReferenceEventService _referenceEventService; + private readonly ICurrentContext _currentContext; + + + public ImportCiphersCommand( + ICipherRepository cipherRepository, + IFolderRepository folderRepository, + ICollectionRepository collectionRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository, + IPushNotificationService pushService, + IPolicyService policyService, + IReferenceEventService referenceEventService, + ICurrentContext currentContext) + { + _cipherRepository = cipherRepository; + _folderRepository = folderRepository; + _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; + _collectionRepository = collectionRepository; + _pushService = pushService; + _policyService = policyService; + _referenceEventService = referenceEventService; + _currentContext = currentContext; + } + + + public async Task ImportIntoIndividualVaultAsync( + List folders, + List ciphers, + IEnumerable> folderRelationships) + { + var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId; + + // Make sure the user can save new ciphers to their personal vault + var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value, PolicyType.PersonalOwnership); + if (anyPersonalOwnershipPolicies) + { + throw new BadRequestException("You cannot import items into your personal vault because you are " + + "a member of an organization which forbids it."); + } + + foreach (var cipher in ciphers) + { + cipher.SetNewId(); + + if (cipher.UserId.HasValue && cipher.Favorite) + { + cipher.Favorites = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":\"true\"}}"; + } + } + + var userfoldersIds = (await _folderRepository.GetManyByUserIdAsync(userId ?? Guid.Empty)).Select(f => f.Id).ToList(); + + //Assign id to the ones that don't exist in DB + //Need to keep the list order to create the relationships + List newFolders = new List(); + foreach (var folder in folders) + { + if (!userfoldersIds.Contains(folder.Id)) + { + folder.SetNewId(); + newFolders.Add(folder); + } + } + + // Create the folder associations based on the newly created folder ids + foreach (var relationship in folderRelationships) + { + var cipher = ciphers.ElementAtOrDefault(relationship.Key); + var folder = folders.ElementAtOrDefault(relationship.Value); + + if (cipher == null || folder == null) + { + continue; + } + + cipher.Folders = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":" + + $"\"{folder.Id.ToString().ToUpperInvariant()}\"}}"; + } + + // Create it all + await _cipherRepository.CreateAsync(ciphers, newFolders); + + // push + if (userId.HasValue) + { + await _pushService.PushSyncVaultAsync(userId.Value); + } + } + + public async Task ImportIntoOrganizationalVaultAsync( + List collections, + List ciphers, + IEnumerable> collectionRelationships, + Guid importingUserId) + { + var org = collections.Count > 0 ? + await _organizationRepository.GetByIdAsync(collections[0].OrganizationId) : + await _organizationRepository.GetByIdAsync(ciphers.FirstOrDefault(c => c.OrganizationId.HasValue).OrganizationId.Value); + var importingOrgUser = await _organizationUserRepository.GetByOrganizationAsync(org.Id, importingUserId); + + if (collections.Count > 0 && org != null && org.MaxCollections.HasValue) + { + var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); + if (org.MaxCollections.Value < (collectionCount + collections.Count)) + { + throw new BadRequestException("This organization can only have a maximum of " + + $"{org.MaxCollections.Value} collections."); + } + } + + // Init. ids for ciphers + foreach (var cipher in ciphers) + { + cipher.SetNewId(); + } + + var organizationCollectionsIds = (await _collectionRepository.GetManyByOrganizationIdAsync(org.Id)).Select(c => c.Id).ToList(); + + //Assign id to the ones that don't exist in DB + //Need to keep the list order to create the relationships + var newCollections = new List(); + var newCollectionUsers = new List(); + + foreach (var collection in collections) + { + if (!organizationCollectionsIds.Contains(collection.Id)) + { + collection.SetNewId(); + newCollections.Add(collection); + newCollectionUsers.Add(new CollectionUser + { + CollectionId = collection.Id, + OrganizationUserId = importingOrgUser.Id, + Manage = true + }); + } + } + + // Create associations based on the newly assigned ids + var collectionCiphers = new List(); + foreach (var relationship in collectionRelationships) + { + var cipher = ciphers.ElementAtOrDefault(relationship.Key); + var collection = collections.ElementAtOrDefault(relationship.Value); + + if (cipher == null || collection == null) + { + continue; + } + + collectionCiphers.Add(new CollectionCipher + { + CipherId = cipher.Id, + CollectionId = collection.Id + }); + } + + // Create it all + await _cipherRepository.CreateAsync(ciphers, newCollections, collectionCiphers, newCollectionUsers); + + // push + await _pushService.PushSyncVaultAsync(importingUserId); + + + if (org != null) + { + await _referenceEventService.RaiseEventAsync( + new ReferenceEvent(ReferenceEventType.VaultImported, org, _currentContext)); + } + } +} diff --git a/src/Core/Tools/ImportFeatures/ImportServiceCollectionExtension.cs b/src/Core/Tools/ImportFeatures/ImportServiceCollectionExtension.cs new file mode 100644 index 0000000000..38c88d7994 --- /dev/null +++ b/src/Core/Tools/ImportFeatures/ImportServiceCollectionExtension.cs @@ -0,0 +1,12 @@ +using Bit.Core.Tools.ImportFeatures.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Tools.ImportFeatures; + +public static class ImportServiceCollectionExtension +{ + public static void AddImportServices(this IServiceCollection services) + { + services.AddScoped(); + } +} diff --git a/src/Core/Tools/ImportFeatures/Interfaces/IImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/Interfaces/IImportCiphersCommand.cs new file mode 100644 index 0000000000..378024d3a0 --- /dev/null +++ b/src/Core/Tools/ImportFeatures/Interfaces/IImportCiphersCommand.cs @@ -0,0 +1,14 @@ +using Bit.Core.Entities; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Models.Data; + +namespace Bit.Core.Tools.ImportFeatures.Interfaces; + +public interface IImportCiphersCommand +{ + Task ImportIntoIndividualVaultAsync(List folders, List ciphers, + IEnumerable> folderRelationships); + + Task ImportIntoOrganizationalVaultAsync(List collections, List ciphers, + IEnumerable> collectionRelationships, Guid importingUserId); +} diff --git a/src/Core/Vault/Authorization/SecurityTaskOperations.cs b/src/Core/Vault/Authorization/SecurityTaskOperations.cs deleted file mode 100644 index 77b504723f..0000000000 --- a/src/Core/Vault/Authorization/SecurityTaskOperations.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Authorization.Infrastructure; - -namespace Bit.Core.Vault.Authorization; - -public class SecurityTaskOperationRequirement : OperationAuthorizationRequirement -{ - public SecurityTaskOperationRequirement(string name) - { - Name = name; - } -} - -public static class SecurityTaskOperations -{ - public static readonly SecurityTaskOperationRequirement Update = new(nameof(Update)); -} diff --git a/src/Core/Vault/Commands/CreateManyTasksCommand.cs b/src/Core/Vault/Commands/CreateManyTasksCommand.cs new file mode 100644 index 0000000000..1b21f202eb --- /dev/null +++ b/src/Core/Vault/Commands/CreateManyTasksCommand.cs @@ -0,0 +1,65 @@ +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Utilities; +using Bit.Core.Vault.Authorization.SecurityTasks; +using Bit.Core.Vault.Commands.Interfaces; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Enums; +using Bit.Core.Vault.Models.Api; +using Bit.Core.Vault.Repositories; +using Microsoft.AspNetCore.Authorization; + +namespace Bit.Core.Vault.Commands; + +public class CreateManyTasksCommand : ICreateManyTasksCommand +{ + private readonly IAuthorizationService _authorizationService; + private readonly ICurrentContext _currentContext; + private readonly ISecurityTaskRepository _securityTaskRepository; + + public CreateManyTasksCommand( + ISecurityTaskRepository securityTaskRepository, + IAuthorizationService authorizationService, + ICurrentContext currentContext) + { + _securityTaskRepository = securityTaskRepository; + _authorizationService = authorizationService; + _currentContext = currentContext; + } + + /// + public async Task> CreateAsync(Guid organizationId, + IEnumerable tasks) + { + if (!_currentContext.UserId.HasValue) + { + throw new NotFoundException(); + } + + var tasksList = tasks?.ToList(); + + if (tasksList is null || tasksList.Count == 0) + { + throw new BadRequestException("No tasks provided."); + } + + var securityTasks = tasksList.Select(t => new SecurityTask + { + OrganizationId = organizationId, + CipherId = t.CipherId, + Type = t.Type, + Status = SecurityTaskStatus.Pending + }).ToList(); + + // Verify authorization for each task + foreach (var task in securityTasks) + { + await _authorizationService.AuthorizeOrThrowAsync( + _currentContext.HttpContext.User, + task, + SecurityTaskOperations.Create); + } + + return await _securityTaskRepository.CreateManyAsync(securityTasks); + } +} diff --git a/src/Core/Vault/Commands/Interfaces/ICreateManyTasksCommand.cs b/src/Core/Vault/Commands/Interfaces/ICreateManyTasksCommand.cs new file mode 100644 index 0000000000..3aa0f85070 --- /dev/null +++ b/src/Core/Vault/Commands/Interfaces/ICreateManyTasksCommand.cs @@ -0,0 +1,17 @@ +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Models.Api; + +namespace Bit.Core.Vault.Commands.Interfaces; + +public interface ICreateManyTasksCommand +{ + /// + /// Creates multiple security tasks for an organization. + /// Each task must be authorized and the user must have the Create permission + /// and associated ciphers must belong to the organization. + /// + /// The + /// + /// Collection of created security tasks + Task> CreateAsync(Guid organizationId, IEnumerable tasks); +} diff --git a/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs b/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs index b46fb0cecb..77b8a8625c 100644 --- a/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs +++ b/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs @@ -1,7 +1,7 @@ using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Utilities; -using Bit.Core.Vault.Authorization; +using Bit.Core.Vault.Authorization.SecurityTasks; using Bit.Core.Vault.Commands.Interfaces; using Bit.Core.Vault.Enums; using Bit.Core.Vault.Repositories; diff --git a/src/Core/Vault/Models/Api/SecurityTaskCreateRequest.cs b/src/Core/Vault/Models/Api/SecurityTaskCreateRequest.cs new file mode 100644 index 0000000000..f865871380 --- /dev/null +++ b/src/Core/Vault/Models/Api/SecurityTaskCreateRequest.cs @@ -0,0 +1,9 @@ +using Bit.Core.Vault.Enums; + +namespace Bit.Core.Vault.Models.Api; + +public class SecurityTaskCreateRequest +{ + public SecurityTaskType Type { get; set; } + public Guid? CipherId { get; set; } +} diff --git a/src/Core/Vault/Models/Data/DeleteAttachmentReponseData.cs b/src/Core/Vault/Models/Data/DeleteAttachmentReponseData.cs new file mode 100644 index 0000000000..0a5e755572 --- /dev/null +++ b/src/Core/Vault/Models/Data/DeleteAttachmentReponseData.cs @@ -0,0 +1,13 @@ +using Bit.Core.Vault.Entities; + +namespace Bit.Core.Vault.Models.Data; + +public class DeleteAttachmentResponseData +{ + public Cipher Cipher { get; set; } + + public DeleteAttachmentResponseData(Cipher cipher) + { + Cipher = cipher; + } +} diff --git a/src/Core/Vault/Repositories/ISecurityTaskRepository.cs b/src/Core/Vault/Repositories/ISecurityTaskRepository.cs index c236172533..cc8303345d 100644 --- a/src/Core/Vault/Repositories/ISecurityTaskRepository.cs +++ b/src/Core/Vault/Repositories/ISecurityTaskRepository.cs @@ -21,4 +21,11 @@ public interface ISecurityTaskRepository : IRepository /// Optional filter for task status. If not provided, returns tasks of all statuses /// Task> GetManyByOrganizationIdStatusAsync(Guid organizationId, SecurityTaskStatus? status = null); + + /// + /// Creates bulk security tasks for an organization. + /// + /// Collection of tasks to create + /// Collection of created security tasks + Task> CreateManyAsync(IEnumerable tasks); } diff --git a/src/Core/Vault/Services/ICipherService.cs b/src/Core/Vault/Services/ICipherService.cs index 83cd729e13..17f55cb47d 100644 --- a/src/Core/Vault/Services/ICipherService.cs +++ b/src/Core/Vault/Services/ICipherService.cs @@ -1,5 +1,4 @@ -using Bit.Core.Entities; -using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Entities; using Bit.Core.Vault.Models.Data; namespace Bit.Core.Vault.Services; @@ -18,7 +17,7 @@ public interface ICipherService string attachmentId, Guid organizationShareId); Task DeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false); Task DeleteManyAsync(IEnumerable cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false); - Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false); + Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false); Task PurgeAsync(Guid organizationId); Task MoveManyAsync(IEnumerable cipherIds, Guid? destinationFolderId, Guid movingUserId); Task SaveFolderAsync(Folder folder); @@ -28,10 +27,6 @@ public interface ICipherService Task ShareManyAsync(IEnumerable<(Cipher cipher, DateTime? lastKnownRevisionDate)> ciphers, Guid organizationId, IEnumerable collectionIds, Guid sharingUserId); Task SaveCollectionsAsync(Cipher cipher, IEnumerable collectionIds, Guid savingUserId, bool orgAdmin); - Task ImportCiphersAsync(List folders, List ciphers, - IEnumerable> folderRelationships); - Task ImportCiphersAsync(List collections, List ciphers, - IEnumerable> collectionRelationships, Guid importingUserId); Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false); Task SoftDeleteManyAsync(IEnumerable cipherIds, Guid deletingUserId, Guid? organizationId = null, bool orgAdmin = false); Task RestoreAsync(Cipher cipher, Guid restoringUserId, bool orgAdmin = false); @@ -39,5 +34,4 @@ public interface ICipherService Task UploadFileForExistingAttachmentAsync(Stream stream, Cipher cipher, CipherAttachment.MetaData attachmentId); Task GetAttachmentDownloadDataAsync(Cipher cipher, string attachmentId); Task ValidateCipherAttachmentFile(Cipher cipher, CipherAttachment.MetaData attachmentData); - Task<(IEnumerable, Dictionary>)> GetOrganizationCiphers(Guid userId, Guid organizationId); } diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index d6806bd115..90c03df90b 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -2,7 +2,6 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Services; using Bit.Core.Context; -using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Platform.Push; @@ -211,6 +210,11 @@ public class CipherService : ICipherService AttachmentData = JsonSerializer.Serialize(data) }); cipher.AddAttachment(attachmentId, data); + + // Update the revision date when an attachment is added + cipher.RevisionDate = DateTime.UtcNow; + await _cipherRepository.ReplaceAsync((CipherDetails)cipher); + await _pushService.PushSyncCipherUpdateAsync(cipher, null); return (attachmentId, uploadUrl); @@ -260,6 +264,10 @@ public class CipherService : ICipherService throw; } + // Update the revision date when an attachment is added + cipher.RevisionDate = DateTime.UtcNow; + await _cipherRepository.ReplaceAsync((CipherDetails)cipher); + // push await _pushService.PushSyncCipherUpdateAsync(cipher, null); } @@ -442,7 +450,7 @@ public class CipherService : ICipherService await _pushService.PushSyncCiphersAsync(deletingUserId); } - public async Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, + public async Task DeleteAttachmentAsync(Cipher cipher, string attachmentId, Guid deletingUserId, bool orgAdmin = false) { if (!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId))) @@ -455,7 +463,7 @@ public class CipherService : ICipherService throw new NotFoundException(); } - await DeleteAttachmentAsync(cipher, cipher.GetAttachments()[attachmentId]); + return await DeleteAttachmentAsync(cipher, cipher.GetAttachments()[attachmentId]); } public async Task PurgeAsync(Guid organizationId) @@ -679,152 +687,6 @@ public class CipherService : ICipherService await _pushService.PushSyncCipherUpdateAsync(cipher, collectionIds); } - public async Task ImportCiphersAsync( - List folders, - List ciphers, - IEnumerable> folderRelationships) - { - var userId = folders.FirstOrDefault()?.UserId ?? ciphers.FirstOrDefault()?.UserId; - - // Make sure the user can save new ciphers to their personal vault - var anyPersonalOwnershipPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId.Value, PolicyType.PersonalOwnership); - if (anyPersonalOwnershipPolicies) - { - throw new BadRequestException("You cannot import items into your personal vault because you are " + - "a member of an organization which forbids it."); - } - - foreach (var cipher in ciphers) - { - cipher.SetNewId(); - - if (cipher.UserId.HasValue && cipher.Favorite) - { - cipher.Favorites = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":\"true\"}}"; - } - } - - var userfoldersIds = (await _folderRepository.GetManyByUserIdAsync(userId ?? Guid.Empty)).Select(f => f.Id).ToList(); - - //Assign id to the ones that don't exist in DB - //Need to keep the list order to create the relationships - List newFolders = new List(); - foreach (var folder in folders) - { - if (!userfoldersIds.Contains(folder.Id)) - { - folder.SetNewId(); - newFolders.Add(folder); - } - } - - // Create the folder associations based on the newly created folder ids - foreach (var relationship in folderRelationships) - { - var cipher = ciphers.ElementAtOrDefault(relationship.Key); - var folder = folders.ElementAtOrDefault(relationship.Value); - - if (cipher == null || folder == null) - { - continue; - } - - cipher.Folders = $"{{\"{cipher.UserId.ToString().ToUpperInvariant()}\":" + - $"\"{folder.Id.ToString().ToUpperInvariant()}\"}}"; - } - - // Create it all - await _cipherRepository.CreateAsync(ciphers, newFolders); - - // push - if (userId.HasValue) - { - await _pushService.PushSyncVaultAsync(userId.Value); - } - } - - public async Task ImportCiphersAsync( - List collections, - List ciphers, - IEnumerable> collectionRelationships, - Guid importingUserId) - { - var org = collections.Count > 0 ? - await _organizationRepository.GetByIdAsync(collections[0].OrganizationId) : - await _organizationRepository.GetByIdAsync(ciphers.FirstOrDefault(c => c.OrganizationId.HasValue).OrganizationId.Value); - var importingOrgUser = await _organizationUserRepository.GetByOrganizationAsync(org.Id, importingUserId); - - if (collections.Count > 0 && org != null && org.MaxCollections.HasValue) - { - var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); - if (org.MaxCollections.Value < (collectionCount + collections.Count)) - { - throw new BadRequestException("This organization can only have a maximum of " + - $"{org.MaxCollections.Value} collections."); - } - } - - // Init. ids for ciphers - foreach (var cipher in ciphers) - { - cipher.SetNewId(); - } - - var organizationCollectionsIds = (await _collectionRepository.GetManyByOrganizationIdAsync(org.Id)).Select(c => c.Id).ToList(); - - //Assign id to the ones that don't exist in DB - //Need to keep the list order to create the relationships - var newCollections = new List(); - var newCollectionUsers = new List(); - - foreach (var collection in collections) - { - if (!organizationCollectionsIds.Contains(collection.Id)) - { - collection.SetNewId(); - newCollections.Add(collection); - newCollectionUsers.Add(new CollectionUser - { - CollectionId = collection.Id, - OrganizationUserId = importingOrgUser.Id, - Manage = true - }); - } - } - - // Create associations based on the newly assigned ids - var collectionCiphers = new List(); - foreach (var relationship in collectionRelationships) - { - var cipher = ciphers.ElementAtOrDefault(relationship.Key); - var collection = collections.ElementAtOrDefault(relationship.Value); - - if (cipher == null || collection == null) - { - continue; - } - - collectionCiphers.Add(new CollectionCipher - { - CipherId = cipher.Id, - CollectionId = collection.Id - }); - } - - // Create it all - await _cipherRepository.CreateAsync(ciphers, newCollections, collectionCiphers, newCollectionUsers); - - // push - await _pushService.PushSyncVaultAsync(importingUserId); - - - if (org != null) - { - await _referenceEventService.RaiseEventAsync( - new ReferenceEvent(ReferenceEventType.VaultImported, org, _currentContext)); - } - } - public async Task SoftDeleteAsync(Cipher cipher, Guid deletingUserId, bool orgAdmin = false) { if (!orgAdmin && !(await UserCanEditAsync(cipher, deletingUserId))) @@ -956,35 +818,6 @@ public class CipherService : ICipherService return restoringCiphers; } - public async Task<(IEnumerable, Dictionary>)> GetOrganizationCiphers(Guid userId, Guid organizationId) - { - if (!await _currentContext.ViewAllCollections(organizationId) && !await _currentContext.AccessReports(organizationId) && !await _currentContext.AccessImportExport(organizationId)) - { - throw new NotFoundException(); - } - - IEnumerable orgCiphers; - if (await _currentContext.AccessImportExport(organizationId)) - { - // Admins, Owners, Providers and Custom (with import/export permission) can access all items even if not assigned to them - orgCiphers = await _cipherRepository.GetManyOrganizationDetailsByOrganizationIdAsync(organizationId); - } - else - { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: true); - orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId); - } - - var orgCipherIds = orgCiphers.Select(c => c.Id); - - var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(organizationId); - var collectionCiphersGroupDict = collectionCiphers - .Where(c => orgCipherIds.Contains(c.CipherId)) - .GroupBy(c => c.CipherId).ToDictionary(s => s.Key); - - return (orgCiphers, collectionCiphersGroupDict); - } - private async Task UserCanEditAsync(Cipher cipher, Guid userId) { if (!cipher.OrganizationId.HasValue && cipher.UserId.HasValue && cipher.UserId.Value == userId) @@ -1010,11 +843,11 @@ public class CipherService : ICipherService } } - private async Task DeleteAttachmentAsync(Cipher cipher, CipherAttachment.MetaData attachmentData) + private async Task DeleteAttachmentAsync(Cipher cipher, CipherAttachment.MetaData attachmentData) { if (attachmentData == null || string.IsNullOrWhiteSpace(attachmentData.AttachmentId)) { - return; + return null; } await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentData.AttachmentId); @@ -1022,8 +855,14 @@ public class CipherService : ICipherService await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentData); await _eventService.LogCipherEventAsync(cipher, Bit.Core.Enums.EventType.Cipher_AttachmentDeleted); + // Update the revision date when an attachment is deleted + cipher.RevisionDate = DateTime.UtcNow; + await _cipherRepository.ReplaceAsync((CipherDetails)cipher); + // push await _pushService.PushSyncCipherUpdateAsync(cipher, null); + + return new DeleteAttachmentResponseData(cipher); } private async Task ValidateCipherEditForAttachmentAsync(Cipher cipher, Guid savingUserId, bool orgAdmin, diff --git a/src/Core/Vault/VaultServiceCollectionExtensions.cs b/src/Core/Vault/VaultServiceCollectionExtensions.cs index 4995d0405f..fcb9259135 100644 --- a/src/Core/Vault/VaultServiceCollectionExtensions.cs +++ b/src/Core/Vault/VaultServiceCollectionExtensions.cs @@ -20,5 +20,7 @@ public static class VaultServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index bac39c68dd..57af285b03 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Bit.Core.AdminConsole.Services.Implementations; using Bit.Core.Context; using Bit.Core.IdentityServer; using Bit.Core.Services; @@ -63,11 +64,29 @@ public class Startup services.AddScoped(); if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } else { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } services.AddOptionality(); @@ -82,6 +101,35 @@ public class Startup { services.AddHostedService(); } + + // Optional RabbitMQ Listeners + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) + { + services.AddSingleton(); + services.AddKeyedSingleton("persistent"); + services.AddSingleton(provider => + new RabbitMqEventListenerService( + provider.GetRequiredService(), + provider.GetRequiredService>(), + globalSettings, + globalSettings.EventLogging.RabbitMq.EventRepositoryQueueName)); + + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.WebhookUrl)) + { + services.AddSingleton(); + services.AddHttpClient(WebhookEventHandler.HttpClientName); + + services.AddSingleton(provider => + new RabbitMqEventListenerService( + provider.GetRequiredService(), + provider.GetRequiredService>(), + globalSettings, + globalSettings.EventLogging.RabbitMq.WebhookQueueName)); + } + } } public void Configure( diff --git a/src/EventsProcessor/Startup.cs b/src/EventsProcessor/Startup.cs index 2f64c0f926..65d1d36e24 100644 --- a/src/EventsProcessor/Startup.cs +++ b/src/EventsProcessor/Startup.cs @@ -1,8 +1,11 @@ using System.Globalization; +using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.SharedWeb.Utilities; using Microsoft.IdentityModel.Logging; +using TableStorageRepos = Bit.Core.Repositories.TableStorage; namespace Bit.EventsProcessor; @@ -24,9 +27,37 @@ public class Startup services.AddOptions(); // Settings - services.AddGlobalSettingsServices(Configuration, Environment); + var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment); // Hosted Services + + // Optional Azure Service Bus Listeners + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddKeyedSingleton("persistent"); + services.AddSingleton(provider => + new AzureServiceBusEventListenerService( + provider.GetRequiredService(), + provider.GetRequiredService>(), + globalSettings, + globalSettings.EventLogging.AzureServiceBus.EventRepositorySubscriptionName)); + + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.WebhookUrl)) + { + services.AddSingleton(); + services.AddHttpClient(WebhookEventHandler.HttpClientName); + + services.AddSingleton(provider => + new AzureServiceBusEventListenerService( + provider.GetRequiredService(), + provider.GetRequiredService>(), + globalSettings, + globalSettings.EventLogging.AzureServiceBus.WebhookSubscriptionName)); + } + } services.AddHostedService(); } diff --git a/src/Identity/Billing/Controller/AccountsController.cs b/src/Identity/Billing/Controller/AccountsController.cs index f06fc7bf2c..96ec1280cd 100644 --- a/src/Identity/Billing/Controller/AccountsController.cs +++ b/src/Identity/Billing/Controller/AccountsController.cs @@ -1,5 +1,4 @@ -using Bit.Core; -using Bit.Core.Billing.Models.Api.Requests.Accounts; +using Bit.Core.Billing.Models.Api.Requests.Accounts; using Bit.Core.Billing.TrialInitiation.Registration; using Bit.Core.Context; using Bit.Core.Tools.Enums; @@ -18,8 +17,8 @@ public class AccountsController( ISendTrialInitiationEmailForRegistrationCommand sendTrialInitiationEmailForRegistrationCommand, IReferenceEventService referenceEventService) : Microsoft.AspNetCore.Mvc.Controller { - [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("trial/send-verification-email")] + [SelfHosted(NotSelfHostedOnly = true)] public async Task PostTrialInitiationSendVerificationEmailAsync([FromBody] TrialSendVerificationEmailRequestModel model) { var token = await sendTrialInitiationEmailForRegistrationCommand.Handle( diff --git a/src/Identity/Controllers/AccountsController.cs b/src/Identity/Controllers/AccountsController.cs index 40c926bda0..c840a7ddc5 100644 --- a/src/Identity/Controllers/AccountsController.cs +++ b/src/Identity/Controllers/AccountsController.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Text; using Bit.Core; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Api.Request.Accounts; @@ -15,6 +16,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Settings; using Bit.Core.Tokens; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Business; @@ -44,6 +46,41 @@ public class AccountsController : Controller private readonly IFeatureService _featureService; private readonly IDataProtectorTokenFactory _registrationEmailVerificationTokenDataFactory; + private readonly byte[] _defaultKdfHmacKey = null; + private static readonly List _defaultKdfResults = + [ + // The first result (index 0) should always return the "normal" default. + new() + { + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, + }, + // We want more weight for this default, so add it again + new() + { + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, + }, + // Add some other possible defaults... + new() + { + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = 100_000, + }, + new() + { + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = 5_000, + }, + new() + { + Kdf = KdfType.Argon2id, + KdfIterations = AuthConstants.ARGON2_ITERATIONS.Default, + KdfMemory = AuthConstants.ARGON2_MEMORY.Default, + KdfParallelism = AuthConstants.ARGON2_PARALLELISM.Default, + } + ]; + public AccountsController( ICurrentContext currentContext, ILogger logger, @@ -55,7 +92,8 @@ public class AccountsController : Controller ISendVerificationEmailForRegistrationCommand sendVerificationEmailForRegistrationCommand, IReferenceEventService referenceEventService, IFeatureService featureService, - IDataProtectorTokenFactory registrationEmailVerificationTokenDataFactory + IDataProtectorTokenFactory registrationEmailVerificationTokenDataFactory, + GlobalSettings globalSettings ) { _currentContext = currentContext; @@ -69,6 +107,11 @@ public class AccountsController : Controller _referenceEventService = referenceEventService; _featureService = featureService; _registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory; + + if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey)) + { + _defaultKdfHmacKey = Encoding.UTF8.GetBytes(globalSettings.KdfDefaultHashKey); + } } [HttpPost("register")] @@ -82,7 +125,6 @@ public class AccountsController : Controller return await ProcessRegistrationResult(identityResult, user, delaysEnabled: true); } - [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("register/send-verification-email")] public async Task PostRegisterSendVerificationEmail([FromBody] RegisterSendVerificationEmailRequestModel model) { @@ -106,7 +148,6 @@ public class AccountsController : Controller return NoContent(); } - [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("register/verification-email-clicked")] public async Task PostRegisterVerificationEmailClicked([FromBody] RegisterVerificationEmailClickedRequestModel model) { @@ -139,7 +180,6 @@ public class AccountsController : Controller } - [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("register/finish")] public async Task PostRegisterFinish([FromBody] RegisterFinishRequestModel model) { @@ -217,11 +257,7 @@ public class AccountsController : Controller var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email); if (kdfInformation == null) { - kdfInformation = new UserKdfInformation - { - Kdf = KdfType.PBKDF2_SHA256, - KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, - }; + kdfInformation = GetDefaultKdf(model.Email); } return new PreloginResponseModel(kdfInformation); } @@ -240,4 +276,26 @@ public class AccountsController : Controller Token = token }; } + + private UserKdfInformation GetDefaultKdf(string email) + { + if (_defaultKdfHmacKey == null) + { + return _defaultKdfResults[0]; + } + else + { + // Compute the HMAC hash of the email + var hmacMessage = Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant()); + using var hmac = new System.Security.Cryptography.HMACSHA256(_defaultKdfHmacKey); + var hmacHash = hmac.ComputeHash(hmacMessage); + // Convert the hash to a number + var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant(); + var hashFirst8Bytes = hashHex.Substring(0, 16); + var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber); + // Find the default KDF value for this hash number + var hashIndex = (int)(Math.Abs(hashNumber) % _defaultKdfResults.Count); + return _defaultKdfResults[hashIndex]; + } + } } diff --git a/src/Identity/Identity.csproj b/src/Identity/Identity.csproj index cb506d86e9..e9e188b53f 100644 --- a/src/Identity/Identity.csproj +++ b/src/Identity/Identity.csproj @@ -3,6 +3,8 @@ bitwarden-Identity false + + $(WarningsNotAsErrors);CS0162 diff --git a/src/Identity/IdentityServer/ApiResources.cs b/src/Identity/IdentityServer/ApiResources.cs index a0712aafe7..f969d67908 100644 --- a/src/Identity/IdentityServer/ApiResources.cs +++ b/src/Identity/IdentityServer/ApiResources.cs @@ -18,6 +18,7 @@ public class ApiResources Claims.SecurityStamp, Claims.Premium, Claims.Device, + Claims.DeviceType, Claims.OrganizationOwner, Claims.OrganizationAdmin, Claims.OrganizationUser, diff --git a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs index 78c00f86d5..88691fa8f7 100644 --- a/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs @@ -77,7 +77,7 @@ public abstract class BaseRequestValidator where T : class protected async Task ValidateAsync(T context, ValidatedTokenRequest request, CustomValidatorRequestContext validatorContext) { - // 1. we need to check if the user is a bot and if their master password hash is correct + // 1. We need to check if the user is a bot and if their master password hash is correct. var isBot = validatorContext.CaptchaResponse?.IsBot ?? false; var valid = await ValidateContextAsync(context, validatorContext); var user = validatorContext.User; @@ -99,7 +99,7 @@ public abstract class BaseRequestValidator where T : class return; } - // 2. Does this user belong to an organization that requires SSO + // 2. Decide if this user belongs to an organization that requires SSO. validatorContext.SsoRequired = await RequireSsoLoginAsync(user, request.GrantType); if (validatorContext.SsoRequired) { @@ -111,17 +111,22 @@ public abstract class BaseRequestValidator where T : class return; } - // 3. Check if 2FA is required - (validatorContext.TwoFactorRequired, var twoFactorOrganization) = await _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(user, request); - // This flag is used to determine if the user wants a rememberMe token sent when authentication is successful + // 3. Check if 2FA is required. + (validatorContext.TwoFactorRequired, var twoFactorOrganization) = + await _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(user, request); + + // This flag is used to determine if the user wants a rememberMe token sent when + // authentication is successful. var returnRememberMeToken = false; + if (validatorContext.TwoFactorRequired) { - var twoFactorToken = request.Raw["TwoFactorToken"]?.ToString(); - var twoFactorProvider = request.Raw["TwoFactorProvider"]?.ToString(); + var twoFactorToken = request.Raw["TwoFactorToken"]; + var twoFactorProvider = request.Raw["TwoFactorProvider"]; var validTwoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) && !string.IsNullOrWhiteSpace(twoFactorProvider); - // response for 2FA required and not provided state + + // 3a. Response for 2FA required and not provided state. if (!validTwoFactorRequest || !Enum.TryParse(twoFactorProvider, out TwoFactorProviderType twoFactorProviderType)) { @@ -133,26 +138,27 @@ public abstract class BaseRequestValidator where T : class return; } - // Include Master Password Policy in 2FA response - resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicy(user)); + // Include Master Password Policy in 2FA response. + resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user)); SetTwoFactorResult(context, resultDict); return; } - var twoFactorTokenValid = await _twoFactorAuthenticationValidator - .VerifyTwoFactor(user, twoFactorOrganization, twoFactorProviderType, twoFactorToken); + var twoFactorTokenValid = + await _twoFactorAuthenticationValidator + .VerifyTwoFactorAsync(user, twoFactorOrganization, twoFactorProviderType, twoFactorToken); - // response for 2FA required but request is not valid or remember token expired state + // 3b. Response for 2FA required but request is not valid or remember token expired state. if (!twoFactorTokenValid) { - // The remember me token has expired + // The remember me token has expired. if (twoFactorProviderType == TwoFactorProviderType.Remember) { var resultDict = await _twoFactorAuthenticationValidator .BuildTwoFactorResultAsync(user, twoFactorOrganization); // Include Master Password Policy in 2FA response - resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicy(user)); + resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user)); SetTwoFactorResult(context, resultDict); } else @@ -163,17 +169,19 @@ public abstract class BaseRequestValidator where T : class return; } - // When the two factor authentication is successful, we can check if the user wants a rememberMe token - var twoFactorRemember = request.Raw["TwoFactorRemember"]?.ToString() == "1"; - if (twoFactorRemember // Check if the user wants a rememberMe token - && twoFactorTokenValid // Make sure two factor authentication was successful - && twoFactorProviderType != TwoFactorProviderType.Remember) // if the two factor auth was rememberMe do not send another token + // 3c. When the 2FA authentication is successful, we can check if the user wants a + // rememberMe token. + var twoFactorRemember = request.Raw["TwoFactorRemember"] == "1"; + // Check if the user wants a rememberMe token. + if (twoFactorRemember + // if the 2FA auth was rememberMe do not send another token. + && twoFactorProviderType != TwoFactorProviderType.Remember) { returnRememberMeToken = true; } } - // 4. Check if the user is logging in from a new device + // 4. Check if the user is logging in from a new device. var deviceValid = await _deviceValidator.ValidateRequestDeviceAsync(request, validatorContext); if (!deviceValid) { @@ -182,14 +190,11 @@ public abstract class BaseRequestValidator where T : class return; } - // 5. Force legacy users to the web for migration - if (FeatureService.IsEnabled(FeatureFlagKeys.BlockLegacyUsers)) + // 5. Force legacy users to the web for migration. + if (UserService.IsLegacyUser(user) && request.ClientId != "web") { - if (UserService.IsLegacyUser(user) && request.ClientId != "web") - { - await FailAuthForLegacyUserAsync(user, context); - return; - } + await FailAuthForLegacyUserAsync(user, context); + return; } await BuildSuccessResultAsync(user, context, validatorContext.Device, returnRememberMeToken); @@ -213,6 +218,7 @@ public abstract class BaseRequestValidator where T : class if (device != null) { claims.Add(new Claim(Claims.Device, device.Identifier)); + claims.Add(new Claim(Claims.DeviceType, device.Type.ToString())); } var customResponse = new Dictionary(); @@ -226,7 +232,7 @@ public abstract class BaseRequestValidator where T : class customResponse.Add("Key", user.Key); } - customResponse.Add("MasterPasswordPolicy", await GetMasterPasswordPolicy(user)); + customResponse.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user)); customResponse.Add("ForcePasswordReset", user.ForcePasswordReset); customResponse.Add("ResetMasterPassword", string.IsNullOrWhiteSpace(user.MasterPassword)); customResponse.Add("Kdf", (byte)user.Kdf); @@ -405,7 +411,7 @@ public abstract class BaseRequestValidator where T : class return unknownDevice && failedLoginCeiling > 0 && failedLoginCount == failedLoginCeiling; } - private async Task GetMasterPasswordPolicy(User user) + private async Task GetMasterPasswordPolicyAsync(User user) { // Check current context/cache to see if user is in any organizations, avoids extra DB call if not var orgs = (await CurrentContext.OrganizationMembershipAsync(_organizationUserRepository, user.Id)) diff --git a/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs b/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs index 1b148c5974..fee10e10ff 100644 --- a/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/DeviceValidator.cs @@ -85,28 +85,17 @@ public class DeviceValidator( } } - // At this point we have established either new device verification is not required or the NewDeviceOtp is valid + // At this point we have established either new device verification is not required or the NewDeviceOtp is valid, + // so we save the device to the database and proceed with authentication requestDevice.UserId = context.User.Id; await _deviceService.SaveAsync(requestDevice); context.Device = requestDevice; - // backwards compatibility -- If NewDeviceVerification not enabled send the new login emails - // PM-13340: removal Task; remove entire if block emails should no longer be sent - if (!_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)) + if (!_globalSettings.DisableEmailNewDevice) { - // This ensures the user doesn't receive a "new device" email on the first login - var now = DateTime.UtcNow; - if (now - context.User.CreationDate > TimeSpan.FromMinutes(10)) - { - var deviceType = requestDevice.Type.GetType().GetMember(requestDevice.Type.ToString()) - .FirstOrDefault()?.GetCustomAttribute()?.GetName(); - if (!_globalSettings.DisableEmailNewDevice) - { - await _mailService.SendNewDeviceLoggedInEmail(context.User.Email, deviceType, now, - _currentContext.IpAddress); - } - } + await SendNewDeviceLoginEmail(context.User, requestDevice); } + return true; } @@ -174,6 +163,19 @@ public class DeviceValidator( return DeviceValidationResultType.NewDeviceVerificationRequired; } + private async Task SendNewDeviceLoginEmail(User user, Device requestDevice) + { + // Ensure that the user doesn't receive a "new device" email on the first login + var now = DateTime.UtcNow; + if (now - user.CreationDate > TimeSpan.FromMinutes(10)) + { + var deviceType = requestDevice.Type.GetType().GetMember(requestDevice.Type.ToString()) + .FirstOrDefault()?.GetCustomAttribute()?.GetName(); + await _mailService.SendNewDeviceLoggedInEmail(user.Email, deviceType, now, + _currentContext.IpAddress); + } + } + public async Task GetKnownDeviceAsync(User user, Device device) { if (user == null || device == null) diff --git a/src/Identity/IdentityServer/RequestValidators/TwoFactorAuthenticationValidator.cs b/src/Identity/IdentityServer/RequestValidators/TwoFactorAuthenticationValidator.cs index e2c6406c89..856846cdd6 100644 --- a/src/Identity/IdentityServer/RequestValidators/TwoFactorAuthenticationValidator.cs +++ b/src/Identity/IdentityServer/RequestValidators/TwoFactorAuthenticationValidator.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Identity.TokenProviders; @@ -44,7 +45,7 @@ public interface ITwoFactorAuthenticationValidator /// Two Factor Provider to use to verify the token /// secret passed from the user and consumed by the two-factor provider's verify method /// boolean - Task VerifyTwoFactor(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token); + Task VerifyTwoFactorAsync(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token); } public class TwoFactorAuthenticationValidator( @@ -139,7 +140,7 @@ public class TwoFactorAuthenticationValidator( return twoFactorResultDict; } - public async Task VerifyTwoFactor( + public async Task VerifyTwoFactorAsync( User user, Organization organization, TwoFactorProviderType type, @@ -154,24 +155,39 @@ public class TwoFactorAuthenticationValidator( return false; } - switch (type) + if (_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin)) { - case TwoFactorProviderType.Authenticator: - case TwoFactorProviderType.Email: - case TwoFactorProviderType.Duo: - case TwoFactorProviderType.YubiKey: - case TwoFactorProviderType.WebAuthn: - case TwoFactorProviderType.Remember: - if (type != TwoFactorProviderType.Remember && - !await _userService.TwoFactorProviderIsEnabledAsync(type, user)) - { - return false; - } - return await _userManager.VerifyTwoFactorTokenAsync(user, - CoreHelpers.CustomProviderName(type), token); - default: - return false; + if (type is TwoFactorProviderType.RecoveryCode) + { + return await _userService.RecoverTwoFactorAsync(user, token); + } } + + // These cases we want to always return false, U2f is deprecated and OrganizationDuo + // uses a different flow than the other two factor providers, it follows the same + // structure of a UserTokenProvider but has it's logic ran outside the usual token + // provider flow. See IOrganizationDuoUniversalTokenProvider.cs + if (type is TwoFactorProviderType.U2f or TwoFactorProviderType.OrganizationDuo) + { + return false; + } + + // Now we are concerning the rest of the Two Factor Provider Types + + // The intent of this check is to make sure that the user is using a 2FA provider that + // is enabled and allowed by their premium status. The exception for Remember + // is because it is a "special" 2FA type that isn't ever explicitly + // enabled by a user, so we can't check the user's 2FA providers to see if they're + // enabled. We just have to check if the token is valid. + if (type != TwoFactorProviderType.Remember && + !await _userService.TwoFactorProviderIsEnabledAsync(type, user)) + { + return false; + } + + // Finally, verify the token based on the provider type. + return await _userManager.VerifyTwoFactorTokenAsync( + user, CoreHelpers.CustomProviderName(type), token); } private async Task>> GetEnabledTwoFactorProvidersAsync( diff --git a/src/Identity/Utilities/LoginApprovingDeviceTypes.cs b/src/Identity/Utilities/LoginApprovingDeviceTypes.cs index 46b4606ccf..b8b11a4d19 100644 --- a/src/Identity/Utilities/LoginApprovingDeviceTypes.cs +++ b/src/Identity/Utilities/LoginApprovingDeviceTypes.cs @@ -12,6 +12,7 @@ public static class LoginApprovingDeviceTypes var deviceTypes = new List(); deviceTypes.AddRange(DeviceTypes.DesktopTypes); deviceTypes.AddRange(DeviceTypes.MobileTypes); + deviceTypes.AddRange(DeviceTypes.BrowserTypes); _deviceTypes = deviceTypes.AsReadOnly(); } diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs index d8245ce719..2b4db3940c 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs @@ -109,9 +109,13 @@ public class GroupRepository : Repository, IGroupRepository } } - public async Task> GetManyUserIdsByIdAsync(Guid id) + public async Task> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false) { - using (var connection = new SqlConnection(ConnectionString)) + var connectionString = useReadOnlyReplica + ? ReadOnlyConnectionString + : ConnectionString; + + using (var connection = new SqlConnection(connectionString)) { var results = await connection.QueryAsync( $"[{Schema}].[GroupUser_ReadOrganizationUserIdsByGroupId]", @@ -186,6 +190,17 @@ public class GroupRepository : Repository, IGroupRepository } } + public async Task AddGroupUsersByIdAsync(Guid groupId, IEnumerable organizationUserIds) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + "[dbo].[GroupUser_AddUsers]", + new { GroupId = groupId, OrganizationUserIds = organizationUserIds.ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); + } + } + public async Task DeleteManyAsync(IEnumerable groupIds) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs index 20fdf83155..f624f7da28 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationRepository.cs @@ -1,5 +1,6 @@ using System.Data; using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Auth.Entities; using Bit.Core.Entities; using Bit.Core.Models.Data.Organizations; @@ -180,4 +181,19 @@ public class OrganizationRepository : Repository, IOrganizat return result.ToList(); } } + + public async Task> GetAddableToProviderByUserIdAsync( + Guid userId, + ProviderType providerType) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadAddableToProviderByUserId]", + new { UserId = userId, ProviderType = providerType }, + commandType: CommandType.StoredProcedure); + + return result.ToList(); + } + } } diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs index 42f79852f3..9b77fb216e 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -567,4 +567,17 @@ public class OrganizationUserRepository : Repository, IO new { OrganizationUserIds = JsonSerializer.Serialize(organizationUserIds), Status = OrganizationUserStatusType.Revoked }, commandType: CommandType.StoredProcedure); } + + public async Task> GetManyDetailsByRoleAsync(Guid organizationId, OrganizationUserType role) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[OrganizationUser_ReadManyDetailsByRole]", + new { OrganizationId = organizationId, Role = role }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } } diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/PolicyRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/PolicyRepository.cs index 196f3e3733..071ff3153a 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/PolicyRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/PolicyRepository.cs @@ -1,6 +1,7 @@ using System.Data; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Settings; using Bit.Infrastructure.Dapper.Repositories; @@ -59,4 +60,17 @@ public class PolicyRepository : Repository, IPolicyRepository return results.ToList(); } } + + public async Task> GetPolicyDetailsByUserId(Guid userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[PolicyDetails_ReadByUserId]", + new { UserId = userId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } } diff --git a/src/Infrastructure.Dapper/DapperHelpers.cs b/src/Infrastructure.Dapper/DapperHelpers.cs index c256612447..9a67af3a93 100644 --- a/src/Infrastructure.Dapper/DapperHelpers.cs +++ b/src/Infrastructure.Dapper/DapperHelpers.cs @@ -81,7 +81,7 @@ public class DataTableBuilder return true; } - // Value type properties will implicitly box into the object so + // Value type properties will implicitly box into the object so // we need to look past the Convert expression // i => (System.Object?)i.Id if ( diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 93814a6d7f..26abf5632c 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -51,7 +51,6 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure.Dapper/Infrastructure.Dapper.csproj b/src/Infrastructure.Dapper/Infrastructure.Dapper.csproj index 046009ef73..19512670ce 100644 --- a/src/Infrastructure.Dapper/Infrastructure.Dapper.csproj +++ b/src/Infrastructure.Dapper/Infrastructure.Dapper.csproj @@ -1,5 +1,10 @@ + + + $(WarningsNotAsErrors);CS8618;CS4014 + + diff --git a/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs b/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs deleted file mode 100644 index be60017262..0000000000 --- a/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Data; -using Bit.Core.Entities; -using Bit.Core.Repositories; -using Bit.Core.Settings; -using Dapper; -using Microsoft.Data.SqlClient; - -#nullable enable - -namespace Bit.Infrastructure.Dapper.Repositories; - -public class TaxRateRepository : Repository, ITaxRateRepository -{ - public TaxRateRepository(GlobalSettings globalSettings) - : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) - { } - - public TaxRateRepository(string connectionString, string readOnlyConnectionString) - : base(connectionString, readOnlyConnectionString) - { } - - public async Task> SearchAsync(int skip, int count) - { - using (var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - $"[{Schema}].[TaxRate_Search]", - new { Skip = skip, Count = count }, - commandType: CommandType.StoredProcedure); - - return results.ToList(); - } - } - - public async Task> GetAllActiveAsync() - { - using (var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - $"[{Schema}].[TaxRate_ReadAllActive]", - commandType: CommandType.StoredProcedure); - - return results.ToList(); - } - } - - public async Task ArchiveAsync(TaxRate model) - { - using (var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.ExecuteAsync( - $"[{Schema}].[TaxRate_Archive]", - new { Id = model.Id }, - commandType: CommandType.StoredProcedure); - } - } - - public async Task> GetByLocationAsync(TaxRate model) - { - using (var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - $"[{Schema}].[TaxRate_ReadByLocation]", - new { Country = model.Country, PostalCode = model.PostalCode }, - commandType: CommandType.StoredProcedure); - - return results.ToList(); - } - } -} diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index 098e8299e4..b8304fbbb0 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -98,7 +98,7 @@ public class CipherRepository : Repository, ICipherRepository return results .GroupBy(c => c.Id) - .Select(g => g.OrderByDescending(og => og.Edit).First()) + .Select(g => g.OrderByDescending(og => og.Edit).ThenByDescending(og => og.ViewPassword).First()) .ToList(); } } diff --git a/src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs index 35dace9a9e..f7a5f3b878 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/SecurityTaskRepository.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Text.Json; using Bit.Core.Settings; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Enums; @@ -46,4 +47,29 @@ public class SecurityTaskRepository : Repository, ISecurityT return results.ToList(); } + + /// + public async Task> CreateManyAsync(IEnumerable tasks) + { + var tasksList = tasks?.ToList(); + if (tasksList is null || tasksList.Count == 0) + { + return Array.Empty(); + } + + foreach (var task in tasksList) + { + task.SetNewId(); + } + + var tasksJson = JsonSerializer.Serialize(tasksList); + + await using var connection = new SqlConnection(ConnectionString); + await connection.ExecuteAsync( + $"[{Schema}].[{Table}_CreateMany]", + new { SecurityTasksJson = tasksJson }, + commandType: CommandType.StoredProcedure); + + return tasksList; + } } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Models/Organization.cs b/src/Infrastructure.EntityFramework/AdminConsole/Models/Organization.cs index 39968142bb..d7f83d829d 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Models/Organization.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Models/Organization.cs @@ -9,11 +9,6 @@ namespace Bit.Infrastructure.EntityFramework.AdminConsole.Models; public class Organization : Core.AdminConsole.Entities.Organization { - // Shadow property. To be removed by - // https://bitwarden.atlassian.net/browse/PM-10863. - // This was replaced with `LimitCollectionCreation` and - // `LimitCollectionDeletion`. - public bool LimitCollectionCreationDeletion { get; set; } public virtual ICollection Ciphers { get; set; } public virtual ICollection OrganizationUsers { get; set; } public virtual ICollection Groups { get; set; } @@ -43,7 +38,6 @@ public class OrganizationMapperProfile : Profile .ForMember(org => org.ApiKeys, opt => opt.Ignore()) .ForMember(org => org.Connections, opt => opt.Ignore()) .ForMember(org => org.Domains, opt => opt.Ignore()) - .ForMember(org => org.LimitCollectionCreationDeletion, opt => opt.Ignore()) .ReverseMap(); CreateProjection() diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs index 0e91bd42ef..305a715d4c 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs @@ -163,8 +163,10 @@ public class GroupRepository : Repository> GetManyUserIdsByIdAsync(Guid id) + public async Task> GetManyUserIdsByIdAsync(Guid id, bool useReadOnlyReplica = false) { + // EF is only used for self-hosted so read-only replica parameter is ignored + using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); @@ -255,6 +257,29 @@ public class GroupRepository : Repository organizationUserIds) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var orgId = (await dbContext.Groups.FindAsync(groupId)).OrganizationId; + var insert = from ou in dbContext.OrganizationUsers + where organizationUserIds.Contains(ou.Id) && + ou.OrganizationId == orgId && + !dbContext.GroupUsers.Any(gu => gu.GroupId == groupId && ou.Id == gu.OrganizationUserId) + select new GroupUser + { + GroupId = groupId, + OrganizationUserId = ou.Id, + }; + await dbContext.AddRangeAsync(insert); + + await dbContext.SaveChangesAsync(); + await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(orgId); + await dbContext.SaveChangesAsync(); + } + } + public async Task DeleteManyAsync(IEnumerable groupIds) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs index fb3766c6c7..ea4e1334c6 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs @@ -1,9 +1,12 @@ using AutoMapper; using AutoMapper.QueryableExtensions; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Billing.Constants; using Bit.Core.Billing.Enums; using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations; using Bit.Core.Repositories; +using LinqToDB.Tools; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -101,6 +104,7 @@ public class OrganizationRepository : Repository + { + PlanType.Free, + PlanType.Custom, + PlanType.FamiliesAnnually2019, + PlanType.FamiliesAnnually + }; + var query = from o in dbContext.Organizations - where - ((o.PlanType >= PlanType.TeamsMonthly2019 && o.PlanType <= PlanType.EnterpriseAnnually2019) || - (o.PlanType >= PlanType.TeamsMonthly2020 && o.PlanType <= PlanType.EnterpriseAnnually)) && - !dbContext.ProviderOrganizations.Any(po => po.OrganizationId == o.Id) && - (string.IsNullOrWhiteSpace(name) || EF.Functions.Like(o.Name, $"%{name}%")) + where o.PlanType.NotIn(disallowedPlanTypes) && + !dbContext.ProviderOrganizations.Any(po => po.OrganizationId == o.Id) && + (string.IsNullOrWhiteSpace(name) || EF.Functions.Like(o.Name, $"%{name}%")) select o; if (string.IsNullOrWhiteSpace(ownerEmail)) @@ -151,7 +161,7 @@ public class OrganizationRepository : Repository o.CreationDate).Skip(skip).Take(take).ToArrayAsync(); + return await query.OrderByDescending(o => o.CreationDate).ThenByDescending(o => o.Id).Skip(skip).Take(take).ToArrayAsync(); } public async Task UpdateStorageAsync(Guid id) @@ -297,6 +307,41 @@ public class OrganizationRepository : Repository> GetAddableToProviderByUserIdAsync( + Guid userId, + ProviderType providerType) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + + var planTypes = providerType switch + { + ProviderType.Msp => PlanConstants.EnterprisePlanTypes.Concat(PlanConstants.TeamsPlanTypes), + ProviderType.MultiOrganizationEnterprise => PlanConstants.EnterprisePlanTypes, + _ => [] + }; + + var query = + from organizationUser in dbContext.OrganizationUsers + join organization in dbContext.Organizations on organizationUser.OrganizationId equals organization.Id + where + organizationUser.UserId == userId && + organizationUser.Type == OrganizationUserType.Owner && + organizationUser.Status == OrganizationUserStatusType.Confirmed && + organization.Enabled && + organization.GatewayCustomerId != null && + organization.GatewaySubscriptionId != null && + organization.Seats > 0 && + organization.Status == OrganizationStatusType.Created && + !organization.UseSecretsManager && + organization.PlanType.In(planTypes) + select organization; + + return await query.ToArrayAsync(); + } + } + public Task EnableCollectionEnhancements(Guid organizationId) { throw new NotImplementedException("Collection enhancements migration is not yet supported for Entity Framework."); diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs index 007ff1a7ff..ef6460df0e 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -733,4 +733,25 @@ public class OrganizationUserRepository : Repository> GetManyDetailsByRoleAsync(Guid organizationId, OrganizationUserType role) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from ou in dbContext.OrganizationUsers + join u in dbContext.Users + on ou.UserId equals u.Id + where ou.OrganizationId == organizationId && + ou.Type == role && + ou.Status == OrganizationUserStatusType.Confirmed + select new OrganizationUserUserDetails + { + Id = ou.Id, + Email = ou.Email ?? u.Email, + Permissions = ou.Permissions + }; + return await query.ToListAsync(); + } + } } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs index 3eb4ac934b..0564681341 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/PolicyRepository.cs @@ -1,6 +1,8 @@ using AutoMapper; using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Enums; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Repositories; @@ -50,4 +52,43 @@ public class PolicyRepository : Repository>(results); } } + + public async Task> GetPolicyDetailsByUserId(Guid userId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + var providerOrganizations = from pu in dbContext.ProviderUsers + where pu.UserId == userId + join po in dbContext.ProviderOrganizations + on pu.ProviderId equals po.ProviderId + select po; + + var query = from p in dbContext.Policies + join ou in dbContext.OrganizationUsers + on p.OrganizationId equals ou.OrganizationId + join o in dbContext.Organizations + on p.OrganizationId equals o.Id + where + p.Enabled && + o.Enabled && + o.UsePolicies && + ( + (ou.Status != OrganizationUserStatusType.Invited && ou.UserId == userId) || + // Invited orgUsers do not have a UserId associated with them, so we have to match up their email + (ou.Status == OrganizationUserStatusType.Invited && ou.Email == dbContext.Users.Find(userId).Email) + ) + select new PolicyDetails + { + OrganizationUserId = ou.Id, + OrganizationId = p.OrganizationId, + PolicyType = p.Type, + PolicyData = p.Data, + OrganizationUserType = ou.Type, + OrganizationUserStatus = ou.Status, + OrganizationUserPermissionsData = ou.Permissions, + IsProvider = providerOrganizations.Any(po => po.OrganizationId == p.OrganizationId) + }; + return await query.ToListAsync(); + } } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs index c93903ab0f..73aea44332 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -68,6 +68,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery ou.Status >= 0), Seats = x.o.Seats, Plan = x.o.Plan, + PlanType = x.o.PlanType, Status = x.o.Status }); } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs index 3f3d3d389e..9483ccbc92 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/ProviderUserOrganizationDetailsViewQuery.cs @@ -46,6 +46,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj index 972d0eac0e..06ad2dc19a 100644 --- a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj +++ b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj @@ -1,5 +1,10 @@ + + + $(WarningsNotAsErrors);CS0108;CS8632 + + diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationEntityTypeConfiguration.cs index c8bb2fd0ad..fc2bcd81c9 100644 --- a/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationEntityTypeConfiguration.cs +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationEntityTypeConfiguration.cs @@ -30,6 +30,10 @@ public class NotificationEntityTypeConfiguration : IEntityTypeConfiguration n.UserId) .IsClustered(false); + builder + .HasIndex(n => n.TaskId) + .IsClustered(false); + builder.ToTable(nameof(Notification)); } } diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Models/Notification.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Models/Notification.cs index a13e99a276..ec8db45c5a 100644 --- a/src/Infrastructure.EntityFramework/NotificationCenter/Models/Notification.cs +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Models/Notification.cs @@ -1,6 +1,7 @@ using AutoMapper; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.Models; +using Bit.Infrastructure.EntityFramework.Vault.Models; namespace Bit.Infrastructure.EntityFramework.NotificationCenter.Models; @@ -8,6 +9,7 @@ public class Notification : Core.NotificationCenter.Entities.Notification { public virtual User User { get; set; } public virtual Organization Organization { get; set; } + public virtual SecurityTask Task { get; set; } } public class NotificationMapperProfile : Profile diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index e339c13351..50d791b81b 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -46,27 +46,17 @@ public class OrganizationDomainRepository : Repository x.VerifiedDate == null - && x.JobRunCount != 3 - && x.NextRunDate.Year == date.Year - && x.NextRunDate.Month == date.Month - && x.NextRunDate.Day == date.Day - && x.NextRunDate.Hour == date.Hour) - .AsNoTracking() + var start36HoursWindow = date.AddHours(-36); + var end36HoursWindow = date; + + var pastDomains = await dbContext.OrganizationDomains + .Where(x => x.NextRunDate >= start36HoursWindow + && x.NextRunDate <= end36HoursWindow + && x.VerifiedDate == null + && x.JobRunCount != 3) .ToListAsync(); - //Get records that have ignored/failed by the background service - var pastDomains = dbContext.OrganizationDomains - .AsEnumerable() - .Where(x => (date - x.NextRunDate).TotalHours > 36 - && x.VerifiedDate == null - && x.JobRunCount != 3) - .ToList(); - - var results = domains.Union(pastDomains); - - return Mapper.Map>(results); + return Mapper.Map>(pastDomains); } public async Task GetOrganizationDomainSsoDetailsAsync(string email) diff --git a/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs b/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs deleted file mode 100644 index 38fcaaa1aa..0000000000 --- a/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs +++ /dev/null @@ -1,68 +0,0 @@ -using AutoMapper; -using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; - -#nullable enable - -namespace Bit.Infrastructure.EntityFramework.Repositories; - -public class TaxRateRepository : Repository, ITaxRateRepository -{ - public TaxRateRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) - : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.TaxRates) - { } - - public async Task ArchiveAsync(Core.Entities.TaxRate model) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - await dbContext.TaxRates - .Where(tr => tr.Id == model.Id) - .ExecuteUpdateAsync(property => property.SetProperty(tr => tr.Active, false)); - } - } - - public async Task> GetAllActiveAsync() - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var results = await dbContext.TaxRates - .Where(t => t.Active) - .ToListAsync(); - return Mapper.Map>(results); - } - } - - public async Task> GetByLocationAsync(Core.Entities.TaxRate taxRate) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var results = await dbContext.TaxRates - .Where(t => t.Active && - t.Country == taxRate.Country && - t.PostalCode == taxRate.PostalCode) - .ToListAsync(); - return Mapper.Map>(results); - } - } - - public async Task> SearchAsync(int skip, int count) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var results = await dbContext.TaxRates - .Skip(skip) - .Take(count) - .Where(t => t.Active) - .OrderBy(t => t.Country).ThenByDescending(t => t.PostalCode) - .ToListAsync(); - return Mapper.Map>(results); - } - } -} diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs index 5adfdc4c76..a3ba2632fe 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/SecurityTaskRepository.cs @@ -52,4 +52,28 @@ public class SecurityTaskRepository : Repository st.CreationDate).ToListAsync(); } + + /// + public async Task> CreateManyAsync( + IEnumerable tasks) + { + var tasksList = tasks?.ToList(); + if (tasksList is null || tasksList.Count == 0) + { + return Array.Empty(); + } + + foreach (var task in tasksList) + { + task.SetNewId(); + } + + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entities = Mapper.Map>(tasksList); + await dbContext.AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + + return tasksList; + } } diff --git a/src/Notifications/HubHelpers.cs b/src/Notifications/HubHelpers.cs index 6f49822dc9..af571e48c4 100644 --- a/src/Notifications/HubHelpers.cs +++ b/src/Notifications/HubHelpers.cs @@ -10,6 +10,8 @@ public static class HubHelpers private static JsonSerializerOptions _deserializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + private static readonly string _receiveMessageMethod = "ReceiveMessage"; + public static async Task SendNotificationToHubAsync( string notificationJson, IHubContext hubContext, @@ -18,7 +20,8 @@ public static class HubHelpers CancellationToken cancellationToken = default(CancellationToken) ) { - var notification = JsonSerializer.Deserialize>(notificationJson, _deserializerOptions); + var notification = + JsonSerializer.Deserialize>(notificationJson, _deserializerOptions); logger.LogInformation("Sending notification: {NotificationType}", notification.Type); switch (notification.Type) { @@ -32,14 +35,15 @@ public static class HubHelpers if (cipherNotification.Payload.UserId.HasValue) { await hubContext.Clients.User(cipherNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", cipherNotification, cancellationToken); + .SendAsync(_receiveMessageMethod, cipherNotification, cancellationToken); } else if (cipherNotification.Payload.OrganizationId.HasValue) { - await hubContext.Clients.Group( - $"Organization_{cipherNotification.Payload.OrganizationId}") - .SendAsync("ReceiveMessage", cipherNotification, cancellationToken); + await hubContext.Clients + .Group(NotificationsHub.GetOrganizationGroup(cipherNotification.Payload.OrganizationId.Value)) + .SendAsync(_receiveMessageMethod, cipherNotification, cancellationToken); } + break; case PushType.SyncFolderUpdate: case PushType.SyncFolderCreate: @@ -48,7 +52,7 @@ public static class HubHelpers JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); await hubContext.Clients.User(folderNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", folderNotification, cancellationToken); + .SendAsync(_receiveMessageMethod, folderNotification, cancellationToken); break; case PushType.SyncCiphers: case PushType.SyncVault: @@ -60,30 +64,30 @@ public static class HubHelpers JsonSerializer.Deserialize>( notificationJson, _deserializerOptions); await hubContext.Clients.User(userNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", userNotification, cancellationToken); + .SendAsync(_receiveMessageMethod, userNotification, cancellationToken); break; case PushType.SyncSendCreate: case PushType.SyncSendUpdate: case PushType.SyncSendDelete: var sendNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await hubContext.Clients.User(sendNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", sendNotification, cancellationToken); + .SendAsync(_receiveMessageMethod, sendNotification, cancellationToken); break; case PushType.AuthRequestResponse: var authRequestResponseNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await anonymousHubContext.Clients.Group(authRequestResponseNotification.Payload.Id.ToString()) .SendAsync("AuthRequestResponseRecieved", authRequestResponseNotification, cancellationToken); break; case PushType.AuthRequest: var authRequestNotification = JsonSerializer.Deserialize>( - notificationJson, _deserializerOptions); + notificationJson, _deserializerOptions); await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString()) - .SendAsync("ReceiveMessage", authRequestNotification, cancellationToken); + .SendAsync(_receiveMessageMethod, authRequestNotification, cancellationToken); break; case PushType.SyncOrganizationStatusChanged: var orgStatusNotification = @@ -99,6 +103,33 @@ public static class HubHelpers await hubContext.Clients.Group($"Organization_{organizationCollectionSettingsChangedNotification.Payload.OrganizationId}") .SendAsync("ReceiveMessage", organizationCollectionSettingsChangedNotification, cancellationToken); break; + case PushType.SyncNotification: + case PushType.SyncNotificationStatus: + var syncNotification = + JsonSerializer.Deserialize>( + notificationJson, _deserializerOptions); + if (syncNotification.Payload.UserId.HasValue) + { + if (syncNotification.Payload.ClientType == ClientType.All) + { + await hubContext.Clients.User(syncNotification.Payload.UserId.ToString()) + .SendAsync(_receiveMessageMethod, syncNotification, cancellationToken); + } + else + { + await hubContext.Clients.Group(NotificationsHub.GetUserGroup( + syncNotification.Payload.UserId.Value, syncNotification.Payload.ClientType)) + .SendAsync(_receiveMessageMethod, syncNotification, cancellationToken); + } + } + else if (syncNotification.Payload.OrganizationId.HasValue) + { + await hubContext.Clients.Group(NotificationsHub.GetOrganizationGroup( + syncNotification.Payload.OrganizationId.Value, syncNotification.Payload.ClientType)) + .SendAsync(_receiveMessageMethod, syncNotification, cancellationToken); + } + + break; default: break; } diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj index 68ae96963e..4d19f7faf9 100644 --- a/src/Notifications/Notifications.csproj +++ b/src/Notifications/Notifications.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/src/Notifications/NotificationsHub.cs b/src/Notifications/NotificationsHub.cs index a86cf329c5..27cd19c0a0 100644 --- a/src/Notifications/NotificationsHub.cs +++ b/src/Notifications/NotificationsHub.cs @@ -1,5 +1,7 @@ using Bit.Core.Context; +using Bit.Core.Enums; using Bit.Core.Settings; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; namespace Bit.Notifications; @@ -20,13 +22,25 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub { var currentContext = new CurrentContext(null, null); await currentContext.BuildAsync(Context.User, _globalSettings); + + var clientType = DeviceTypes.ToClientType(currentContext.DeviceType); + if (clientType != ClientType.All && currentContext.UserId.HasValue) + { + await Groups.AddToGroupAsync(Context.ConnectionId, GetUserGroup(currentContext.UserId.Value, clientType)); + } + if (currentContext.Organizations != null) { foreach (var org in currentContext.Organizations) { - await Groups.AddToGroupAsync(Context.ConnectionId, $"Organization_{org.Id}"); + await Groups.AddToGroupAsync(Context.ConnectionId, GetOrganizationGroup(org.Id)); + if (clientType != ClientType.All) + { + await Groups.AddToGroupAsync(Context.ConnectionId, GetOrganizationGroup(org.Id, clientType)); + } } } + _connectionCounter.Increment(); await base.OnConnectedAsync(); } @@ -35,14 +49,39 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub { var currentContext = new CurrentContext(null, null); await currentContext.BuildAsync(Context.User, _globalSettings); + + var clientType = DeviceTypes.ToClientType(currentContext.DeviceType); + if (clientType != ClientType.All && currentContext.UserId.HasValue) + { + await Groups.RemoveFromGroupAsync(Context.ConnectionId, + GetUserGroup(currentContext.UserId.Value, clientType)); + } + if (currentContext.Organizations != null) { foreach (var org in currentContext.Organizations) { - await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"Organization_{org.Id}"); + await Groups.RemoveFromGroupAsync(Context.ConnectionId, GetOrganizationGroup(org.Id)); + if (clientType != ClientType.All) + { + await Groups.RemoveFromGroupAsync(Context.ConnectionId, GetOrganizationGroup(org.Id, clientType)); + } } } + _connectionCounter.Decrement(); await base.OnDisconnectedAsync(exception); } + + public static string GetUserGroup(Guid userId, ClientType clientType) + { + return $"UserClientType_{userId}_{clientType}"; + } + + public static string GetOrganizationGroup(Guid organizationId, ClientType? clientType = null) + { + return clientType is null or ClientType.All + ? $"Organization_{organizationId}" + : $"OrganizationClientType_{organizationId}_{clientType}"; + } } diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 63c114405f..5a1205c961 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using AspNetCoreRateLimit; +using Azure.Storage.Queues; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.Services; @@ -40,6 +41,7 @@ using Bit.Core.SecretsManager.Repositories.Noop; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Tokens; +using Bit.Core.Tools.ImportFeatures; using Bit.Core.Tools.ReportFeatures; using Bit.Core.Tools.Services; using Bit.Core.Utilities; @@ -128,6 +130,7 @@ public static class ServiceCollectionExtensions services.AddKeyManagementServices(); services.AddNotificationCenterServices(); services.AddPlatformServices(); + services.AddImportServices(); } public static void AddTokenizers(this IServiceCollection services) @@ -304,7 +307,10 @@ public static class ServiceCollectionExtensions services.AddKeyedSingleton("implementation"); if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) { - services.AddKeyedSingleton("implementation"); + services.AddKeyedSingleton("notifications", + (_, _) => new QueueClient(globalSettings.Notifications.ConnectionString, "notifications")); + services.AddKeyedSingleton( + "implementation"); } } @@ -319,11 +325,29 @@ public static class ServiceCollectionExtensions if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } else if (globalSettings.SelfHosted) { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } else { diff --git a/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql b/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql index 015d0f7c1f..f40e9149c0 100644 --- a/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql +++ b/src/Sql/Auth/dbo/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql @@ -10,18 +10,21 @@ BEGIN 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 + LEFT JOIN ( + SELECT + Id, + CreationDate, + RequestDeviceIdentifier, + Approved, + ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) as rn 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 Type IN (0, 1) -- AuthenticateAndUnlock and Unlock types only + AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired + AND UserId = @UserId -- Requests for this user only + ) AR -- This join will get the most recent request per device, regardless of approval status + ON D.Identifier = AR.RequestDeviceIdentifier AND AR.rn = 1 AND AR.Approved IS NULL -- Get only the most recent unapproved request per device WHERE - D.UserId = @UserId + D.UserId = @UserId -- Include only devices for this user AND D.Active = 1; -- Include only active devices END; + diff --git a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Create.sql b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Create.sql index 1e396a611b..7f67823055 100644 --- a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Create.sql +++ b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Create.sql @@ -8,7 +8,8 @@ CREATE PROCEDURE [dbo].[Notification_Create] @Title NVARCHAR(256), @Body NVARCHAR(MAX), @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7) + @RevisionDate DATETIME2(7), + @TaskId UNIQUEIDENTIFIER = NULL AS BEGIN SET NOCOUNT ON @@ -23,7 +24,8 @@ BEGIN [Title], [Body], [CreationDate], - [RevisionDate] + [RevisionDate], + [TaskId] ) VALUES ( @Id, @@ -35,6 +37,7 @@ BEGIN @Title, @Body, @CreationDate, - @RevisionDate + @RevisionDate, + @TaskId ) END diff --git a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Update.sql b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Update.sql index 17d656f5a9..4369c01f71 100644 --- a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Update.sql +++ b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_Update.sql @@ -8,7 +8,8 @@ CREATE PROCEDURE [dbo].[Notification_Update] @Title NVARCHAR(256), @Body NVARCHAR(MAX), @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7) + @RevisionDate DATETIME2(7), + @TaskId UNIQUEIDENTIFIER = NULL AS BEGIN SET NOCOUNT ON @@ -22,6 +23,7 @@ BEGIN [Title] = @Title, [Body] = @Body, [CreationDate] = @CreationDate, - [RevisionDate] = @RevisionDate + [RevisionDate] = @RevisionDate, + [TaskId] = @TaskId WHERE [Id] = @Id END diff --git a/src/Sql/NotificationCenter/dbo/Tables/Notification.sql b/src/Sql/NotificationCenter/dbo/Tables/Notification.sql index 790168780f..009985c5d0 100644 --- a/src/Sql/NotificationCenter/dbo/Tables/Notification.sql +++ b/src/Sql/NotificationCenter/dbo/Tables/Notification.sql @@ -10,9 +10,11 @@ CREATE TABLE [dbo].[Notification] [Body] NVARCHAR (MAX) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, + [TaskId] UNIQUEIDENTIFIER NULL, CONSTRAINT [PK_Notification] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Notification_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), - CONSTRAINT [FK_Notification_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) + CONSTRAINT [FK_Notification_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]), + CONSTRAINT [FK_Notification_SecurityTask] FOREIGN KEY ([TaskId]) REFERENCES [dbo].[SecurityTask] ([Id]) ); @@ -30,3 +32,6 @@ GO CREATE NONCLUSTERED INDEX [IX_Notification_OrganizationId] ON [dbo].[Notification]([OrganizationId] ASC) WHERE OrganizationId IS NOT NULL; +GO +CREATE NONCLUSTERED INDEX [IX_Notification_TaskId] + ON [dbo].[Notification] ([TaskId] ASC) WHERE TaskId IS NOT NULL; diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql index e2fb2629bd..189ad0a4a5 100644 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql +++ b/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId.sql @@ -5,12 +5,40 @@ AS BEGIN SET NOCOUNT ON - SELECT TOP 1 - * +SELECT + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp], + MAX ([Edit]) AS [Edit], + MAX ([ViewPassword]) AS [ViewPassword] FROM [dbo].[UserCipherDetails](@UserId) WHERE [Id] = @Id - ORDER BY - [Edit] DESC -END \ No newline at end of file + GROUP BY + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp] +END diff --git a/src/Sql/Vault/dbo/Stored Procedures/SecurityTask/SecurityTask_CreateMany.sql b/src/Sql/Vault/dbo/Stored Procedures/SecurityTask/SecurityTask_CreateMany.sql new file mode 100644 index 0000000000..9e60f2ad1b --- /dev/null +++ b/src/Sql/Vault/dbo/Stored Procedures/SecurityTask/SecurityTask_CreateMany.sql @@ -0,0 +1,55 @@ +CREATE PROCEDURE [dbo].[SecurityTask_CreateMany] + @SecurityTasksJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + CREATE TABLE #TempSecurityTasks + ( + [Id] UNIQUEIDENTIFIER, + [OrganizationId] UNIQUEIDENTIFIER, + [CipherId] UNIQUEIDENTIFIER, + [Type] TINYINT, + [Status] TINYINT, + [CreationDate] DATETIME2(7), + [RevisionDate] DATETIME2(7) + ) + + INSERT INTO #TempSecurityTasks + ([Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate]) + SELECT CAST(JSON_VALUE([value], '$.Id') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.OrganizationId') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.CipherId') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.Type') AS TINYINT), + CAST(JSON_VALUE([value], '$.Status') AS TINYINT), + CAST(JSON_VALUE([value], '$.CreationDate') AS DATETIME2(7)), + CAST(JSON_VALUE([value], '$.RevisionDate') AS DATETIME2(7)) + FROM OPENJSON(@SecurityTasksJson) ST + + INSERT INTO [dbo].[SecurityTask] + ( + [Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate] + ) + SELECT [Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate] + FROM #TempSecurityTasks + + DROP TABLE #TempSecurityTasks +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql index 4098ab59e2..f3a1d964b5 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql @@ -14,10 +14,9 @@ BEGIN WHERE [Id] = @CipherId ) - - ;WITH [AvailableCollectionsCTE] AS( - SELECT + SELECT C.[Id] + INTO #TempAvailableCollections FROM [dbo].[Collection] C INNER JOIN @@ -40,38 +39,33 @@ BEGIN CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 ) - ), - [CollectionCiphersCTE] AS( - SELECT - [CollectionId], - [CipherId] - FROM - [dbo].[CollectionCipher] - WHERE - [CipherId] = @CipherId + -- Insert new collection assignments + INSERT INTO [dbo].[CollectionCipher] ( + [CollectionId], + [CipherId] ) - MERGE - [CollectionCiphersCTE] AS [Target] - USING - @CollectionIds AS [Source] - ON - [Target].[CollectionId] = [Source].[Id] - AND [Target].[CipherId] = @CipherId - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT VALUES - ( - [Source].[Id], - @CipherId - ) - WHEN NOT MATCHED BY SOURCE - AND [Target].[CipherId] = @CipherId - AND [Target].[CollectionId] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - DELETE - ; + SELECT + [Id], + @CipherId + FROM @CollectionIds + WHERE [Id] IN (SELECT [Id] FROM [#TempAvailableCollections]) + AND NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionCipher] + WHERE [CollectionId] = [@CollectionIds].[Id] + AND [CipherId] = @CipherId + ); + + -- Delete removed collection assignments + DELETE CC + FROM [dbo].[CollectionCipher] CC + WHERE CC.[CipherId] = @CipherId + AND CC.[CollectionId] IN (SELECT [Id] FROM [#TempAvailableCollections]) + AND CC.[CollectionId] NOT IN (SELECT [Id] FROM @CollectionIds); IF @OrgId IS NOT NULL BEGIN EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId END + DROP TABLE #TempAvailableCollections; END diff --git a/src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql b/src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql new file mode 100644 index 0000000000..362cdce785 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/GroupUser_AddUsers.sql @@ -0,0 +1,39 @@ +CREATE PROCEDURE [dbo].[GroupUser_AddUsers] + @GroupId UNIQUEIDENTIFIER, + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Group] + WHERE + [Id] = @GroupId + ) + + -- Insert + INSERT INTO + [dbo].[GroupUser] (GroupId, OrganizationUserId) + SELECT DISTINCT + @GroupId, + [Source].[Id] + FROM + @OrganizationUserIds AS [Source] + INNER JOIN + [dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[GroupUser] + WHERE + [GroupId] = @GroupId + AND [OrganizationUserId] = [Source].[Id] + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadManyDetailsByRole.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadManyDetailsByRole.sql new file mode 100644 index 0000000000..e8bf8bb701 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadManyDetailsByRole.sql @@ -0,0 +1,16 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_ReadManyDetailsByRole] + @OrganizationId UNIQUEIDENTIFIER, + @Role TINYINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationUserUserDetailsView] + WHERE + OrganizationId = @OrganizationId + AND Status = 2 -- 2 = Confirmed + AND [Type] = @Role +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index d33269063f..25dfcf893d 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -51,18 +51,15 @@ CREATE PROCEDURE [dbo].[Organization_Create] @MaxAutoscaleSmSeats INT= null, @MaxAutoscaleSmServiceAccounts INT = null, @SecretsManagerBeta BIT = 0, - @LimitCollectionCreationDeletion BIT = NULL, -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 @LimitCollectionCreation BIT = NULL, @LimitCollectionDeletion BIT = NULL, @AllowAdminAccessToAllCollectionItems BIT = 0, - @UseRiskInsights BIT = 0 + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0 AS BEGIN SET NOCOUNT ON - SET @LimitCollectionCreation = COALESCE(@LimitCollectionCreation, @LimitCollectionCreationDeletion, 0); - SET @LimitCollectionDeletion = COALESCE(@LimitCollectionDeletion, @LimitCollectionCreationDeletion, 0); - INSERT INTO [dbo].[Organization] ( [Id], @@ -117,11 +114,11 @@ BEGIN [MaxAutoscaleSmSeats], [MaxAutoscaleSmServiceAccounts], [SecretsManagerBeta], - [LimitCollectionCreationDeletion], -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 [LimitCollectionCreation], [LimitCollectionDeletion], [AllowAdminAccessToAllCollectionItems], - [UseRiskInsights] + [UseRiskInsights], + [LimitItemDeletion] ) VALUES ( @@ -177,10 +174,10 @@ BEGIN @MaxAutoscaleSmSeats, @MaxAutoscaleSmServiceAccounts, @SecretsManagerBeta, - COALESCE(@LimitCollectionCreation, @LimitCollectionDeletion, 0), -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863) @LimitCollectionCreation, @LimitCollectionDeletion, @AllowAdminAccessToAllCollectionItems, - @UseRiskInsights + @UseRiskInsights, + @LimitItemDeletion ) END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql index 056bc1416c..49ee0f9c1c 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql @@ -21,11 +21,11 @@ BEGIN [UseResetPassword], [UsePolicies], [Enabled], - [LimitCollectionCreationDeletion], -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 [LimitCollectionCreation], [LimitCollectionDeletion], [AllowAdminAccessToAllCollectionItems], - [UseRiskInsights] + [UseRiskInsights], + [LimitItemDeletion] FROM [dbo].[Organization] END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAddableToProviderByUserId.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAddableToProviderByUserId.sql new file mode 100644 index 0000000000..e11109ae10 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAddableToProviderByUserId.sql @@ -0,0 +1,23 @@ +CREATE PROCEDURE [dbo].[Organization_ReadAddableToProviderByUserId] + @UserId UNIQUEIDENTIFIER, + @ProviderType TINYINT +AS +BEGIN + SET NOCOUNT ON + SELECT O.* FROM [dbo].[OrganizationUser] AS OU + JOIN [dbo].[Organization] AS O ON O.[Id] = OU.[OrganizationId] + WHERE + OU.[UserId] = @UserId AND + OU.[Type] = 0 AND + OU.[Status] = 2 AND + O.[Enabled] = 1 AND + O.[GatewayCustomerId] IS NOT NULL AND + O.[GatewaySubscriptionId] IS NOT NULL AND + O.[Seats] > 0 AND + O.[Status] = 1 AND + O.[UseSecretsManager] = 0 AND + -- All Teams & Enterprise for MSP + (@ProviderType = 0 AND O.[PlanType] IN (2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20) OR + -- All Enterprise for MOE + @ProviderType = 2 AND O.[PlanType] IN (4, 5, 10, 11, 14, 15, 19, 20)); +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql b/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql index e40f78fee0..4f2269b583 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql @@ -1,5 +1,5 @@ CREATE PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] - @Name NVARCHAR(50), + @Name NVARCHAR(55), @OwnerEmail NVARCHAR(256), @Skip INT = 0, @Take INT = 25 @@ -9,7 +9,7 @@ BEGIN SET NOCOUNT ON DECLARE @NameLikeSearch NVARCHAR(55) = '%' + @Name + '%' DECLARE @OwnerLikeSearch NVARCHAR(55) = @OwnerEmail + '%' - + IF @OwnerEmail IS NOT NULL BEGIN SELECT @@ -21,11 +21,11 @@ BEGIN INNER JOIN [dbo].[User] U ON U.[Id] = OU.[UserId] WHERE - ((O.[PlanType] >= 2 AND O.[PlanType] <= 5) OR (O.[PlanType] >= 8 AND O.[PlanType] <= 20) AND (O.PlanType <> 16)) -- All 'Teams' and 'Enterprise' organizations + O.[PlanType] NOT IN (0, 1, 6, 7) -- Not 'Free', 'Custom' or 'Families' AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) AND (U.[Email] LIKE @OwnerLikeSearch) - ORDER BY O.[CreationDate] DESC + ORDER BY O.[CreationDate] DESC, O.[Id] OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY END @@ -36,11 +36,11 @@ BEGIN FROM [dbo].[OrganizationView] O WHERE - ((O.[PlanType] >= 2 AND O.[PlanType] <= 5) OR (O.[PlanType] >= 8 AND O.[PlanType] <= 20) AND (O.PlanType <> 16)) -- All 'Teams' and 'Enterprise' organizations + O.[PlanType] NOT IN (0, 1, 6, 7) -- Not 'Free', 'Custom' or 'Families' AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) - ORDER BY O.[CreationDate] DESC + ORDER BY O.[CreationDate] DESC, O.[Id] OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY END -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index 1bbcb7ebc8..6e9fe88f48 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -51,18 +51,15 @@ CREATE PROCEDURE [dbo].[Organization_Update] @MaxAutoscaleSmSeats INT = null, @MaxAutoscaleSmServiceAccounts INT = null, @SecretsManagerBeta BIT = 0, - @LimitCollectionCreationDeletion BIT = null, -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 @LimitCollectionCreation BIT = null, @LimitCollectionDeletion BIT = null, @AllowAdminAccessToAllCollectionItems BIT = 0, - @UseRiskInsights BIT = 0 + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0 AS BEGIN SET NOCOUNT ON - SET @LimitCollectionCreation = COALESCE(@LimitCollectionCreation, @LimitCollectionCreationDeletion, 0); - SET @LimitCollectionDeletion = COALESCE(@LimitCollectionDeletion, @LimitCollectionCreationDeletion, 0); - UPDATE [dbo].[Organization] SET @@ -117,11 +114,11 @@ BEGIN [MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats, [MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts, [SecretsManagerBeta] = @SecretsManagerBeta, - [LimitCollectionCreationDeletion] = COALESCE(@LimitCollectionCreation, @LimitCollectionDeletion, 0), [LimitCollectionCreation] = @LimitCollectionCreation, [LimitCollectionDeletion] = @LimitCollectionDeletion, [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems, - [UseRiskInsights] = @UseRiskInsights + [UseRiskInsights] = @UseRiskInsights, + [LimitItemDeletion] = @LimitItemDeletion WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByUserId.sql new file mode 100644 index 0000000000..910ff3c4c6 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/PolicyDetails_ReadByUserId.sql @@ -0,0 +1,43 @@ +CREATE PROCEDURE [dbo].[PolicyDetails_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON +SELECT + OU.[Id] AS OrganizationUserId, + P.[OrganizationId], + P.[Type] AS PolicyType, + P.[Data] AS PolicyData, + OU.[Type] AS OrganizationUserType, + OU.[Status] AS OrganizationUserStatus, + OU.[Permissions] AS OrganizationUserPermissionsData, + CASE WHEN EXISTS ( + SELECT 1 + FROM [dbo].[ProviderUserView] PU + INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId] + WHERE PU.[UserId] = OU.[UserId] AND PO.[OrganizationId] = P.[OrganizationId] + ) THEN 1 ELSE 0 END AS IsProvider +FROM [dbo].[PolicyView] P +INNER JOIN [dbo].[OrganizationUserView] OU + ON P.[OrganizationId] = OU.[OrganizationId] +INNER JOIN [dbo].[OrganizationView] O + ON P.[OrganizationId] = O.[Id] +WHERE + P.Enabled = 1 + AND O.Enabled = 1 + AND O.UsePolicies = 1 + AND ( + -- OrgUsers who have accepted their invite and are linked to a UserId + -- (Note: this excludes "invited but revoked" users who don't have an OU.UserId yet, + -- but those users will go through policy enforcement later as part of accepting their invite after being restored. + -- This is an intentionally unhandled edge case for now.) + (OU.[Status] != 0 AND OU.[UserId] = @UserId) + + -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email + OR EXISTS ( + SELECT 1 + FROM [dbo].[UserView] U + WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 + ) + ) +END diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index 279b78bfc1..6d10126972 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -51,9 +51,9 @@ CREATE TABLE [dbo].[Organization] ( [MaxAutoscaleSmSeats] INT NULL, [MaxAutoscaleSmServiceAccounts] INT NULL, [SecretsManagerBeta] BIT NOT NULL CONSTRAINT [DF_Organization_SecretsManagerBeta] DEFAULT (0), - [LimitCollectionCreationDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitCollectionCreationDeletion] DEFAULT (0), [LimitCollectionCreation] BIT NOT NULL CONSTRAINT [DF_Organization_LimitCollectionCreation] DEFAULT (0), [LimitCollectionDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitCollectionDeletion] DEFAULT (0), + [LimitItemDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitItemDeletion] DEFAULT (0), [AllowAdminAccessToAllCollectionItems] BIT NOT NULL CONSTRAINT [DF_Organization_AllowAdminAccessToAllCollectionItems] DEFAULT (0), [UseRiskInsights] BIT NOT NULL CONSTRAINT [DF_Organization_UseRiskInsights] DEFAULT (0), CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC) diff --git a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql index c4f79e0c69..70c7413b75 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -46,11 +46,11 @@ SELECT O.[UsePasswordManager], O.[SmSeats], O.[SmServiceAccounts], - O.[LimitCollectionCreationDeletion], -- Deprecated https://bitwarden.atlassian.net/browse/PM-10863 O.[LimitCollectionCreation], O.[LimitCollectionDeletion], O.[AllowAdminAccessToAllCollectionItems], - O.[UseRiskInsights] + O.[UseRiskInsights], + O.[LimitItemDeletion] FROM [dbo].[OrganizationUser] OU LEFT JOIN diff --git a/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql index 0fcff73699..3a08418ed3 100644 --- a/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql @@ -13,6 +13,7 @@ SELECT (SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status >= 0) OccupiedSeats, O.[Seats], O.[Plan], + O.[PlanType], O.[Status] FROM [dbo].[ProviderOrganization] PO diff --git a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql index 20b896b6ad..be6b6fdd0e 100644 --- a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql @@ -32,12 +32,12 @@ SELECT 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 + P.[Type] ProviderType, + O.[LimitItemDeletion] FROM [dbo].[ProviderUser] PU INNER JOIN diff --git a/test/Api.IntegrationTest/Api.IntegrationTest.csproj b/test/Api.IntegrationTest/Api.IntegrationTest.csproj index 3e300df9ca..8fa74f98d4 100644 --- a/test/Api.IntegrationTest/Api.IntegrationTest.csproj +++ b/test/Api.IntegrationTest/Api.IntegrationTest.csproj @@ -1,6 +1,5 @@ - enable false diff --git a/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs b/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs index dd514803fe..9370948a85 100644 --- a/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs +++ b/test/Api.IntegrationTest/Helpers/OrganizationTestHelpers.cs @@ -36,7 +36,13 @@ public static class OrganizationTestHelpers OwnerKey = ownerKey, Owner = owner, AdditionalSeats = passwordManagerSeats, - PaymentMethodType = paymentMethod + PaymentMethodType = paymentMethod, + PaymentToken = "TOKEN", + TaxInfo = new TaxInfo + { + BillingAddressCountry = "US", + BillingAddressPostalCode = "12345" + } }); Debug.Assert(signUpResult.OrganizationUser is not null); diff --git a/test/Api.IntegrationTest/NotificationCenter/Controllers/NotificationsControllerTests.cs b/test/Api.IntegrationTest/NotificationCenter/Controllers/NotificationsControllerTests.cs index 6d487c5d8f..ca04c9775d 100644 --- a/test/Api.IntegrationTest/NotificationCenter/Controllers/NotificationsControllerTests.cs +++ b/test/Api.IntegrationTest/NotificationCenter/Controllers/NotificationsControllerTests.cs @@ -133,12 +133,10 @@ public class NotificationsControllerTests : IClassFixture [InlineData(null, null, "2", 10)] [InlineData(10, null, "2", 10)] [InlineData(10, 2, "3", 10)] - [InlineData(10, 3, null, 0)] - [InlineData(15, null, "2", 15)] - [InlineData(15, 2, null, 5)] - [InlineData(20, null, "2", 20)] - [InlineData(20, 2, null, 0)] - [InlineData(1000, null, null, 20)] + [InlineData(10, 3, null, 4)] + [InlineData(24, null, "2", 24)] + [InlineData(24, 2, null, 0)] + [InlineData(1000, null, null, 24)] public async Task ListAsync_PaginationFilter_ReturnsNextPageOfNotificationsCorrectOrder( int? pageSize, int? pageNumber, string? expectedContinuationToken, int expectedCount) { @@ -505,11 +503,12 @@ public class NotificationsControllerTests : IClassFixture userPartOrOrganizationNotificationWithStatuses } .SelectMany(n => n) + .Where(n => n.Item1.ClientType is ClientType.All or ClientType.Web) .ToList(); } private async Task> CreateNotificationsAsync(Guid? userId = null, Guid? organizationId = null, - int numberToCreate = 5) + int numberToCreate = 3) { var priorities = Enum.GetValues(); var clientTypes = Enum.GetValues(); @@ -570,13 +569,9 @@ public class NotificationsControllerTests : IClassFixture DeletedDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)) }); - return - [ - (notifications[0], readDateNotificationStatus), - (notifications[1], deletedDateNotificationStatus), - (notifications[2], readDateAndDeletedDateNotificationStatus), - (notifications[3], null), - (notifications[4], null) - ]; + List statuses = + [readDateNotificationStatus, deletedDateNotificationStatus, readDateAndDeletedDateNotificationStatus]; + + return notifications.Select(n => (n, statuses.Find(s => s.NotificationId == n.Id))).ToList(); } } diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs index 0ba8a101d7..e3071bd227 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs @@ -123,24 +123,74 @@ public class OrganizationUsersControllerTests [Theory] [BitAutoData] - public async Task Accept_RequireMasterPasswordReset(Guid orgId, Guid orgUserId, + public async Task Accept_WhenOrganizationUsePoliciesIsEnabledAndResetPolicyIsEnabled_ShouldHandleResetPassword(Guid orgId, Guid orgUserId, OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) { + // Arrange + var applicationCacheService = sutProvider.GetDependency(); + applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true }); + var policy = new Policy { Enabled = true, Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }), }; - sutProvider.GetDependency().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); - sutProvider.GetDependency().GetByOrganizationIdTypeAsync(orgId, + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); + + + var policyRepository = sutProvider.GetDependency(); + policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword).Returns(policy); + // Act await sutProvider.Sut.Accept(orgId, orgUserId, model); + // Assert await sutProvider.GetDependency().Received(1) - .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, sutProvider.GetDependency()); + .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService); await sutProvider.GetDependency().Received(1) .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); + + await userService.Received(1).GetUserByPrincipalAsync(default); + await applicationCacheService.Received(1).GetOrganizationAbilityAsync(orgId); + await policyRepository.Received(1).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); + + } + + [Theory] + [BitAutoData] + public async Task Accept_WhenOrganizationUsePoliciesIsDisabled_ShouldNotHandleResetPassword(Guid orgId, Guid orgUserId, + OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) + { + // Arrange + var applicationCacheService = sutProvider.GetDependency(); + applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = false }); + + var policy = new Policy + { + Enabled = true, + Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }), + }; + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); + + var policyRepository = sutProvider.GetDependency(); + policyRepository.GetByOrganizationIdTypeAsync(orgId, + PolicyType.ResetPassword).Returns(policy); + + // Act + await sutProvider.Sut.Accept(orgId, orgUserId, model); + + // Assert + await userService.Received(1).GetUserByPrincipalAsync(default); + await sutProvider.GetDependency().Received(1) + .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService); + await sutProvider.GetDependency().Received(0) + .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); + + await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); + await applicationCacheService.Received(1).GetOrganizationAbilityAsync(orgId); } [Theory] diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index f35dbaa5cf..b739469c78 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; @@ -52,6 +53,7 @@ public class OrganizationsControllerTests : IDisposable private readonly IDataProtectorTokenFactory _orgDeleteTokenDataFactory; private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand; + private readonly IOrganizationDeleteCommand _organizationDeleteCommand; private readonly OrganizationsController _sut; public OrganizationsControllerTests() @@ -75,6 +77,7 @@ public class OrganizationsControllerTests : IDisposable _orgDeleteTokenDataFactory = Substitute.For>(); _removeOrganizationUserCommand = Substitute.For(); _cloudOrganizationSignUpCommand = Substitute.For(); + _organizationDeleteCommand = Substitute.For(); _sut = new OrganizationsController( _organizationRepository, @@ -95,7 +98,8 @@ public class OrganizationsControllerTests : IDisposable _providerBillingService, _orgDeleteTokenDataFactory, _removeOrganizationUserCommand, - _cloudOrganizationSignUpCommand); + _cloudOrganizationSignUpCommand, + _organizationDeleteCommand); } public void Dispose() @@ -226,6 +230,6 @@ public class OrganizationsControllerTests : IDisposable await _providerBillingService.Received(1) .ScaleSeats(provider, organization.PlanType, -organization.Seats.Value); - await _organizationService.Received(1).DeleteAsync(organization); + await _organizationDeleteCommand.Received(1).DeleteAsync(organization); } } diff --git a/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/ProviderClientsControllerTests.cs similarity index 98% rename from test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs rename to test/Api.Test/AdminConsole/Controllers/ProviderClientsControllerTests.cs index 86bacd9aa3..8ddd92a5fa 100644 --- a/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/ProviderClientsControllerTests.cs @@ -1,5 +1,5 @@ using System.Security.Claims; -using Bit.Api.Billing.Controllers; +using Bit.Api.AdminConsole.Controllers; using Bit.Api.Billing.Models.Requests; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; @@ -19,10 +19,9 @@ using Microsoft.AspNetCore.Http.HttpResults; using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; - using static Bit.Api.Test.Billing.Utilities; -namespace Bit.Api.Test.Billing.Controllers; +namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(ProviderClientsController))] [SutProviderCustomize] diff --git a/test/Api.Test/Api.Test.csproj b/test/Api.Test/Api.Test.csproj index d6b31ce930..ec22583caf 100644 --- a/test/Api.Test/Api.Test.csproj +++ b/test/Api.Test/Api.Test.csproj @@ -2,6 +2,8 @@ false + + $(WarningsNotAsErrors);CS8620;CS0169 diff --git a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs index 1b8c040789..33b7e764d4 100644 --- a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs @@ -419,22 +419,32 @@ public class AccountsControllerTests : IDisposable [Theory] - [BitAutoData(true, false)] // User has PublicKey and PrivateKey, and Keys in request are NOT null - [BitAutoData(true, true)] // User has PublicKey and PrivateKey, and Keys in request are null - [BitAutoData(false, false)] // User has neither PublicKey nor PrivateKey, and Keys in request are NOT null - [BitAutoData(false, true)] // User has neither PublicKey nor PrivateKey, and Keys in request are null + [BitAutoData(true, "existingPrivateKey", "existingPublicKey", true)] // allow providing existing keys in the request + [BitAutoData(true, null, null, true)] // allow not setting the public key when the user already has a key + [BitAutoData(false, "newPrivateKey", "newPublicKey", true)] // allow setting new keys when the user has no keys + [BitAutoData(false, null, null, true)] // allow not setting the public key when the user has no keys + // do not allow single key + [BitAutoData(false, "existingPrivateKey", null, false)] + [BitAutoData(false, null, "existingPublicKey", false)] + [BitAutoData(false, "newPrivateKey", null, false)] + [BitAutoData(false, null, "newPublicKey", false)] + [BitAutoData(true, "existingPrivateKey", null, false)] + [BitAutoData(true, null, "existingPublicKey", false)] + [BitAutoData(true, "newPrivateKey", null, false)] + [BitAutoData(true, null, "newPublicKey", false)] + // reject overwriting existing keys + [BitAutoData(true, "newPrivateKey", "newPublicKey", false)] public async Task PostSetPasswordAsync_WhenUserExistsAndSettingPasswordSucceeds_ShouldHandleKeysCorrectlyAndReturn( - bool hasExistingKeys, - bool shouldSetKeysToNull, - User user, - SetPasswordRequestModel setPasswordRequestModel) + bool hasExistingKeys, + string requestPrivateKey, + string requestPublicKey, + bool shouldSucceed, + User user, + SetPasswordRequestModel setPasswordRequestModel) { // Arrange const string existingPublicKey = "existingPublicKey"; - const string existingEncryptedPrivateKey = "existingEncryptedPrivateKey"; - - const string newPublicKey = "newPublicKey"; - const string newEncryptedPrivateKey = "newEncryptedPrivateKey"; + const string existingEncryptedPrivateKey = "existingPrivateKey"; if (hasExistingKeys) { @@ -447,16 +457,16 @@ public class AccountsControllerTests : IDisposable user.PrivateKey = null; } - if (shouldSetKeysToNull) + if (requestPrivateKey == null && requestPublicKey == null) { setPasswordRequestModel.Keys = null; } else { - setPasswordRequestModel.Keys = new KeysRequestModel() + setPasswordRequestModel.Keys = new KeysRequestModel { - PublicKey = newPublicKey, - EncryptedPrivateKey = newEncryptedPrivateKey + EncryptedPrivateKey = requestPrivateKey, + PublicKey = requestPublicKey }; } @@ -469,44 +479,66 @@ public class AccountsControllerTests : IDisposable .Returns(Task.FromResult(IdentityResult.Success)); // Act - await _sut.PostSetPasswordAsync(setPasswordRequestModel); - - // Assert - await _setInitialMasterPasswordCommand.Received(1) - .SetInitialMasterPasswordAsync( - Arg.Is(u => u == user), - Arg.Is(s => s == setPasswordRequestModel.MasterPasswordHash), - Arg.Is(s => s == setPasswordRequestModel.Key), - Arg.Is(s => s == setPasswordRequestModel.OrgIdentifier)); - - // Additional Assertions for User object modifications - Assert.Equal(setPasswordRequestModel.MasterPasswordHint, user.MasterPasswordHint); - Assert.Equal(setPasswordRequestModel.Kdf, user.Kdf); - Assert.Equal(setPasswordRequestModel.KdfIterations, user.KdfIterations); - Assert.Equal(setPasswordRequestModel.KdfMemory, user.KdfMemory); - Assert.Equal(setPasswordRequestModel.KdfParallelism, user.KdfParallelism); - Assert.Equal(setPasswordRequestModel.Key, user.Key); - - if (hasExistingKeys) + if (shouldSucceed) { - // User Keys should not be modified - Assert.Equal(existingPublicKey, user.PublicKey); - Assert.Equal(existingEncryptedPrivateKey, user.PrivateKey); - } - else if (!shouldSetKeysToNull) - { - // User had no keys so they should be set to the request model's keys - Assert.Equal(setPasswordRequestModel.Keys.PublicKey, user.PublicKey); - Assert.Equal(setPasswordRequestModel.Keys.EncryptedPrivateKey, user.PrivateKey); + await _sut.PostSetPasswordAsync(setPasswordRequestModel); + // Assert + await _setInitialMasterPasswordCommand.Received(1) + .SetInitialMasterPasswordAsync( + Arg.Is(u => u == user), + Arg.Is(s => s == setPasswordRequestModel.MasterPasswordHash), + Arg.Is(s => s == setPasswordRequestModel.Key), + Arg.Is(s => s == setPasswordRequestModel.OrgIdentifier)); + + // Additional Assertions for User object modifications + Assert.Equal(setPasswordRequestModel.MasterPasswordHint, user.MasterPasswordHint); + Assert.Equal(setPasswordRequestModel.Kdf, user.Kdf); + Assert.Equal(setPasswordRequestModel.KdfIterations, user.KdfIterations); + Assert.Equal(setPasswordRequestModel.KdfMemory, user.KdfMemory); + Assert.Equal(setPasswordRequestModel.KdfParallelism, user.KdfParallelism); + Assert.Equal(setPasswordRequestModel.Key, user.Key); } else { - // User had no keys and the request model's keys were null, so they should be set to null - Assert.Null(user.PublicKey); - Assert.Null(user.PrivateKey); + await Assert.ThrowsAsync(() => _sut.PostSetPasswordAsync(setPasswordRequestModel)); } } + [Theory] + [BitAutoData] + public async Task PostSetPasswordAsync_WhenUserExistsAndHasKeysAndKeysAreUpdated_ShouldThrowAsync( + User user, + SetPasswordRequestModel setPasswordRequestModel) + { + // Arrange + const string existingPublicKey = "existingPublicKey"; + const string existingEncryptedPrivateKey = "existingEncryptedPrivateKey"; + + const string newPublicKey = "newPublicKey"; + const string newEncryptedPrivateKey = "newEncryptedPrivateKey"; + + user.PublicKey = existingPublicKey; + user.PrivateKey = existingEncryptedPrivateKey; + + setPasswordRequestModel.Keys = new KeysRequestModel() + { + PublicKey = newPublicKey, + EncryptedPrivateKey = newEncryptedPrivateKey + }; + + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(Task.FromResult(user)); + _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync( + user, + setPasswordRequestModel.MasterPasswordHash, + setPasswordRequestModel.Key, + setPasswordRequestModel.OrgIdentifier) + .Returns(Task.FromResult(IdentityResult.Success)); + + // Act & Assert + await Assert.ThrowsAsync(() => _sut.PostSetPasswordAsync(setPasswordRequestModel)); + } + + [Theory] [BitAutoData] public async Task PostSetPasswordAsync_WhenUserDoesNotExist_ShouldThrowUnauthorizedAccessException( @@ -525,6 +557,7 @@ public class AccountsControllerTests : IDisposable User user, SetPasswordRequestModel model) { + model.Keys = null; // Arrange _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(Task.FromResult(user)); _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) diff --git a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs index e7c5cd9ef5..2afce14ac5 100644 --- a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs @@ -127,6 +127,7 @@ public class CiphersControllerTests UserId = userId, OrganizationId = Guid.NewGuid(), Type = CipherType.Login, + ViewPassword = true, Data = @" { ""Uris"": [ diff --git a/test/Billing.Test/Billing.Test.csproj b/test/Billing.Test/Billing.Test.csproj index 4d71425681..b4ea2938f6 100644 --- a/test/Billing.Test/Billing.Test.csproj +++ b/test/Billing.Test/Billing.Test.csproj @@ -6,7 +6,6 @@ - diff --git a/test/Billing.Test/Controllers/FreshdeskControllerTests.cs b/test/Billing.Test/Controllers/FreshdeskControllerTests.cs index f07c64dad9..26ce310b9c 100644 --- a/test/Billing.Test/Controllers/FreshdeskControllerTests.cs +++ b/test/Billing.Test/Controllers/FreshdeskControllerTests.cs @@ -1,4 +1,5 @@ -using Bit.Billing.Controllers; +using System.Text.Json; +using Bit.Billing.Controllers; using Bit.Billing.Models; using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; @@ -70,6 +71,159 @@ public class FreshdeskControllerTests _ = mockHttpMessageHandler.Received(1).Send(Arg.Is(m => m.Method == HttpMethod.Post && m.RequestUri.ToString().EndsWith($"{model.TicketId}/notes")), Arg.Any()); } + [Theory] + [BitAutoData((string)null, null)] + [BitAutoData((string)null)] + [BitAutoData(WebhookKey, null)] + public async Task PostWebhookOnyxAi_InvalidWebhookKey_results_in_BadRequest( + string freshdeskWebhookKey, FreshdeskWebhookModel model, + BillingSettings billingSettings, SutProvider sutProvider) + { + sutProvider.GetDependency>() + .Value.FreshDesk.WebhookKey.Returns(billingSettings.FreshDesk.WebhookKey); + + var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); + + var statusCodeResult = Assert.IsAssignableFrom(response); + Assert.Equal(StatusCodes.Status400BadRequest, statusCodeResult.StatusCode); + } + + [Theory] + [BitAutoData(WebhookKey)] + public async Task PostWebhookOnyxAi_invalid_ticketid_results_in_BadRequest( + string freshdeskWebhookKey, FreshdeskWebhookModel model, SutProvider sutProvider) + { + sutProvider.GetDependency>() + .Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); + + var mockHttpMessageHandler = Substitute.ForPartsOf(); + var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); + mockHttpMessageHandler.Send(Arg.Any(), Arg.Any()) + .Returns(mockResponse); + var httpClient = new HttpClient(mockHttpMessageHandler); + + sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(httpClient); + + var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); + + var result = Assert.IsAssignableFrom(response); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + } + + [Theory] + [BitAutoData(WebhookKey)] + public async Task PostWebhookOnyxAi_invalid_freshdesk_response_results_in_BadRequest( + string freshdeskWebhookKey, FreshdeskWebhookModel model, + SutProvider sutProvider) + { + sutProvider.GetDependency>() + .Value.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); + + var mockHttpMessageHandler = Substitute.ForPartsOf(); + var mockResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = new StringContent("non json content. expect json deserializer to throw error") + }; + mockHttpMessageHandler.Send(Arg.Any(), Arg.Any()) + .Returns(mockResponse); + var httpClient = new HttpClient(mockHttpMessageHandler); + + sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(httpClient); + + var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); + + var result = Assert.IsAssignableFrom(response); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + } + + [Theory] + [BitAutoData(WebhookKey)] + public async Task PostWebhookOnyxAi_invalid_onyx_response_results_in_BadRequest( + string freshdeskWebhookKey, FreshdeskWebhookModel model, + FreshdeskViewTicketModel freshdeskTicketInfo, SutProvider sutProvider) + { + var billingSettings = sutProvider.GetDependency>().Value; + billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); + billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api"); + + // mocking freshdesk Api request for ticket info + var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf(); + var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo)) + }; + mockFreshdeskHttpMessageHandler.Send(Arg.Any(), Arg.Any()) + .Returns(mockFreshdeskResponse); + var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler); + + // mocking Onyx api response given a ticket description + var mockOnyxHttpMessageHandler = Substitute.ForPartsOf(); + var mockOnyxResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); + mockOnyxHttpMessageHandler.Send(Arg.Any(), Arg.Any()) + .Returns(mockOnyxResponse); + var onyxHttpClient = new HttpClient(mockOnyxHttpMessageHandler); + + sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(freshdeskHttpClient); + sutProvider.GetDependency().CreateClient("OnyxApi").Returns(onyxHttpClient); + + var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); + + var result = Assert.IsAssignableFrom(response); + Assert.Equal(StatusCodes.Status400BadRequest, result.StatusCode); + } + + [Theory] + [BitAutoData(WebhookKey)] + public async Task PostWebhookOnyxAi_success( + string freshdeskWebhookKey, FreshdeskWebhookModel model, + FreshdeskViewTicketModel freshdeskTicketInfo, + OnyxAnswerWithCitationResponseModel onyxResponse, + SutProvider sutProvider) + { + var billingSettings = sutProvider.GetDependency>().Value; + billingSettings.FreshDesk.WebhookKey.Returns(freshdeskWebhookKey); + billingSettings.Onyx.BaseUrl.Returns("http://simulate-onyx-api.com/api"); + + // mocking freshdesk Api request for ticket info (GET) + var mockFreshdeskHttpMessageHandler = Substitute.ForPartsOf(); + var mockFreshdeskResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(freshdeskTicketInfo)) + }; + mockFreshdeskHttpMessageHandler.Send( + Arg.Is(_ => _.Method == HttpMethod.Get), + Arg.Any()) + .Returns(mockFreshdeskResponse); + + // mocking freshdesk api add note request (POST) + var mockFreshdeskAddNoteResponse = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest); + mockFreshdeskHttpMessageHandler.Send( + Arg.Is(_ => _.Method == HttpMethod.Post), + Arg.Any()) + .Returns(mockFreshdeskAddNoteResponse); + var freshdeskHttpClient = new HttpClient(mockFreshdeskHttpMessageHandler); + + + // mocking Onyx api response given a ticket description + var mockOnyxHttpMessageHandler = Substitute.ForPartsOf(); + onyxResponse.ErrorMsg = string.Empty; + var mockOnyxResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(onyxResponse)) + }; + mockOnyxHttpMessageHandler.Send(Arg.Any(), Arg.Any()) + .Returns(mockOnyxResponse); + var onyxHttpClient = new HttpClient(mockOnyxHttpMessageHandler); + + sutProvider.GetDependency().CreateClient("FreshdeskApi").Returns(freshdeskHttpClient); + sutProvider.GetDependency().CreateClient("OnyxApi").Returns(onyxHttpClient); + + var response = await sutProvider.Sut.PostWebhookOnyxAi(freshdeskWebhookKey, model); + + var result = Assert.IsAssignableFrom(response); + Assert.Equal(StatusCodes.Status200OK, result.StatusCode); + } + public class MockHttpMessageHandler : HttpMessageHandler { protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) diff --git a/test/Billing.Test/Controllers/PayPalControllerTests.cs b/test/Billing.Test/Controllers/PayPalControllerTests.cs index 3c9edd2220..7ec17bd85a 100644 --- a/test/Billing.Test/Controllers/PayPalControllerTests.cs +++ b/test/Billing.Test/Controllers/PayPalControllerTests.cs @@ -3,12 +3,12 @@ using Bit.Billing.Controllers; using Bit.Billing.Test.Utilities; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.Services; using Divergic.Logging.Xunit; -using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -34,6 +34,7 @@ public class PayPalControllerTests private readonly ITransactionRepository _transactionRepository = Substitute.For(); private readonly IUserRepository _userRepository = Substitute.For(); private readonly IProviderRepository _providerRepository = Substitute.For(); + private readonly IPremiumUserBillingService _premiumUserBillingService = Substitute.For(); private const string _defaultWebhookKey = "webhook-key"; @@ -386,8 +387,6 @@ public class PayPalControllerTests _userRepository.GetByIdAsync(userId).Returns(user); - _paymentService.CreditAccountAsync(user, 48M).Returns(true); - var controller = ConfigureControllerContextWith(logger, _defaultWebhookKey, ipnBody); var result = await controller.PostIpn(); @@ -399,9 +398,7 @@ public class PayPalControllerTests transaction.UserId == userId && transaction.Amount == 48M)); - await _paymentService.Received(1).CreditAccountAsync(user, 48M); - - await _userRepository.Received(1).ReplaceAsync(user); + await _premiumUserBillingService.Received(1).Credit(user, 48M); await _mailService.Received(1).SendAddedCreditAsync(billingEmail, 48M); } @@ -545,7 +542,8 @@ public class PayPalControllerTests _paymentService, _transactionRepository, _userRepository, - _providerRepository); + _providerRepository, + _premiumUserBillingService); var httpContext = new DefaultHttpContext(); @@ -577,14 +575,14 @@ public class PayPalControllerTests { var statusCodeActionResult = (IStatusCodeActionResult)result; - statusCodeActionResult.StatusCode.Should().Be(statusCode); + Assert.Equal(statusCode, statusCodeActionResult.StatusCode); } private static void Logged(ICacheLogger logger, LogLevel logLevel, string message) { - logger.Last.Should().NotBeNull(); - logger.Last!.LogLevel.Should().Be(logLevel); - logger.Last!.Message.Should().Be(message); + Assert.NotNull(logger.Last); + Assert.Equal(logLevel, logger.Last!.LogLevel); + Assert.Equal(message, logger.Last!.Message); } private static void LoggedError(ICacheLogger logger, string message) diff --git a/test/Billing.Test/Services/StripeEventServiceTests.cs b/test/Billing.Test/Services/StripeEventServiceTests.cs index 15aa5c7234..b40e8b9408 100644 --- a/test/Billing.Test/Services/StripeEventServiceTests.cs +++ b/test/Billing.Test/Services/StripeEventServiceTests.cs @@ -2,7 +2,6 @@ using Bit.Billing.Services.Implementations; using Bit.Billing.Test.Utilities; using Bit.Core.Settings; -using FluentAssertions; using Microsoft.Extensions.Logging; using NSubstitute; using Stripe; @@ -36,10 +35,8 @@ public class StripeEventServiceTests var function = async () => await _stripeEventService.GetCharge(stripeEvent); // Assert - await function - .Should() - .ThrowAsync() - .WithMessage($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Charge)}'"); + var exception = await Assert.ThrowsAsync(function); + Assert.Equal($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Charge)}'", exception.Message); await _stripeFacade.DidNotReceiveWithAnyArgs().GetCharge( Arg.Any(), @@ -58,7 +55,7 @@ public class StripeEventServiceTests var charge = await _stripeEventService.GetCharge(stripeEvent); // Assert - charge.Should().BeEquivalentTo(stripeEvent.Data.Object as Charge); + Assert.Equivalent(stripeEvent.Data.Object as Charge, charge, true); await _stripeFacade.DidNotReceiveWithAnyArgs().GetCharge( Arg.Any(), @@ -88,8 +85,8 @@ public class StripeEventServiceTests var charge = await _stripeEventService.GetCharge(stripeEvent, true, expand); // Assert - charge.Should().Be(apiCharge); - charge.Should().NotBeSameAs(eventCharge); + Assert.Equal(apiCharge, charge); + Assert.NotSame(eventCharge, charge); await _stripeFacade.Received().GetCharge( apiCharge.Id, @@ -110,10 +107,8 @@ public class StripeEventServiceTests var function = async () => await _stripeEventService.GetCustomer(stripeEvent); // Assert - await function - .Should() - .ThrowAsync() - .WithMessage($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Customer)}'"); + var exception = await Assert.ThrowsAsync(function); + Assert.Equal($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Customer)}'", exception.Message); await _stripeFacade.DidNotReceiveWithAnyArgs().GetCustomer( Arg.Any(), @@ -132,7 +127,7 @@ public class StripeEventServiceTests var customer = await _stripeEventService.GetCustomer(stripeEvent); // Assert - customer.Should().BeEquivalentTo(stripeEvent.Data.Object as Customer); + Assert.Equivalent(stripeEvent.Data.Object as Customer, customer, true); await _stripeFacade.DidNotReceiveWithAnyArgs().GetCustomer( Arg.Any(), @@ -162,8 +157,8 @@ public class StripeEventServiceTests var customer = await _stripeEventService.GetCustomer(stripeEvent, true, expand); // Assert - customer.Should().Be(apiCustomer); - customer.Should().NotBeSameAs(eventCustomer); + Assert.Equal(apiCustomer, customer); + Assert.NotSame(eventCustomer, customer); await _stripeFacade.Received().GetCustomer( apiCustomer.Id, @@ -184,10 +179,8 @@ public class StripeEventServiceTests var function = async () => await _stripeEventService.GetInvoice(stripeEvent); // Assert - await function - .Should() - .ThrowAsync() - .WithMessage($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Invoice)}'"); + var exception = await Assert.ThrowsAsync(function); + Assert.Equal($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Invoice)}'", exception.Message); await _stripeFacade.DidNotReceiveWithAnyArgs().GetInvoice( Arg.Any(), @@ -206,7 +199,7 @@ public class StripeEventServiceTests var invoice = await _stripeEventService.GetInvoice(stripeEvent); // Assert - invoice.Should().BeEquivalentTo(stripeEvent.Data.Object as Invoice); + Assert.Equivalent(stripeEvent.Data.Object as Invoice, invoice, true); await _stripeFacade.DidNotReceiveWithAnyArgs().GetInvoice( Arg.Any(), @@ -236,8 +229,8 @@ public class StripeEventServiceTests var invoice = await _stripeEventService.GetInvoice(stripeEvent, true, expand); // Assert - invoice.Should().Be(apiInvoice); - invoice.Should().NotBeSameAs(eventInvoice); + Assert.Equal(apiInvoice, invoice); + Assert.NotSame(eventInvoice, invoice); await _stripeFacade.Received().GetInvoice( apiInvoice.Id, @@ -258,10 +251,8 @@ public class StripeEventServiceTests var function = async () => await _stripeEventService.GetPaymentMethod(stripeEvent); // Assert - await function - .Should() - .ThrowAsync() - .WithMessage($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(PaymentMethod)}'"); + var exception = await Assert.ThrowsAsync(function); + Assert.Equal($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(PaymentMethod)}'", exception.Message); await _stripeFacade.DidNotReceiveWithAnyArgs().GetPaymentMethod( Arg.Any(), @@ -280,7 +271,7 @@ public class StripeEventServiceTests var paymentMethod = await _stripeEventService.GetPaymentMethod(stripeEvent); // Assert - paymentMethod.Should().BeEquivalentTo(stripeEvent.Data.Object as PaymentMethod); + Assert.Equivalent(stripeEvent.Data.Object as PaymentMethod, paymentMethod, true); await _stripeFacade.DidNotReceiveWithAnyArgs().GetPaymentMethod( Arg.Any(), @@ -310,8 +301,8 @@ public class StripeEventServiceTests var paymentMethod = await _stripeEventService.GetPaymentMethod(stripeEvent, true, expand); // Assert - paymentMethod.Should().Be(apiPaymentMethod); - paymentMethod.Should().NotBeSameAs(eventPaymentMethod); + Assert.Equal(apiPaymentMethod, paymentMethod); + Assert.NotSame(eventPaymentMethod, paymentMethod); await _stripeFacade.Received().GetPaymentMethod( apiPaymentMethod.Id, @@ -332,10 +323,8 @@ public class StripeEventServiceTests var function = async () => await _stripeEventService.GetSubscription(stripeEvent); // Assert - await function - .Should() - .ThrowAsync() - .WithMessage($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Subscription)}'"); + var exception = await Assert.ThrowsAsync(function); + Assert.Equal($"Stripe event with ID '{stripeEvent.Id}' does not have object matching type '{nameof(Subscription)}'", exception.Message); await _stripeFacade.DidNotReceiveWithAnyArgs().GetSubscription( Arg.Any(), @@ -354,7 +343,7 @@ public class StripeEventServiceTests var subscription = await _stripeEventService.GetSubscription(stripeEvent); // Assert - subscription.Should().BeEquivalentTo(stripeEvent.Data.Object as Subscription); + Assert.Equivalent(stripeEvent.Data.Object as Subscription, subscription, true); await _stripeFacade.DidNotReceiveWithAnyArgs().GetSubscription( Arg.Any(), @@ -384,8 +373,8 @@ public class StripeEventServiceTests var subscription = await _stripeEventService.GetSubscription(stripeEvent, true, expand); // Assert - subscription.Should().Be(apiSubscription); - subscription.Should().NotBeSameAs(eventSubscription); + Assert.Equal(apiSubscription, subscription); + Assert.NotSame(eventSubscription, subscription); await _stripeFacade.Received().GetSubscription( apiSubscription.Id, @@ -417,7 +406,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetSubscription( subscription.Id, @@ -447,7 +436,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetCharge( charge.Id, @@ -475,7 +464,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetCustomer( invoice.CustomerId, @@ -505,7 +494,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetInvoice( invoice.Id, @@ -535,7 +524,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetPaymentMethod( paymentMethod.Id, @@ -561,7 +550,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetCustomer( customer.Id, @@ -592,7 +581,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeFalse(); + Assert.False(cloudRegionValid); await _stripeFacade.Received(1).GetSubscription( subscription.Id, @@ -623,7 +612,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetSubscription( subscription.Id, @@ -657,7 +646,7 @@ public class StripeEventServiceTests var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); // Assert - cloudRegionValid.Should().BeTrue(); + Assert.True(cloudRegionValid); await _stripeFacade.Received(1).GetSubscription( subscription.Id, diff --git a/test/Common/MockedHttpClient/MockedHttpMessageHandler.cs b/test/Common/MockedHttpClient/MockedHttpMessageHandler.cs index 1b1bd52a03..8a6c1dae97 100644 --- a/test/Common/MockedHttpClient/MockedHttpMessageHandler.cs +++ b/test/Common/MockedHttpClient/MockedHttpMessageHandler.cs @@ -8,6 +8,8 @@ public class MockedHttpMessageHandler : HttpMessageHandler { private readonly List _matchers = new(); + public List CapturedRequests { get; } = new List(); + /// /// The fallback handler to use when the request does not match any of the provided matchers. /// @@ -16,6 +18,7 @@ public class MockedHttpMessageHandler : HttpMessageHandler protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + CapturedRequests.Add(request); var matcher = _matchers.FirstOrDefault(x => x.Matches(request)); if (matcher == null) { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AcceptOrgUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AcceptOrgUserCommandTests.cs index eca4f449b0..2dda23481a 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AcceptOrgUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AcceptOrgUserCommandTests.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Models.Business.Tokenables; +using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -182,6 +183,29 @@ public class AcceptOrgUserCommandTests exception.Message); } + [Theory] + [BitAutoData(OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Owner)] + public async Task AcceptOrgUser_AdminOfFreePlanTryingToJoinSecondFreeOrg_ThrowsBadRequest( + OrganizationUserType userType, + SutProvider sutProvider, + User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails) + { + // Arrange + SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails); + org.PlanType = PlanType.Free; + orgUser.Type = userType; + + sutProvider.GetDependency() + .GetCountByFreeOrganizationAdminUserAsync(user.Id) + .Returns(1); + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService)); + + Assert.Equal("You can only be an admin of one free organization.", exception.Message); + } // AcceptOrgUserByOrgIdAsync tests -------------------------------------------------------------------------------- diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommandTests.cs index 3653cd27d7..0ccad9e5c7 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/RevokeNonCompliantOrganizationUserCommandTests.cs @@ -162,7 +162,7 @@ public class RevokeNonCompliantOrganizationUserCommandTests await sutProvider.GetDependency() .Received(1) - .RevokeManyByIdAsync(Arg.Any>()); + .RevokeManyByIdAsync(Arg.Is>(x => x.Count() == 1 && x.Contains(userToRevoke.Id))); Assert.True(result.Success); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs index 73bf00474b..cd03f9583b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs @@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -144,6 +145,7 @@ public class UpdateOrganizationUserCommandTests newUserData.Id = oldUserData.Id; newUserData.UserId = oldUserData.UserId; newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId = organization.Id; + newUserData.Type = OrganizationUserType.Admin; newUserData.Permissions = JsonSerializer.Serialize(permissions, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -159,6 +161,10 @@ public class UpdateOrganizationUserCommandTests .Returns(callInfo => callInfo.Arg>() .Select(guid => new Group { Id = guid, OrganizationId = oldUserData.OrganizationId }).ToList()); + sutProvider.GetDependency() + .GetCountByFreeOrganizationAdminUserAsync(newUserData.Id) + .Returns(0); + await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups); var organizationService = sutProvider.GetDependency(); @@ -175,6 +181,31 @@ public class UpdateOrganizationUserCommandTests Arg.Is>(i => i.Contains(newUserData.Id))); } + [Theory] + [BitAutoData(OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Owner)] + public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws( + OrganizationUserType userType, + OrganizationUser oldUserData, + OrganizationUser newUserData, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = PlanType.Free; + newUserData.Type = userType; + + Setup(sutProvider, organization, newUserData, oldUserData); + + sutProvider.GetDependency() + .GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value) + .Returns(1); + + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(newUserData, null, null, null)); + Assert.Contains("User can only be an admin of one free organization.", exception.Message); + } + private void Setup(SutProvider sutProvider, Organization organization, OrganizationUser newUser, OrganizationUser oldUser) { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommandTests.cs new file mode 100644 index 0000000000..0a83bb89d8 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationDeleteCommandTests.cs @@ -0,0 +1,53 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.OrganizationFixtures; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations; + +[SutProviderCustomize] +public class OrganizationDeleteCommandTests +{ + [Theory, PaidOrganizationCustomize, BitAutoData] + public async Task Delete_Success(Organization organization, SutProvider sutProvider) + { + var organizationRepository = sutProvider.GetDependency(); + var applicationCacheService = sutProvider.GetDependency(); + + await sutProvider.Sut.DeleteAsync(organization); + + await organizationRepository.Received().DeleteAsync(organization); + await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id); + } + + [Theory, PaidOrganizationCustomize, BitAutoData] + public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider sutProvider, + SsoConfig ssoConfig) + { + ssoConfig.Enabled = true; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector }); + var ssoConfigRepository = sutProvider.GetDependency(); + var organizationRepository = sutProvider.GetDependency(); + var applicationCacheService = sutProvider.GetDependency(); + + ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.DeleteAsync(organization)); + + Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message); + + await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default); + await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommandTests.cs new file mode 100644 index 0000000000..6289c3b8e3 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationEnableCommandTests.cs @@ -0,0 +1,147 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +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; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations; + +[SutProviderCustomize] +public class OrganizationEnableCommandTests +{ + [Theory, BitAutoData] + public async Task EnableAsync_WhenOrganizationDoesNotExist_DoesNothing( + Guid organizationId, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetByIdAsync(organizationId) + .Returns((Organization)null); + + await sutProvider.Sut.EnableAsync(organizationId); + + await sutProvider.GetDependency() + .DidNotReceive() + .ReplaceAsync(Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceive() + .UpsertOrganizationAbilityAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task EnableAsync_WhenOrganizationAlreadyEnabled_DoesNothing( + Organization organization, + SutProvider sutProvider) + { + organization.Enabled = true; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + await sutProvider.Sut.EnableAsync(organization.Id); + + await sutProvider.GetDependency() + .DidNotReceive() + .ReplaceAsync(Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceive() + .UpsertOrganizationAbilityAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task EnableAsync_WhenOrganizationDisabled_EnablesAndSaves( + Organization organization, + SutProvider sutProvider) + { + organization.Enabled = false; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + await sutProvider.Sut.EnableAsync(organization.Id); + + Assert.True(organization.Enabled); + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync(organization); + await sutProvider.GetDependency() + .Received(1) + .UpsertOrganizationAbilityAsync(organization); + } + + [Theory, BitAutoData] + public async Task EnableAsync_WithExpiration_WhenOrganizationHasNoGateway_DoesNothing( + Organization organization, + DateTime expirationDate, + SutProvider sutProvider) + { + organization.Enabled = false; + organization.Gateway = null; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + await sutProvider.Sut.EnableAsync(organization.Id, expirationDate); + + await sutProvider.GetDependency() + .DidNotReceive() + .ReplaceAsync(Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceive() + .UpsertOrganizationAbilityAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task EnableAsync_WithExpiration_WhenValid_EnablesAndSetsExpiration( + Organization organization, + DateTime expirationDate, + SutProvider sutProvider) + { + organization.Enabled = false; + organization.Gateway = GatewayType.Stripe; + organization.RevisionDate = DateTime.UtcNow.AddDays(-1); + var originalRevisionDate = organization.RevisionDate; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + await sutProvider.Sut.EnableAsync(organization.Id, expirationDate); + + Assert.True(organization.Enabled); + Assert.Equal(expirationDate, organization.ExpirationDate); + Assert.True(organization.RevisionDate > originalRevisionDate); + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync(organization); + await sutProvider.GetDependency() + .Received(1) + .UpsertOrganizationAbilityAsync(organization); + } + + [Theory, BitAutoData] + public async Task EnableAsync_WithoutExpiration_DoesNotUpdateRevisionDate( + Organization organization, + SutProvider sutProvider) + { + organization.Enabled = false; + var originalRevisionDate = organization.RevisionDate; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + await sutProvider.Sut.EnableAsync(organization.Id); + + Assert.True(organization.Enabled); + Assert.Equal(originalRevisionDate, organization.RevisionDate); + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync(organization); + await sutProvider.GetDependency() + .Received(1) + .UpsertOrganizationAbilityAsync(organization); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommandTests.cs new file mode 100644 index 0000000000..41c5b569d4 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationInitiateDeleteCommandTests.cs @@ -0,0 +1,112 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Business.Tokenables; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Tokens; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations; + +[SutProviderCustomize] +public class OrganizationInitiateDeleteCommandTests +{ + [Theory] + [BitAutoData(OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Owner)] + public async Task InitiateDeleteAsync_ValidAdminUser_Success(OrganizationUserType organizationUserType, + Organization organization, User orgAdmin, OrganizationUserOrganizationDetails orgAdminUser, + string token, SutProvider sutProvider) + { + orgAdminUser.Type = organizationUserType; + orgAdminUser.Status = OrganizationUserStatusType.Confirmed; + + sutProvider.GetDependency() + .GetByEmailAsync(orgAdmin.Email) + .Returns(orgAdmin); + + sutProvider.GetDependency() + .GetDetailsByUserAsync(orgAdmin.Id, organization.Id) + .Returns(orgAdminUser); + + sutProvider.GetDependency>() + .Protect(Arg.Any()) + .Returns(token); + + await sutProvider.Sut.InitiateDeleteAsync(organization, orgAdmin.Email); + + await sutProvider.GetDependency().Received(1) + .SendInitiateDeleteOrganzationEmailAsync(orgAdmin.Email, organization, token); + } + + [Theory, BitAutoData] + public async Task InitiateDeleteAsync_UserNotFound_ThrowsBadRequest( + Organization organization, string email, SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetByEmailAsync(email) + .Returns((User)null); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InitiateDeleteAsync(organization, email)); + + Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message); + } + + [Theory] + [BitAutoData(OrganizationUserType.User)] + [BitAutoData(OrganizationUserType.Custom)] + public async Task InitiateDeleteAsync_UserNotOrgAdmin_ThrowsBadRequest(OrganizationUserType organizationUserType, + Organization organization, User user, OrganizationUserOrganizationDetails orgUser, + SutProvider sutProvider) + { + orgUser.Type = organizationUserType; + orgUser.Status = OrganizationUserStatusType.Confirmed; + + sutProvider.GetDependency() + .GetByEmailAsync(user.Email) + .Returns(user); + + sutProvider.GetDependency() + .GetDetailsByUserAsync(user.Id, organization.Id) + .Returns(orgUser); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email)); + + Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message); + } + + [Theory] + [BitAutoData(OrganizationUserStatusType.Invited)] + [BitAutoData(OrganizationUserStatusType.Revoked)] + [BitAutoData(OrganizationUserStatusType.Accepted)] + public async Task InitiateDeleteAsync_UserNotConfirmed_ThrowsBadRequest( + OrganizationUserStatusType organizationUserStatusType, + Organization organization, User user, OrganizationUserOrganizationDetails orgUser, + SutProvider sutProvider) + { + orgUser.Type = OrganizationUserType.Admin; + orgUser.Status = organizationUserStatusType; + + sutProvider.GetDependency() + .GetByEmailAsync(user.Email) + .Returns(user); + + sutProvider.GetDependency() + .GetDetailsByUserAsync(user.Id, organization.Id) + .Returns(orgUser); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InitiateDeleteAsync(organization, user.Email)); + + Assert.Equal(OrganizationInitiateDeleteCommand.OrganizationAdminNotFoundErrorMessage, exception.Message); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs index a16b48240c..859c74f3d0 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/CloudOrganizationSignUpCommandTests.cs @@ -1,14 +1,14 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Models.Sales; +using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.Data; -using Bit.Core.Models.StaticStore; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Business; using Bit.Core.Tools.Services; @@ -60,20 +60,16 @@ public class CloudICloudOrganizationSignUpCommandTests Assert.NotNull(result.Organization); Assert.NotNull(result.OrganizationUser); - await sutProvider.GetDependency().Received(1).PurchaseOrganizationAsync( - Arg.Any(), - signup.PaymentMethodType.Value, - signup.PaymentToken, - plan, - signup.AdditionalStorageGb, - signup.AdditionalSeats, - signup.PremiumAccessAddon, - signup.TaxInfo, - false, - signup.AdditionalSmSeats.GetValueOrDefault(), - signup.AdditionalServiceAccounts.GetValueOrDefault(), - signup.UseSecretsManager - ); + await sutProvider.GetDependency().Received(1).Finalize( + Arg.Is(sale => + sale.CustomerSetup.TokenizedPaymentSource.Type == signup.PaymentMethodType.Value && + sale.CustomerSetup.TokenizedPaymentSource.Token == signup.PaymentToken && + sale.CustomerSetup.TaxInformation.Country == signup.TaxInfo.BillingAddressCountry && + sale.CustomerSetup.TaxInformation.PostalCode == signup.TaxInfo.BillingAddressPostalCode && + sale.SubscriptionSetup.Plan == plan && + sale.SubscriptionSetup.PasswordManagerOptions.Seats == signup.AdditionalSeats && + sale.SubscriptionSetup.PasswordManagerOptions.Storage == signup.AdditionalStorageGb && + sale.SubscriptionSetup.SecretsManagerOptions == null)); } [Theory] @@ -155,20 +151,17 @@ public class CloudICloudOrganizationSignUpCommandTests Assert.NotNull(result.Organization); Assert.NotNull(result.OrganizationUser); - await sutProvider.GetDependency().Received(1).PurchaseOrganizationAsync( - Arg.Any(), - signup.PaymentMethodType.Value, - signup.PaymentToken, - Arg.Is(plan), - signup.AdditionalStorageGb, - signup.AdditionalSeats, - signup.PremiumAccessAddon, - signup.TaxInfo, - false, - signup.AdditionalSmSeats.GetValueOrDefault(), - signup.AdditionalServiceAccounts.GetValueOrDefault(), - signup.IsFromSecretsManagerTrial - ); + await sutProvider.GetDependency().Received(1).Finalize( + Arg.Is(sale => + sale.CustomerSetup.TokenizedPaymentSource.Type == signup.PaymentMethodType.Value && + sale.CustomerSetup.TokenizedPaymentSource.Token == signup.PaymentToken && + sale.CustomerSetup.TaxInformation.Country == signup.TaxInfo.BillingAddressCountry && + sale.CustomerSetup.TaxInformation.PostalCode == signup.TaxInfo.BillingAddressPostalCode && + sale.SubscriptionSetup.Plan == plan && + sale.SubscriptionSetup.PasswordManagerOptions.Seats == signup.AdditionalSeats && + sale.SubscriptionSetup.PasswordManagerOptions.Storage == signup.AdditionalStorageGb && + sale.SubscriptionSetup.SecretsManagerOptions.Seats == signup.AdditionalSmSeats && + sale.SubscriptionSetup.SecretsManagerOptions.ServiceAccounts == signup.AdditionalServiceAccounts)); } [Theory] @@ -242,4 +235,27 @@ public class CloudICloudOrganizationSignUpCommandTests () => sutProvider.Sut.SignUpOrganizationAsync(signup)); Assert.Contains("You can't subtract Machine Accounts!", exception.Message); } + + [Theory] + [BitAutoData] + public async Task SignUpAsync_Free_ExistingFreeOrgAdmin_ThrowsBadRequest( + SutProvider sutProvider) + { + // Arrange + var signup = new OrganizationSignup + { + Plan = PlanType.Free, + IsFromProvider = false, + Owner = new User { Id = Guid.NewGuid() } + }; + + sutProvider.GetDependency() + .GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id) + .Returns(1); + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpOrganizationAsync(signup)); + Assert.Contains("You can only be an admin of one free organization.", exception.Message); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirementQueryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirementQueryTests.cs new file mode 100644 index 0000000000..4c98353774 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirementQueryTests.cs @@ -0,0 +1,60 @@ +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.AdminConsole.Repositories; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies; + +[SutProviderCustomize] +public class PolicyRequirementQueryTests +{ + /// + /// Tests that the query correctly registers, retrieves and instantiates arbitrary IPolicyRequirements + /// according to their provided CreateRequirement delegate. + /// + [Theory, BitAutoData] + public async Task GetAsync_Works(Guid userId, Guid organizationId) + { + var policyRepository = Substitute.For(); + var factories = new List> + { + // In prod this cast is handled when the CreateRequirement delegate is registered in DI + (RequirementFactory)TestPolicyRequirement.Create + }; + + var sut = new PolicyRequirementQuery(policyRepository, factories); + policyRepository.GetPolicyDetailsByUserId(userId).Returns([ + new PolicyDetails + { + OrganizationId = organizationId + } + ]); + + var requirement = await sut.GetAsync(userId); + Assert.Equal(organizationId, requirement.OrganizationId); + } + + [Theory, BitAutoData] + public async Task GetAsync_ThrowsIfNoRequirementRegistered(Guid userId) + { + var policyRepository = Substitute.For(); + var sut = new PolicyRequirementQuery(policyRepository, []); + + var exception = await Assert.ThrowsAsync(() + => sut.GetAsync(userId)); + Assert.Contains("No Policy Requirement found", exception.Message); + } + + /// + /// Intentionally simplified PolicyRequirement that just holds the Policy.OrganizationId for us to assert against. + /// + private class TestPolicyRequirement : IPolicyRequirement + { + public Guid OrganizationId { get; init; } + public static TestPolicyRequirement Create(IEnumerable policyDetails) + => new() { OrganizationId = policyDetails.Single().OrganizationId }; + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs index 8c350d4161..0edc2b5973 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/TwoFactorAuthenticationPolicyValidatorTests.cs @@ -351,7 +351,7 @@ public class TwoFactorAuthenticationPolicyValidatorTests await sutProvider.GetDependency() .Received(1) - .SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization.DisplayName(), + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), "user3@test.com"); } } diff --git a/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs b/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs new file mode 100644 index 0000000000..48c3a143d4 --- /dev/null +++ b/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs @@ -0,0 +1,35 @@ +using Bit.Core.Models.Data; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Services; + +[SutProviderCustomize] +public class EventRepositoryHandlerTests +{ + [Theory, BitAutoData] + public async Task HandleEventAsync_WritesEventToIEventWriteService( + EventMessage eventMessage, + SutProvider sutProvider) + { + await sutProvider.Sut.HandleEventAsync(eventMessage); + await sutProvider.GetDependency().Received(1).CreateAsync( + Arg.Is(AssertHelper.AssertPropertyEqual(eventMessage)) + ); + } + + [Theory, BitAutoData] + public async Task HandleManyEventAsync_WritesEventsToIEventWriteService( + IEnumerable eventMessages, + SutProvider sutProvider) + { + await sutProvider.Sut.HandleManyEventsAsync(eventMessages); + await sutProvider.GetDependency().Received(1).CreateManyAsync( + Arg.Is(AssertHelper.AssertPropertyEqual(eventMessages)) + ); + } +} diff --git a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs index fc839030aa..52dd39e182 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs @@ -6,9 +6,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Entities; -using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Business.Tokenables; -using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Repositories; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Enums; @@ -21,6 +19,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.Services; using Bit.Core.Settings; @@ -42,8 +41,6 @@ using Xunit; using Organization = Bit.Core.AdminConsole.Entities.Organization; using OrganizationUser = Bit.Core.Entities.OrganizationUser; -#nullable enable - namespace Bit.Core.Test.Services; [SutProviderCustomize] @@ -1427,39 +1424,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("Seat limit has been reached. Contact your provider to purchase additional seats.", failureMessage); } - [Theory, PaidOrganizationCustomize, BitAutoData] - public async Task Delete_Success(Organization organization, SutProvider sutProvider) - { - var organizationRepository = sutProvider.GetDependency(); - var applicationCacheService = sutProvider.GetDependency(); - - await sutProvider.Sut.DeleteAsync(organization); - - await organizationRepository.Received().DeleteAsync(organization); - await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id); - } - - [Theory, PaidOrganizationCustomize, BitAutoData] - public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider sutProvider, - SsoConfig ssoConfig) - { - ssoConfig.Enabled = true; - ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector }); - var ssoConfigRepository = sutProvider.GetDependency(); - var organizationRepository = sutProvider.GetDependency(); - var applicationCacheService = sutProvider.GetDependency(); - - ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.DeleteAsync(organization)); - - Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message); - - await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default); - await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default); - } - private void RestoreRevokeUser_Setup( Organization organization, OrganizationUser? requestingOrganizationUser, @@ -1485,76 +1449,174 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationUser] OrganizationUser organizationUser, SutProvider sutProvider) { RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); await sutProvider.Sut.RevokeUserAsync(organizationUser, owner.Id); - await organizationUserRepository.Received().RevokeAsync(organizationUser.Id); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RevokeAsync(organizationUser.Id); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked); } + [Theory, BitAutoData] + public async Task RevokeUser_WithPushSyncOrgKeysOnRevokeRestoreEnabled_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser] OrganizationUser organizationUser, SutProvider sutProvider) + { + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) + .Returns(true); + + await sutProvider.Sut.RevokeUserAsync(organizationUser, owner.Id); + + await sutProvider.GetDependency() + .Received(1) + .RevokeAsync(organizationUser.Id); + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked); + await sutProvider.GetDependency() + .Received(1) + .PushSyncOrgKeysAsync(organizationUser.UserId!.Value); + } + [Theory, BitAutoData] public async Task RevokeUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider sutProvider) { RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); await sutProvider.Sut.RevokeUserAsync(organizationUser, eventSystemUser); - await organizationUserRepository.Received().RevokeAsync(organizationUser.Id); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RevokeAsync(organizationUser.Id); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, eventSystemUser); } + [Theory, BitAutoData] + public async Task RevokeUser_WithEventSystemUser_WithPushSyncOrgKeysOnRevokeRestoreEnabled_Success(Organization organization, [OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider sutProvider) + { + RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) + .Returns(true); + + await sutProvider.Sut.RevokeUserAsync(organizationUser, eventSystemUser); + + await sutProvider.GetDependency() + .Received(1) + .RevokeAsync(organizationUser.Id); + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, eventSystemUser); + await sutProvider.GetDependency() + .Received(1) + .PushSyncOrgKeysAsync(organizationUser.UserId!.Value); + } + [Theory, BitAutoData] public async Task RestoreUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider sutProvider) { RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id); - await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); } + [Theory, BitAutoData] + public async Task RestoreUser_WithPushSyncOrgKeysOnRevokeRestoreEnabled_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider sutProvider) + { + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) + .Returns(true); + + await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id); + + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); + await sutProvider.GetDependency() + .Received(1) + .PushSyncOrgKeysAsync(organizationUser.UserId!.Value); + } + [Theory, BitAutoData] public async Task RestoreUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider sutProvider) { RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); await sutProvider.Sut.RestoreUserAsync(organizationUser, eventSystemUser); - await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser); } + [Theory, BitAutoData] + public async Task RestoreUser_WithEventSystemUser_WithPushSyncOrgKeysOnRevokeRestoreEnabled_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider sutProvider) + { + RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.PushSyncOrgKeysOnRevokeRestore) + .Returns(true); + + await sutProvider.Sut.RestoreUserAsync(organizationUser, eventSystemUser); + + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Invited); + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser); + await sutProvider.GetDependency() + .Received(1) + .PushSyncOrgKeysAsync(organizationUser.UserId!.Value); + } + [Theory, BitAutoData] public async Task RestoreUser_RestoreThemselves_Fails(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider sutProvider) { organizationUser.UserId = owner.Id; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id)); Assert.Contains("you cannot restore yourself", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory] @@ -1566,17 +1628,21 @@ OrganizationUserInvite invite, SutProvider sutProvider) { restoringUser.Type = restoringUserType; RestoreRevokeUser_Setup(organization, restoringUser, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.RestoreUserAsync(organizationUser, restoringUser.Id)); Assert.Contains("only owners can restore other owners", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory] @@ -1588,17 +1654,21 @@ OrganizationUserInvite invite, SutProvider sutProvider) { organizationUser.Status = userStatus; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id)); Assert.Contains("already active", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1610,8 +1680,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) { organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); sutProvider.GetDependency() .AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) @@ -1626,9 +1694,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1645,8 +1719,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, false) }); RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) @@ -1661,9 +1733,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1675,8 +1753,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) { organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) @@ -1687,8 +1763,11 @@ OrganizationUserInvite invite, SutProvider sutProvider) await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id); - await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); } @@ -1703,10 +1782,10 @@ OrganizationUserInvite invite, SutProvider sutProvider) organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke secondOrganizationUser.UserId = organizationUser.UserId; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); - organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser }); + sutProvider.GetDependency() + .GetManyByUserAsync(organizationUser.UserId.Value) + .Returns(new[] { organizationUser, secondOrganizationUser }); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) .Returns(new[] @@ -1723,9 +1802,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com is not compliant with the single organization policy", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1739,11 +1824,8 @@ OrganizationUserInvite invite, SutProvider sutProvider) organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke secondOrganizationUser.UserId = organizationUser.UserId; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); - var twoFactorIsEnabledQuery = sutProvider.GetDependency(); - twoFactorIsEnabledQuery + sutProvider.GetDependency() .TwoFactorIsEnabledAsync(Arg.Is>(i => i.Contains(organizationUser.UserId.Value))) .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, true) }); @@ -1760,9 +1842,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1776,10 +1864,10 @@ OrganizationUserInvite invite, SutProvider sutProvider) organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke secondOrganizationUser.UserId = organizationUser.UserId; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); - organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser }); + sutProvider.GetDependency() + .GetManyByUserAsync(organizationUser.UserId.Value) + .Returns(new[] { organizationUser, secondOrganizationUser }); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) .Returns(new[] @@ -1803,9 +1891,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com is not compliant with the single organization and two-step login polciy", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1818,8 +1912,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) organizationUser.Email = null; RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) @@ -1834,9 +1926,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant()); - await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); - await eventService.DidNotReceiveWithAnyArgs() + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RestoreAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .PushSyncOrgKeysAsync(Arg.Any()); } [Theory, BitAutoData] @@ -1848,22 +1946,22 @@ OrganizationUserInvite invite, SutProvider sutProvider) { organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); - var organizationUserRepository = sutProvider.GetDependency(); - var eventService = sutProvider.GetDependency(); - var twoFactorIsEnabledQuery = sutProvider.GetDependency(); sutProvider.GetDependency() .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } }); - twoFactorIsEnabledQuery + sutProvider.GetDependency() .TwoFactorIsEnabledAsync(Arg.Is>(i => i.Contains(organizationUser.UserId.Value))) .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, true) }); await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id); - await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); - await eventService.Received() + await sutProvider.GetDependency() + .Received(1) + .RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); + await sutProvider.GetDependency() + .Received(1) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); } @@ -2180,4 +2278,107 @@ OrganizationUserInvite invite, SutProvider sutProvider) } ); } + + [Theory, BitAutoData] + public async Task RestoreUsers_Success(Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser1, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser2, + SutProvider sutProvider) + { + // Arrange + RestoreRevokeUser_Setup(organization, owner, orgUser1, sutProvider); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + var userService = Substitute.For(); + + orgUser1.Email = orgUser2.Email = null; // Mock that users were previously confirmed + orgUser1.OrganizationId = orgUser2.OrganizationId = organization.Id; + organizationUserRepository + .GetManyAsync(Arg.Is>(ids => ids.Contains(orgUser1.Id) && ids.Contains(orgUser2.Id))) + .Returns(new[] { orgUser1, orgUser2 }); + + twoFactorIsEnabledQuery + .TwoFactorIsEnabledAsync(Arg.Is>(ids => ids.Contains(orgUser1.UserId!.Value) && ids.Contains(orgUser2.UserId!.Value))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)> + { + (orgUser1.UserId!.Value, true), + (orgUser2.UserId!.Value, false) + }); + + // Act + var result = await sutProvider.Sut.RestoreUsersAsync(organization.Id, new[] { orgUser1.Id, orgUser2.Id }, owner.Id, userService); + + // Assert + Assert.Equal(2, result.Count); + Assert.All(result, r => Assert.Empty(r.Item2)); // No error messages + await organizationUserRepository + .Received(1) + .RestoreAsync(orgUser1.Id, OrganizationUserStatusType.Confirmed); + await organizationUserRepository + .Received(1) + .RestoreAsync(orgUser2.Id, OrganizationUserStatusType.Confirmed); + await eventService.Received(1) + .LogOrganizationUserEventAsync(orgUser1, EventType.OrganizationUser_Restored); + await eventService.Received(1) + .LogOrganizationUserEventAsync(orgUser2, EventType.OrganizationUser_Restored); + } + + [Theory, BitAutoData] + public async Task RestoreUsers_With2FAPolicy_BlocksNonCompliantUser(Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser1, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser2, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser3, + SutProvider sutProvider) + { + // Arrange + RestoreRevokeUser_Setup(organization, owner, orgUser1, sutProvider); + var organizationUserRepository = sutProvider.GetDependency(); + var userRepository = sutProvider.GetDependency(); + var policyService = sutProvider.GetDependency(); + var userService = Substitute.For(); + + orgUser1.Email = orgUser2.Email = null; + orgUser3.UserId = null; + orgUser3.Key = null; + orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = organization.Id; + organizationUserRepository + .GetManyAsync(Arg.Is>(ids => ids.Contains(orgUser1.Id) && ids.Contains(orgUser2.Id) && ids.Contains(orgUser3.Id))) + .Returns(new[] { orgUser1, orgUser2, orgUser3 }); + + userRepository.GetByIdAsync(orgUser2.UserId!.Value).Returns(new User { Email = "test@example.com" }); + + // Setup 2FA policy + policyService.GetPoliciesApplicableToUserAsync(Arg.Any(), PolicyType.TwoFactorAuthentication, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organization.Id, PolicyType = PolicyType.TwoFactorAuthentication } }); + + // User1 has 2FA, User2 doesn't + sutProvider.GetDependency() + .TwoFactorIsEnabledAsync(Arg.Is>(ids => ids.Contains(orgUser1.UserId!.Value) && ids.Contains(orgUser2.UserId!.Value))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)> + { + (orgUser1.UserId!.Value, true), + (orgUser2.UserId!.Value, false) + }); + + // Act + var result = await sutProvider.Sut.RestoreUsersAsync(organization.Id, new[] { orgUser1.Id, orgUser2.Id, orgUser3.Id }, owner.Id, userService); + + // Assert + Assert.Equal(3, result.Count); + Assert.Empty(result[0].Item2); // First user should succeed + Assert.Contains("two-step login", result[1].Item2); // Second user should fail + Assert.Empty(result[2].Item2); // Third user should succeed + await organizationUserRepository + .Received(1) + .RestoreAsync(orgUser1.Id, OrganizationUserStatusType.Confirmed); + await organizationUserRepository + .DidNotReceive() + .RestoreAsync(orgUser2.Id, Arg.Any()); + await organizationUserRepository + .Received(1) + .RestoreAsync(orgUser3.Id, OrganizationUserStatusType.Invited); + } } diff --git a/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs b/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs new file mode 100644 index 0000000000..6c7d7178c1 --- /dev/null +++ b/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs @@ -0,0 +1,85 @@ +using System.Net; +using System.Net.Http.Json; +using Bit.Core.Models.Data; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; +using Bit.Test.Common.MockedHttpClient; +using NSubstitute; +using Xunit; +using GlobalSettings = Bit.Core.Settings.GlobalSettings; + +namespace Bit.Core.Test.Services; + +[SutProviderCustomize] +public class WebhookEventHandlerTests +{ + private readonly MockedHttpMessageHandler _handler; + private HttpClient _httpClient; + + private const string _webhookUrl = "http://localhost/test/event"; + + public WebhookEventHandlerTests() + { + _handler = new MockedHttpMessageHandler(); + _handler.Fallback + .WithStatusCode(HttpStatusCode.OK) + .WithContent(new StringContent("testtest")); + _httpClient = _handler.ToHttpClient(); + } + + public SutProvider GetSutProvider() + { + var clientFactory = Substitute.For(); + clientFactory.CreateClient(WebhookEventHandler.HttpClientName).Returns(_httpClient); + + var globalSettings = new GlobalSettings(); + globalSettings.EventLogging.WebhookUrl = _webhookUrl; + + return new SutProvider() + .SetDependency(globalSettings) + .SetDependency(clientFactory) + .Create(); + } + + [Theory, BitAutoData] + public async Task HandleEventAsync_PostsEventToUrl(EventMessage eventMessage) + { + var sutProvider = GetSutProvider(); + + await sutProvider.Sut.HandleEventAsync(eventMessage); + sutProvider.GetDependency().Received(1).CreateClient( + Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName)) + ); + + Assert.Single(_handler.CapturedRequests); + var request = _handler.CapturedRequests[0]; + Assert.NotNull(request); + var returned = await request.Content.ReadFromJsonAsync(); + + Assert.Equal(HttpMethod.Post, request.Method); + Assert.Equal(_webhookUrl, request.RequestUri.ToString()); + AssertHelper.AssertPropertyEqual(eventMessage, returned, new[] { "IdempotencyId" }); + } + + [Theory, BitAutoData] + public async Task HandleEventManyAsync_PostsEventsToUrl(IEnumerable eventMessages) + { + var sutProvider = GetSutProvider(); + + await sutProvider.Sut.HandleManyEventsAsync(eventMessages); + sutProvider.GetDependency().Received(1).CreateClient( + Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName)) + ); + + Assert.Single(_handler.CapturedRequests); + var request = _handler.CapturedRequests[0]; + Assert.NotNull(request); + var returned = request.Content.ReadFromJsonAsAsyncEnumerable(); + + Assert.Equal(HttpMethod.Post, request.Method); + Assert.Equal(_webhookUrl, request.RequestUri.ToString()); + AssertHelper.AssertPropertyEqual(eventMessages, returned, new[] { "IdempotencyId" }); + } +} diff --git a/test/Core.Test/Auth/Models/Business/Tokenables/OrgUserInviteTokenableTests.cs b/test/Core.Test/Auth/Models/Business/Tokenables/OrgUserInviteTokenableTests.cs index aeeda206fa..866cdbfe29 100644 --- a/test/Core.Test/Auth/Models/Business/Tokenables/OrgUserInviteTokenableTests.cs +++ b/test/Core.Test/Auth/Models/Business/Tokenables/OrgUserInviteTokenableTests.cs @@ -109,7 +109,7 @@ public class OrgUserInviteTokenableTests [Theory] [InlineData(null)] [InlineData("")] - public void Valid_NullOrEmptyOrgUserEmail_ReturnsFalse(string email) + public void Valid_NullOrEmptyOrgUserEmail_ReturnsFalse(string? email) { var token = new OrgUserInviteTokenable { diff --git a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs index 4e42125dce..8feef2facc 100644 --- a/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs +++ b/test/Core.Test/Auth/Services/AuthRequestServiceTests.cs @@ -7,10 +7,13 @@ using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -227,6 +230,14 @@ public class AuthRequestServiceTests await sutProvider.GetDependency() .Received() .CreateAsync(createdAuthRequest); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .SendDeviceApprovalRequestedNotificationEmailAsync( + Arg.Any>(), + Arg.Any(), + Arg.Any(), + Arg.Any()); } /// @@ -321,6 +332,141 @@ public class AuthRequestServiceTests await sutProvider.GetDependency() .Received(1) .LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .SendDeviceApprovalRequestedNotificationEmailAsync( + Arg.Any>(), + Arg.Any(), + Arg.Any(), + Arg.Any()); + } + + [Theory, BitAutoData] + public async Task CreateAuthRequestAsync_AdminApproval_WithAdminNotifications_CreatesForEachOrganization_SendsEmails( + SutProvider sutProvider, + AuthRequestCreateRequestModel createModel, + User user, + OrganizationUser organizationUser1, + OrganizationUserUserDetails admin1, + OrganizationUserUserDetails customUser1, + OrganizationUser organizationUser2, + OrganizationUserUserDetails admin2, + OrganizationUserUserDetails admin3, + OrganizationUserUserDetails customUser2) + { + createModel.Type = AuthRequestType.AdminApproval; + user.Email = createModel.Email; + organizationUser1.UserId = user.Id; + organizationUser2.UserId = user.Id; + customUser1.Permissions = CoreHelpers.ClassToJsonData(new Permissions + { + ManageResetPassword = false, + }); + customUser2.Permissions = CoreHelpers.ClassToJsonData(new Permissions + { + ManageResetPassword = true, + }); + + sutProvider.GetDependency() + .IsEnabled(FeatureFlagKeys.DeviceApprovalRequestAdminNotifications) + .Returns(true); + + sutProvider.GetDependency() + .GetByEmailAsync(user.Email) + .Returns(user); + + sutProvider.GetDependency() + .DeviceType + .Returns(DeviceType.ChromeExtension); + + sutProvider.GetDependency() + .UserId + .Returns(user.Id); + + sutProvider.GetDependency() + .PasswordlessAuth.KnownDevicesOnly + .Returns(false); + + + sutProvider.GetDependency() + .GetManyByUserAsync(user.Id) + .Returns(new List + { + organizationUser1, + organizationUser2, + }); + + sutProvider.GetDependency() + .GetManyByMinimumRoleAsync(organizationUser1.OrganizationId, OrganizationUserType.Admin) + .Returns( + [ + admin1, + ]); + + sutProvider.GetDependency() + .GetManyDetailsByRoleAsync(organizationUser1.OrganizationId, OrganizationUserType.Custom) + .Returns( + [ + customUser1, + ]); + + sutProvider.GetDependency() + .GetManyByMinimumRoleAsync(organizationUser2.OrganizationId, OrganizationUserType.Admin) + .Returns( + [ + admin2, + admin3, + ]); + + sutProvider.GetDependency() + .GetManyDetailsByRoleAsync(organizationUser2.OrganizationId, OrganizationUserType.Custom) + .Returns( + [ + customUser2, + ]); + + sutProvider.GetDependency() + .CreateAsync(Arg.Any()) + .Returns(c => c.ArgAt(0)); + + var authRequest = await sutProvider.Sut.CreateAuthRequestAsync(createModel); + + Assert.Equal(organizationUser1.OrganizationId, authRequest.OrganizationId); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Is(o => o.OrganizationId == organizationUser1.OrganizationId)); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Is(o => o.OrganizationId == organizationUser2.OrganizationId)); + + await sutProvider.GetDependency() + .Received(2) + .CreateAsync(Arg.Any()); + + await sutProvider.GetDependency() + .Received(1) + .LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval); + + await sutProvider.GetDependency() + .Received(1) + .SendDeviceApprovalRequestedNotificationEmailAsync( + Arg.Is>(emails => emails.Count() == 1 && emails.Contains(admin1.Email)), + organizationUser1.OrganizationId, + user.Email, + user.Name); + + await sutProvider.GetDependency() + .Received(1) + .SendDeviceApprovalRequestedNotificationEmailAsync( + Arg.Is>(emails => emails.Count() == 3 && + emails.Contains(admin2.Email) && emails.Contains(admin3.Email) && + emails.Contains(customUser2.Email)), + organizationUser2.OrganizationId, + user.Email, + user.Name); } /// diff --git a/test/Core.Test/AutoFixture/QueueClientFixtures.cs b/test/Core.Test/AutoFixture/QueueClientFixtures.cs new file mode 100644 index 0000000000..2a722f3853 --- /dev/null +++ b/test/Core.Test/AutoFixture/QueueClientFixtures.cs @@ -0,0 +1,35 @@ +#nullable enable +using AutoFixture; +using AutoFixture.Kernel; +using Azure.Storage.Queues; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; + +namespace Bit.Core.Test.AutoFixture; + +public class QueueClientBuilder : ISpecimenBuilder +{ + public object Create(object request, ISpecimenContext context) + { + var type = request as Type; + if (type == typeof(QueueClient)) + { + return Substitute.For(); + } + + return new NoSpecimen(); + } +} + +public class QueueClientCustomizeAttribute : BitCustomizeAttribute +{ + public override ICustomization GetCustomization() => new QueueClientFixtures(); +} + +public class QueueClientFixtures : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new QueueClientBuilder()); + } +} diff --git a/test/Core.Test/Core.Test.csproj b/test/Core.Test/Core.Test.csproj index 4858afe54d..baace97710 100644 --- a/test/Core.Test/Core.Test.csproj +++ b/test/Core.Test/Core.Test.csproj @@ -2,6 +2,8 @@ false Bit.Core.Test + + $(WarningsNotAsErrors);CS4014 diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs new file mode 100644 index 0000000000..41a6c25bf2 --- /dev/null +++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs @@ -0,0 +1,94 @@ +#nullable enable +using System.ComponentModel.DataAnnotations; +using System.Text.Json; +using Bit.Core.Enums; +using Bit.Core.Models.Api; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Models.Api.Request; + +public class PushSendRequestModelTests +{ + [Theory] + [InlineData(null, null)] + [InlineData(null, "")] + [InlineData(null, " ")] + [InlineData("", null)] + [InlineData(" ", null)] + [InlineData("", "")] + [InlineData(" ", " ")] + public void Validate_UserIdOrganizationIdNullOrEmpty_Invalid(string? userId, string? organizationId) + { + var model = new PushSendRequestModel + { + UserId = userId, + OrganizationId = organizationId, + Type = PushType.SyncCiphers, + Payload = "test" + }; + + var results = Validate(model); + + Assert.Single(results); + Assert.Contains(results, result => result.ErrorMessage == "UserId or OrganizationId is required."); + } + + [Theory] + [BitAutoData("Payload")] + [BitAutoData("Type")] + public void Validate_RequiredFieldNotProvided_Invalid(string requiredField) + { + var model = new PushSendRequestModel + { + UserId = Guid.NewGuid().ToString(), + OrganizationId = Guid.NewGuid().ToString(), + Type = PushType.SyncCiphers, + Payload = "test" + }; + + var dictionary = new Dictionary(); + foreach (var property in model.GetType().GetProperties()) + { + if (property.Name == requiredField) + { + continue; + } + + dictionary[property.Name] = property.GetValue(model); + } + + var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull); + var jsonException = + Assert.Throws(() => JsonSerializer.Deserialize(serialized)); + Assert.Contains($"missing required properties, including the following: {requiredField}", + jsonException.Message); + } + + [Fact] + public void Validate_AllFieldsPresent_Valid() + { + var model = new PushSendRequestModel + { + UserId = Guid.NewGuid().ToString(), + OrganizationId = Guid.NewGuid().ToString(), + Type = PushType.SyncCiphers, + Payload = "test payload", + Identifier = Guid.NewGuid().ToString(), + ClientType = ClientType.All, + DeviceId = Guid.NewGuid().ToString() + }; + + var results = Validate(model); + + Assert.Empty(results); + } + + private static List Validate(PushSendRequestModel model) + { + var results = new List(); + Validator.TryValidateObject(model, new ValidationContext(model), results, true); + return results; + } +} diff --git a/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs index 4f5842d1c7..3256f2f9cb 100644 --- a/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/CreateNotificationCommandTest.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -40,6 +41,12 @@ public class CreateNotificationCommandTest Setup(sutProvider, notification, authorized: false); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -55,5 +62,11 @@ public class CreateNotificationCommandTest Assert.Equal(notification, newNotification); Assert.Equal(DateTime.UtcNow, notification.CreationDate, TimeSpan.FromMinutes(1)); Assert.Equal(notification.CreationDate, notification.RevisionDate); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationAsync(newNotification); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } } diff --git a/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs index 8dc8524926..78aaaba18f 100644 --- a/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/CreateNotificationStatusCommandTest.cs @@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -50,6 +51,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification: null, notificationStatus, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -61,6 +68,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification, notificationStatus, authorizedNotification: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -72,6 +85,12 @@ public class CreateNotificationStatusCommandTest Setup(sutProvider, notification, notificationStatus, true, authorizedCreate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(notificationStatus)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -85,5 +104,11 @@ public class CreateNotificationStatusCommandTest var newNotificationStatus = await sutProvider.Sut.CreateAsync(notificationStatus); Assert.Equal(notificationStatus, newNotificationStatus); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, notificationStatus); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } } diff --git a/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs index a5bb20423c..f1d23b5f18 100644 --- a/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/MarkNotificationDeletedCommandTest.cs @@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -63,6 +64,12 @@ public class MarkNotificationDeletedCommandTest Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -74,6 +81,12 @@ public class MarkNotificationDeletedCommandTest Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -86,6 +99,12 @@ public class MarkNotificationDeletedCommandTest true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -98,6 +117,12 @@ public class MarkNotificationDeletedCommandTest authorizedCreate: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -110,6 +135,12 @@ public class MarkNotificationDeletedCommandTest authorizedUpdate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -119,13 +150,25 @@ public class MarkNotificationDeletedCommandTest Guid notificationId, Guid userId, Notification notification) { Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); + var expectedNotificationStatus = new NotificationStatus + { + NotificationId = notificationId, + UserId = userId, + ReadDate = null, + DeletedDate = DateTime.UtcNow + }; await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(ns => - ns.NotificationId == notificationId && ns.UserId == userId && !ns.ReadDate.HasValue && - ns.DeletedDate.HasValue && DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); + .CreateAsync(Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -134,18 +177,30 @@ public class MarkNotificationDeletedCommandTest SutProvider sutProvider, Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) { - var deletedDate = notificationStatus.DeletedDate; - Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Is(ns => - ns.Equals(notificationStatus) && - ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && - ns.ReadDate == notificationStatus.ReadDate && ns.DeletedDate != deletedDate && - ns.DeletedDate.HasValue && - DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); + .UpdateAsync(Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + } + + private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus, + NotificationStatus? actualNotificationStatus) + { + Assert.NotNull(actualNotificationStatus); + Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId); + Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId); + Assert.Equal(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate); + Assert.NotEqual(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate); + Assert.NotNull(actualNotificationStatus.DeletedDate); + Assert.Equal(DateTime.UtcNow, actualNotificationStatus.DeletedDate.Value, TimeSpan.FromMinutes(1)); } } diff --git a/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs index f80234c075..481a973d32 100644 --- a/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/MarkNotificationReadCommandTest.cs @@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization; using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -63,6 +64,12 @@ public class MarkNotificationReadCommandTest Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -74,6 +81,12 @@ public class MarkNotificationReadCommandTest Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -86,6 +99,12 @@ public class MarkNotificationReadCommandTest true, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -98,6 +117,12 @@ public class MarkNotificationReadCommandTest authorizedCreate: false, true); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -110,6 +135,12 @@ public class MarkNotificationReadCommandTest authorizedUpdate: false); await Assert.ThrowsAsync(() => sutProvider.Sut.MarkReadAsync(notificationId)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -119,13 +150,25 @@ public class MarkNotificationReadCommandTest Guid notificationId, Guid userId, Notification notification) { Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); + var expectedNotificationStatus = new NotificationStatus + { + NotificationId = notificationId, + UserId = userId, + ReadDate = DateTime.UtcNow, + DeletedDate = null + }; await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(ns => - ns.NotificationId == notificationId && ns.UserId == userId && !ns.DeletedDate.HasValue && - ns.ReadDate.HasValue && DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); + .CreateAsync(Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(expectedNotificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); } [Theory] @@ -134,18 +177,30 @@ public class MarkNotificationReadCommandTest SutProvider sutProvider, Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) { - var readDate = notificationStatus.ReadDate; - Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Is(ns => - ns.Equals(notificationStatus) && - ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && - ns.DeletedDate == notificationStatus.DeletedDate && ns.ReadDate != readDate && - ns.ReadDate.HasValue && - DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); + .UpdateAsync(Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationStatusAsync(notification, + Arg.Do(ns => AssertNotificationStatus(notificationStatus, ns))); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + } + + private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus, + NotificationStatus? actualNotificationStatus) + { + Assert.NotNull(actualNotificationStatus); + Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId); + Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId); + Assert.NotEqual(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate); + Assert.NotNull(actualNotificationStatus.ReadDate); + Assert.Equal(DateTime.UtcNow, actualNotificationStatus.ReadDate.Value, TimeSpan.FromMinutes(1)); + Assert.Equal(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate); } } diff --git a/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs b/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs index 976d1d77a3..406347e0df 100644 --- a/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs +++ b/test/Core.Test/NotificationCenter/Commands/UpdateNotificationCommandTest.cs @@ -7,6 +7,7 @@ using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -45,6 +46,12 @@ public class UpdateNotificationCommandTest Setup(sutProvider, notification.Id, notification: null, true); await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -56,6 +63,12 @@ public class UpdateNotificationCommandTest Setup(sutProvider, notification.Id, notification, authorized: false); await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(notification)); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } [Theory] @@ -91,5 +104,11 @@ public class UpdateNotificationCommandTest n.Priority == notificationToUpdate.Priority && n.ClientType == notificationToUpdate.ClientType && n.Title == notificationToUpdate.Title && n.Body == notificationToUpdate.Body && DateTime.UtcNow - n.RevisionDate < TimeSpan.FromMinutes(1))); + await sutProvider.GetDependency() + .Received(1) + .PushNotificationAsync(notification); + await sutProvider.GetDependency() + .Received(0) + .PushNotificationStatusAsync(Arg.Any(), Arg.Any()); } } diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index c26fc23460..2b8ff88dc1 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -1,42 +1,401 @@ -using Bit.Core.NotificationHub; -using Bit.Core.Platform.Push; +#nullable enable +using System.Text.Json; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Models.Data; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationHub; using Bit.Core.Repositories; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; +using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; namespace Bit.Core.Test.NotificationHub; +[SutProviderCustomize] +[NotificationStatusCustomize] public class NotificationHubPushNotificationServiceTests { - private readonly NotificationHubPushNotificationService _sut; - - private readonly IInstallationDeviceRepository _installationDeviceRepository; - private readonly INotificationHubPool _notificationHubPool; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ILogger _logger; - - public NotificationHubPushNotificationServiceTests() + [Theory] + [BitAutoData] + [NotificationCustomize] + public async Task PushNotificationAsync_Global_NotSent( + SutProvider sutProvider, Notification notification) { - _installationDeviceRepository = Substitute.For(); - _httpContextAccessor = Substitute.For(); - _notificationHubPool = Substitute.For(); - _logger = Substitute.For>(); + await sutProvider.Sut.PushNotificationAsync(notification); - _sut = new NotificationHubPushNotificationService( - _installationDeviceRepository, - _notificationHubPool, - _httpContextAccessor, - _logger - ); + await sutProvider.GetDependency() + .Received(0) + .AllClients + .Received(0) + .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact(Skip = "Needs additional work")] - public void ServiceExists() + [Theory] + [BitAutoData(false)] + [BitAutoData(true)] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( + bool organizationIdNull, SutProvider sutProvider, + Notification notification) { - Assert.NotNull(_sut); + if (organizationIdNull) + { + notification.OrganizationId = null; + } + + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload_userId:{notification.UserId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + notification.OrganizationId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( + SutProvider sutProvider, Notification notification) + { + notification.UserId = null; + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( + ClientType clientType, SutProvider sutProvider, + Notification notification) + { + notification.UserId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, null); + + await sutProvider.Sut.PushNotificationAsync(notification); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize] + public async Task PushNotificationStatusAsync_Global_NotSent( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) + { + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency() + .Received(0) + .AllClients + .Received(0) + .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(false)] + [BitAutoData(true)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedClientTypeAll_SentToUser( + bool organizationIdNull, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + if (organizationIdNull) + { + notification.OrganizationId = null; + } + + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.OrganizationId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + [NotificationCustomize(false)] + public async Task PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) + { + notification.UserId = null; + notification.ClientType = ClientType.All; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Web)] + [BitAutoData(ClientType.Mobile)] + [NotificationCustomize(false)] + public async Task + PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( + ClientType clientType, SutProvider sutProvider, + Notification notification, NotificationStatus notificationStatus) + { + notification.UserId = null; + notification.ClientType = clientType; + var expectedNotification = ToNotificationPushNotification(notification, notificationStatus); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationStatus, + expectedNotification, + $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData([null])] + [BitAutoData(ClientType.All)] + public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, + SutProvider sutProvider, Guid userId, PushType pushType, string payload, + string identifier) + { + await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, + clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Mobile)] + [BitAutoData(ClientType.Web)] + public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, + SutProvider sutProvider, Guid userId, PushType pushType, string payload, + string identifier) + { + await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null, + clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload_userId:{userId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData([null])] + [BitAutoData(ClientType.All)] + public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, + SutProvider sutProvider, Guid organizationId, PushType pushType, + string payload, string identifier) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, + null, clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(ClientType.Browser)] + [BitAutoData(ClientType.Desktop)] + [BitAutoData(ClientType.Mobile)] + [BitAutoData(ClientType.Web)] + public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( + ClientType clientType, SutProvider sutProvider, Guid organizationId, + PushType pushType, string payload, string identifier) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier, + null, clientType); + + await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, + $"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); + await sutProvider.GetDependency() + .Received(0) + .UpsertAsync(Arg.Any()); + } + + private static NotificationPushNotification ToNotificationPushNotification(Notification notification, + NotificationStatus? notificationStatus) => + new() + { + Id = notification.Id, + Priority = notification.Priority, + Global = notification.Global, + ClientType = notification.ClientType, + UserId = notification.UserId, + OrganizationId = notification.OrganizationId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus?.ReadDate, + DeletedDate = notificationStatus?.DeletedDate + }; + + private static async Task AssertSendTemplateNotificationAsync( + SutProvider sutProvider, PushType type, object payload, string tag) + { + await sutProvider.GetDependency() + .Received(1) + .AllClients + .Received(1) + .SendTemplateNotificationAsync( + Arg.Is>(dictionary => MatchingSendPayload(dictionary, type, payload)), + tag); + } + + private static bool MatchingSendPayload(IDictionary dictionary, PushType type, object payload) + { + return dictionary.ContainsKey("type") && dictionary["type"].Equals(((byte)type).ToString()) && + dictionary.ContainsKey("payload") && dictionary["payload"].Equals(JsonSerializer.Serialize(payload)); } } diff --git a/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs index c5851f2791..d51df9c882 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushRegistrationServiceTests.cs @@ -1,44 +1,290 @@ -using Bit.Core.NotificationHub; -using Bit.Core.Repositories; -using Bit.Core.Settings; -using Microsoft.Extensions.Logging; +#nullable enable +using Bit.Core.Enums; +using Bit.Core.NotificationHub; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Azure.NotificationHubs; using NSubstitute; using Xunit; namespace Bit.Core.Test.NotificationHub; +[SutProviderCustomize] public class NotificationHubPushRegistrationServiceTests { - private readonly NotificationHubPushRegistrationService _sut; - - private readonly IInstallationDeviceRepository _installationDeviceRepository; - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - private readonly GlobalSettings _globalSettings; - private readonly INotificationHubPool _notificationHubPool; - - public NotificationHubPushRegistrationServiceTests() + [Theory] + [BitAutoData([null])] + [BitAutoData("")] + [BitAutoData(" ")] + public async Task CreateOrUpdateRegistrationAsync_PushTokenNullOrEmpty_InstallationNotCreated(string? pushToken, + SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, + Guid organizationId) { - _installationDeviceRepository = Substitute.For(); - _serviceProvider = Substitute.For(); - _logger = Substitute.For>(); - _globalSettings = new GlobalSettings(); - _notificationHubPool = Substitute.For(); + await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), + identifier.ToString(), DeviceType.Android, [organizationId.ToString()]); - _sut = new NotificationHubPushRegistrationService( - _installationDeviceRepository, - _globalSettings, - _notificationHubPool, - _serviceProvider, - _logger - ); + sutProvider.GetDependency() + .Received(0) + .ClientFor(deviceId); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact(Skip = "Needs additional work")] - public void ServiceExists() + [Theory] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + [BitAutoData(true, true)] + public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroid_InstallationCreated(bool identifierNull, + bool partOfOrganizationId, SutProvider sutProvider, Guid deviceId, + Guid userId, Guid? identifier, Guid organizationId) { - Assert.NotNull(_sut); + var notificationHubClient = Substitute.For(); + sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); + + var pushToken = "test push token"; + + await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), + identifierNull ? null : identifier.ToString(), DeviceType.Android, + partOfOrganizationId ? [organizationId.ToString()] : []); + + sutProvider.GetDependency() + .Received(1) + .ClientFor(deviceId); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => + installation.InstallationId == deviceId.ToString() && + installation.PushChannel == pushToken && + installation.Platform == NotificationPlatform.FcmV1 && + installation.Tags.Contains($"userId:{userId}") && + installation.Tags.Contains("clientType:Mobile") && + (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && + (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && + installation.Templates.Count == 3)); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:payload", + "{\"message\":{\"data\":{\"type\":\"$(type)\",\"payload\":\"$(payload)\"}}}", + new List + { + "template:payload", + $"template:payload_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:message", + "{\"message\":{\"data\":{\"type\":\"$(type)\"},\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}", + new List + { + "template:message", + $"template:message_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:badgeMessage", + "{\"message\":{\"data\":{\"type\":\"$(type)\"},\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}}", + new List + { + "template:badgeMessage", + $"template:badgeMessage_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + } + + [Theory] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + [BitAutoData(true, true)] + public async Task CreateOrUpdateRegistrationAsync_DeviceTypeIOS_InstallationCreated(bool identifierNull, + bool partOfOrganizationId, SutProvider sutProvider, Guid deviceId, + Guid userId, Guid identifier, Guid organizationId) + { + var notificationHubClient = Substitute.For(); + sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); + + var pushToken = "test push token"; + + await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), + identifierNull ? null : identifier.ToString(), DeviceType.iOS, + partOfOrganizationId ? [organizationId.ToString()] : []); + + sutProvider.GetDependency() + .Received(1) + .ClientFor(deviceId); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => + installation.InstallationId == deviceId.ToString() && + installation.PushChannel == pushToken && + installation.Platform == NotificationPlatform.Apns && + installation.Tags.Contains($"userId:{userId}") && + installation.Tags.Contains("clientType:Mobile") && + (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && + (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && + installation.Templates.Count == 3)); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:payload", + "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"},\"aps\":{\"content-available\":1}}", + new List + { + "template:payload", + $"template:payload_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:message", + "{\"data\":{\"type\":\"#(type)\"},\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}", + new List + { + "template:message", + $"template:message_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:badgeMessage", + "{\"data\":{\"type\":\"#(type)\"},\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}", + new List + { + "template:badgeMessage", + $"template:badgeMessage_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + } + + [Theory] + [BitAutoData(false, false)] + [BitAutoData(false, true)] + [BitAutoData(true, false)] + [BitAutoData(true, true)] + public async Task CreateOrUpdateRegistrationAsync_DeviceTypeAndroidAmazon_InstallationCreated(bool identifierNull, + bool partOfOrganizationId, SutProvider sutProvider, Guid deviceId, + Guid userId, Guid identifier, Guid organizationId) + { + var notificationHubClient = Substitute.For(); + sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); + + var pushToken = "test push token"; + + await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), + identifierNull ? null : identifier.ToString(), DeviceType.AndroidAmazon, + partOfOrganizationId ? [organizationId.ToString()] : []); + + sutProvider.GetDependency() + .Received(1) + .ClientFor(deviceId); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => + installation.InstallationId == deviceId.ToString() && + installation.PushChannel == pushToken && + installation.Platform == NotificationPlatform.Adm && + installation.Tags.Contains($"userId:{userId}") && + installation.Tags.Contains("clientType:Mobile") && + (identifierNull || installation.Tags.Contains($"deviceIdentifier:{identifier}")) && + (!partOfOrganizationId || installation.Tags.Contains($"organizationId:{organizationId}")) && + installation.Templates.Count == 3)); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:payload", + "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}", + new List + { + "template:payload", + $"template:payload_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:payload_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:message", + "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}", + new List + { + "template:message", + $"template:message_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:message_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => MatchingInstallationTemplate( + installation.Templates, "template:badgeMessage", + "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}", + new List + { + "template:badgeMessage", + $"template:badgeMessage_userId:{userId}", + "clientType:Mobile", + identifierNull ? null : $"template:badgeMessage_deviceIdentifier:{identifier}", + partOfOrganizationId ? $"organizationId:{organizationId}" : null, + }))); + } + + [Theory] + [BitAutoData(DeviceType.ChromeBrowser)] + [BitAutoData(DeviceType.ChromeExtension)] + [BitAutoData(DeviceType.MacOsDesktop)] + public async Task CreateOrUpdateRegistrationAsync_DeviceTypeNotMobile_InstallationCreated(DeviceType deviceType, + SutProvider sutProvider, Guid deviceId, Guid userId, Guid identifier, + Guid organizationId) + { + var notificationHubClient = Substitute.For(); + sutProvider.GetDependency().ClientFor(Arg.Any()).Returns(notificationHubClient); + + var pushToken = "test push token"; + + await sutProvider.Sut.CreateOrUpdateRegistrationAsync(pushToken, deviceId.ToString(), userId.ToString(), + identifier.ToString(), deviceType, [organizationId.ToString()]); + + sutProvider.GetDependency() + .Received(1) + .ClientFor(deviceId); + await notificationHubClient + .Received(1) + .CreateOrUpdateInstallationAsync(Arg.Is(installation => + installation.InstallationId == deviceId.ToString() && + installation.PushChannel == pushToken && + installation.Tags.Contains($"userId:{userId}") && + installation.Tags.Contains($"clientType:{DeviceTypes.ToClientType(deviceType)}") && + installation.Tags.Contains($"deviceIdentifier:{identifier}") && + installation.Tags.Contains($"organizationId:{organizationId}") && + installation.Templates.Count == 0)); + } + + private static bool MatchingInstallationTemplate(IDictionary templates, string key, + string body, List tags) + { + var tagsNoNulls = tags.FindAll(tag => tag != null); + return templates.ContainsKey(key) && templates[key].Body == body && + templates[key].Tags.Count == tagsNoNulls.Count && + templates[key].Tags.All(tagsNoNulls.Contains); } } diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs index 0f47b6c921..2965a2f03d 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpgradeOrganizationPlanCommandTests.cs @@ -139,7 +139,7 @@ public class UpgradeOrganizationPlanCommandTests && o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts)); Assert.True(result.Item1); - Assert.NotNull(result.Item2); + Assert.Null(result.Item2); } [Theory, FreeOrganizationUpgradeCustomize] diff --git a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs index 85ce5a79ac..22161924ea 100644 --- a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs @@ -1,33 +1,98 @@ -using Bit.Core.Settings; +#nullable enable +using System.Text.Json; +using Azure.Storage.Queues; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Test.AutoFixture; +using Bit.Core.Test.AutoFixture.CurrentContextFixtures; +using Bit.Core.Test.NotificationCenter.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using NSubstitute; using Xunit; namespace Bit.Core.Platform.Push.Internal.Test; +[QueueClientCustomize] +[SutProviderCustomize] public class AzureQueuePushNotificationServiceTests { - private readonly AzureQueuePushNotificationService _sut; - - private readonly GlobalSettings _globalSettings; - private readonly IHttpContextAccessor _httpContextAccessor; - - public AzureQueuePushNotificationServiceTests() + [Theory] + [BitAutoData] + [NotificationCustomize] + [CurrentContextCustomize] + public async Task PushNotificationAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, Guid deviceIdentifier, + ICurrentContext currentContext) { - _globalSettings = new GlobalSettings(); - _httpContextAccessor = Substitute.For(); + currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); + sutProvider.GetDependency().HttpContext!.RequestServices + .GetService(Arg.Any()).Returns(currentContext); - _sut = new AzureQueuePushNotificationService( - _globalSettings, - _httpContextAccessor - ); + await sutProvider.Sut.PushNotificationAsync(notification); + + await sutProvider.GetDependency().Received(1) + .SendMessageAsync(Arg.Is(message => + MatchMessage(PushType.SyncNotification, message, + new NotificationPushNotificationEquals(notification, null), + deviceIdentifier.ToString()))); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact(Skip = "Needs additional work")] - public void ServiceExists() + [Theory] + [BitAutoData] + [NotificationCustomize] + [NotificationStatusCustomize] + [CurrentContextCustomize] + public async Task PushNotificationStatusAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, Guid deviceIdentifier, + ICurrentContext currentContext, NotificationStatus notificationStatus) { - Assert.NotNull(_sut); + currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); + sutProvider.GetDependency().HttpContext!.RequestServices + .GetService(Arg.Any()).Returns(currentContext); + + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency().Received(1) + .SendMessageAsync(Arg.Is(message => + MatchMessage(PushType.SyncNotificationStatus, message, + new NotificationPushNotificationEquals(notification, notificationStatus), + deviceIdentifier.ToString()))); + } + + private static bool MatchMessage(PushType pushType, string message, IEquatable expectedPayloadEquatable, + string contextId) + { + var pushNotificationData = JsonSerializer.Deserialize>(message); + return pushNotificationData != null && + pushNotificationData.Type == pushType && + expectedPayloadEquatable.Equals(pushNotificationData.Payload) && + pushNotificationData.ContextId == contextId; + } + + private class NotificationPushNotificationEquals(Notification notification, NotificationStatus? notificationStatus) + : IEquatable + { + public bool Equals(NotificationPushNotification? other) + { + return other != null && + other.Id == notification.Id && + other.Priority == notification.Priority && + other.Global == notification.Global && + other.ClientType == notification.ClientType && + other.UserId.HasValue == notification.UserId.HasValue && + other.UserId == notification.UserId && + other.OrganizationId.HasValue == notification.OrganizationId.HasValue && + other.OrganizationId == notification.OrganizationId && + other.Title == notification.Title && + other.Body == notification.Body && + other.CreationDate == notification.CreationDate && + other.RevisionDate == notification.RevisionDate && + other.ReadDate == notificationStatus?.ReadDate && + other.DeletedDate == notificationStatus?.DeletedDate; + } } } diff --git a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs index 021aa7f2cc..08dfd0a5c0 100644 --- a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs @@ -1,44 +1,78 @@ -using AutoFixture; +#nullable enable +using Bit.Core.Enums; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Test.Common.AutoFixture; -using Microsoft.Extensions.Logging; +using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -using GlobalSettingsCustomization = Bit.Test.Common.AutoFixture.GlobalSettings; namespace Bit.Core.Platform.Push.Internal.Test; +[SutProviderCustomize] public class MultiServicePushNotificationServiceTests { - private readonly MultiServicePushNotificationService _sut; - - private readonly ILogger _logger; - private readonly ILogger _relayLogger; - private readonly ILogger _hubLogger; - private readonly IEnumerable _services; - private readonly Settings.GlobalSettings _globalSettings; - - public MultiServicePushNotificationServiceTests() + [Theory] + [BitAutoData] + [NotificationCustomize] + public async Task PushNotificationAsync_Notification_Sent( + SutProvider sutProvider, Notification notification) { - _logger = Substitute.For>(); - _relayLogger = Substitute.For>(); - _hubLogger = Substitute.For>(); + await sutProvider.Sut.PushNotificationAsync(notification); - var fixture = new Fixture().WithAutoNSubstitutions().Customize(new GlobalSettingsCustomization()); - _services = fixture.CreateMany(); - _globalSettings = fixture.Create(); - - _sut = new MultiServicePushNotificationService( - _services, - _logger, - _globalSettings - ); + await sutProvider.GetDependency>() + .First() + .Received(1) + .PushNotificationAsync(notification); } - // Remove this test when we add actual tests. It only proves that - // we've properly constructed the system under test. - [Fact] - public void ServiceExists() + [Theory] + [BitAutoData] + [NotificationCustomize] + [NotificationStatusCustomize] + public async Task PushNotificationStatusAsync_Notification_Sent( + SutProvider sutProvider, Notification notification, + NotificationStatus notificationStatus) { - Assert.NotNull(_sut); + await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .PushNotificationStatusAsync(notification, notificationStatus); + } + + [Theory] + [BitAutoData([null, null])] + [BitAutoData(ClientType.All, null)] + [BitAutoData([null, "test device id"])] + [BitAutoData(ClientType.All, "test device id")] + public async Task SendPayloadToUserAsync_Message_Sent(ClientType? clientType, string? deviceId, string userId, + PushType type, object payload, string identifier, SutProvider sutProvider) + { + await sutProvider.Sut.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType); + } + + [Theory] + [BitAutoData([null, null])] + [BitAutoData(ClientType.All, null)] + [BitAutoData([null, "test device id"])] + [BitAutoData(ClientType.All, "test device id")] + public async Task SendPayloadToOrganizationAsync_Message_Sent(ClientType? clientType, string? deviceId, + string organizationId, PushType type, object payload, string identifier, + SutProvider sutProvider) + { + await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, + clientType); + + await sutProvider.GetDependency>() + .First() + .Received(1) + .SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, clientType); } } diff --git a/test/Core.Test/Services/CollectionServiceTests.cs b/test/Core.Test/Services/CollectionServiceTests.cs index 26e47e83e8..6d788deb05 100644 --- a/test/Core.Test/Services/CollectionServiceTests.cs +++ b/test/Core.Test/Services/CollectionServiceTests.cs @@ -1,5 +1,4 @@ using Bit.Core.AdminConsole.Entities; -using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -176,33 +175,4 @@ public class CollectionServiceTest await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(default, default); } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsAsync_WithViewAllCollectionsTrue_ReturnsAllOrganizationCollections( - Collection collection, Guid organizationId, Guid userId, SutProvider sutProvider) - { - sutProvider.GetDependency().UserId.Returns(userId); - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organizationId) - .Returns(new List { collection }); - sutProvider.GetDependency().ViewAllCollections(organizationId).Returns(true); - - var result = await sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId); - - Assert.Single(result); - Assert.Equal(collection, result.First()); - - await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdAsync(organizationId); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default); - } - - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsAsync_WithViewAssignedCollectionsFalse_ThrowsBadRequestException( - Guid organizationId, SutProvider sutProvider) - { - await Assert.ThrowsAsync(() => sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default); - } } diff --git a/test/Core.Test/Services/DeviceServiceTests.cs b/test/Core.Test/Services/DeviceServiceTests.cs index 41ef0b4d74..98b04eb7d3 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.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; @@ -16,15 +17,23 @@ namespace Bit.Core.Test.Services; [SutProviderCustomize] public class DeviceServiceTests { - [Fact] - public async Task DeviceSaveShouldUpdateRevisionDateAndPushRegistration() + [Theory] + [BitAutoData] + public async Task SaveAsync_IdProvided_UpdatedRevisionDateAndPushRegistration(Guid id, Guid userId, + Guid organizationId1, Guid organizationId2, + OrganizationUserOrganizationDetails organizationUserOrganizationDetails1, + OrganizationUserOrganizationDetails organizationUserOrganizationDetails2) { + organizationUserOrganizationDetails1.OrganizationId = organizationId1; + organizationUserOrganizationDetails2.OrganizationId = organizationId2; + var deviceRepo = Substitute.For(); var pushRepo = Substitute.For(); - var deviceService = new DeviceService(deviceRepo, pushRepo); + var organizationUserRepository = Substitute.For(); + organizationUserRepository.GetManyDetailsByUserAsync(Arg.Any(), Arg.Any()) + .Returns([organizationUserOrganizationDetails1, organizationUserOrganizationDetails2]); + var deviceService = new DeviceService(deviceRepo, pushRepo, organizationUserRepository); - var id = Guid.NewGuid(); - var userId = Guid.NewGuid(); var device = new Device { Id = id, @@ -37,8 +46,53 @@ public class DeviceServiceTests await deviceService.SaveAsync(device); Assert.True(device.RevisionDate - DateTime.UtcNow < TimeSpan.FromSeconds(1)); - await pushRepo.Received().CreateOrUpdateRegistrationAsync("testtoken", id.ToString(), - userId.ToString(), "testid", DeviceType.Android); + await pushRepo.Received(1).CreateOrUpdateRegistrationAsync("testtoken", id.ToString(), + userId.ToString(), "testid", DeviceType.Android, + Arg.Do>(organizationIds => + { + var organizationIdsList = organizationIds.ToList(); + Assert.Equal(2, organizationIdsList.Count); + Assert.Contains(organizationId1.ToString(), organizationIdsList); + Assert.Contains(organizationId2.ToString(), organizationIdsList); + })); + } + + [Theory] + [BitAutoData] + public async Task SaveAsync_IdNotProvided_CreatedAndPushRegistration(Guid userId, Guid organizationId1, + Guid organizationId2, + OrganizationUserOrganizationDetails organizationUserOrganizationDetails1, + OrganizationUserOrganizationDetails organizationUserOrganizationDetails2) + { + organizationUserOrganizationDetails1.OrganizationId = organizationId1; + organizationUserOrganizationDetails2.OrganizationId = organizationId2; + + var deviceRepo = Substitute.For(); + var pushRepo = Substitute.For(); + var organizationUserRepository = Substitute.For(); + organizationUserRepository.GetManyDetailsByUserAsync(Arg.Any(), Arg.Any()) + .Returns([organizationUserOrganizationDetails1, organizationUserOrganizationDetails2]); + var deviceService = new DeviceService(deviceRepo, pushRepo, organizationUserRepository); + + var device = new Device + { + Name = "test device", + Type = DeviceType.Android, + UserId = userId, + PushToken = "testtoken", + Identifier = "testid" + }; + await deviceService.SaveAsync(device); + + await pushRepo.Received(1).CreateOrUpdateRegistrationAsync("testtoken", + Arg.Do(id => Guid.TryParse(id, out var _)), userId.ToString(), "testid", DeviceType.Android, + Arg.Do>(organizationIds => + { + var organizationIdsList = organizationIds.ToList(); + Assert.Equal(2, organizationIdsList.Count); + Assert.Contains(organizationId1.ToString(), organizationIdsList); + Assert.Contains(organizationId2.ToString(), organizationIdsList); + })); } /// @@ -62,12 +116,7 @@ public class DeviceServiceTests sutProvider.GetDependency() .GetManyByUserIdAsync(currentUserId) - .Returns(new List - { - deviceOne, - deviceTwo, - deviceThree, - }); + .Returns(new List { deviceOne, deviceTwo, deviceThree, }); var currentDeviceModel = new DeviceKeysUpdateRequestModel { @@ -85,7 +134,8 @@ public class DeviceServiceTests }, }; - await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels); + await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, + alteredDeviceModels); // Updating trust, "current" or "other" only needs to change the EncryptedPublicKey & EncryptedUserKey await sutProvider.GetDependency() @@ -149,11 +199,7 @@ public class DeviceServiceTests sutProvider.GetDependency() .GetManyByUserIdAsync(currentUserId) - .Returns(new List - { - deviceOne, - deviceTwo, - }); + .Returns(new List { deviceOne, deviceTwo, }); var currentDeviceModel = new DeviceKeysUpdateRequestModel { @@ -171,7 +217,8 @@ public class DeviceServiceTests }, }; - await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels); + await sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, + alteredDeviceModels); // Check that UpsertAsync was called for the trusted device await sutProvider.GetDependency() @@ -203,11 +250,7 @@ public class DeviceServiceTests sutProvider.GetDependency() .GetManyByUserIdAsync(currentUserId) - .Returns(new List - { - deviceOne, - deviceTwo, - }); + .Returns(new List { deviceOne, deviceTwo, }); var currentDeviceModel = new DeviceKeysUpdateRequestModel { @@ -237,11 +280,7 @@ public class DeviceServiceTests sutProvider.GetDependency() .GetManyByUserIdAsync(currentUserId) - .Returns(new List - { - deviceOne, - deviceTwo, - }); + .Returns(new List { deviceOne, deviceTwo, }); var currentDeviceModel = new DeviceKeysUpdateRequestModel { @@ -260,6 +299,7 @@ public class DeviceServiceTests }; await Assert.ThrowsAsync(() => - sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, alteredDeviceModels)); + sutProvider.Sut.UpdateDevicesTrustAsync("current_device", currentUserId, currentDeviceModel, + alteredDeviceModels)); } } diff --git a/test/Core.Test/Services/StripePaymentServiceTests.cs b/test/Core.Test/Services/StripePaymentServiceTests.cs index 35e1901a2f..11a19656e1 100644 --- a/test/Core.Test/Services/StripePaymentServiceTests.cs +++ b/test/Core.Test/Services/StripePaymentServiceTests.cs @@ -1,5 +1,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Services; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; @@ -39,6 +40,11 @@ public class StripePaymentServiceTests { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -95,6 +101,12 @@ public class StripePaymentServiceTests { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; + + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -152,6 +164,12 @@ public class StripePaymentServiceTests { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; + + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -210,6 +228,11 @@ public class StripePaymentServiceTests var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); paymentToken = "pm_" + paymentToken; + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -397,6 +420,11 @@ public class StripePaymentServiceTests { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -462,6 +490,12 @@ public class StripePaymentServiceTests { var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); organization.UseSecretsManager = true; + + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == taxInfo.BillingAddressCountry), Arg.Is(p => p == taxInfo.TaxIdNumber)) + .Returns(taxInfo.TaxIdType); + var stripeAdapter = sutProvider.GetDependency(); stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer { @@ -610,6 +644,43 @@ public class StripePaymentServiceTests await braintreeGateway.Customer.Received(1).DeleteAsync("Braintree-Id"); } + [Theory] + [BitAutoData("ES", "A5372895732985327895237")] + public async Task PurchaseOrganizationAsync_ThrowsBadRequestException_WhenTaxIdInvalid(string country, string taxId, SutProvider sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo) + { + taxInfo.BillingAddressCountry = country; + taxInfo.TaxIdNumber = taxId; + taxInfo.TaxIdType = null; + + var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually); + organization.UseSecretsManager = true; + var stripeAdapter = sutProvider.GetDependency(); + stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer + { + Id = "C-1", + }); + stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription + { + Id = "S-1", + CurrentPeriodEnd = DateTime.Today.AddDays(10), + }); + sutProvider.GetDependency() + .BaseServiceUri.CloudRegion + .Returns("US"); + sutProvider + .GetDependency() + .GetStripeTaxCode(Arg.Is(p => p == country), Arg.Is(p => p == taxId)) + .Returns((string)null); + + var actual = await Assert.ThrowsAsync(async () => await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, false, 8, 10)); + + Assert.Equal("billingTaxIdTypeInferenceError", actual.Message); + + await stripeAdapter.Received(0).CustomerCreateAsync(Arg.Any()); + await stripeAdapter.Received(0).SubscriptionCreateAsync(Arg.Any()); + } + + [Theory, BitAutoData] public async Task UpgradeFreeOrganizationAsync_Success(SutProvider sutProvider, Organization organization, TaxInfo taxInfo) diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index a07cc1907f..88c214f471 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -454,8 +454,10 @@ public class UserServiceTests } [Theory, BitAutoData] - public async Task DisableTwoFactorProviderAsync_WithAccountDeprovisioningEnabled_WhenOrganizationHas2FAPolicyEnabled_WhenUserIsManaged_DisablingAllProviders_RemovesOrRevokesUserAndSendsEmail( - SutProvider sutProvider, User user, Organization organization1, Organization organization2) + public async Task DisableTwoFactorProviderAsync_WithAccountDeprovisioningEnabled_WhenOrganizationHas2FAPolicyEnabled_DisablingAllProviders_RevokesUserAndSendsEmail( + SutProvider sutProvider, User user, + Organization organization1, Guid organizationUserId1, + Organization organization2, Guid organizationUserId2) { // Arrange user.SetTwoFactorProviders(new Dictionary @@ -464,6 +466,7 @@ public class UserServiceTests }); organization1.Enabled = organization2.Enabled = true; organization1.UseSso = organization2.UseSso = true; + sutProvider.GetDependency() .IsEnabled(FeatureFlagKeys.AccountDeprovisioning) .Returns(true); @@ -474,12 +477,14 @@ public class UserServiceTests new OrganizationUserPolicyDetails { OrganizationId = organization1.Id, + OrganizationUserId = organizationUserId1, PolicyType = PolicyType.TwoFactorAuthentication, PolicyEnabled = true }, new OrganizationUserPolicyDetails { OrganizationId = organization2.Id, + OrganizationUserId = organizationUserId2, PolicyType = PolicyType.TwoFactorAuthentication, PolicyEnabled = true } @@ -490,9 +495,6 @@ public class UserServiceTests sutProvider.GetDependency() .GetByIdAsync(organization2.Id) .Returns(organization2); - sutProvider.GetDependency() - .GetByVerifiedUserEmailDomainAsync(user.Id) - .Returns(new[] { organization1 }); var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary(), JsonHelpers.LegacyEnumKeyResolver); // Act @@ -506,24 +508,71 @@ public class UserServiceTests .Received(1) .LogUserEventAsync(user.Id, EventType.User_Disabled2fa); - // Revoke the user from the first organization because they are managed by it + // Revoke the user from the first organization await sutProvider.GetDependency() .Received(1) .RevokeNonCompliantOrganizationUsersAsync( Arg.Is(r => r.OrganizationId == organization1.Id && - r.OrganizationUsers.First().UserId == user.Id && + r.OrganizationUsers.First().Id == organizationUserId1 && r.OrganizationUsers.First().OrganizationId == organization1.Id)); await sutProvider.GetDependency() .Received(1) - .SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization1.DisplayName(), user.Email); + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization1.DisplayName(), user.Email); - // Remove the user from the second organization because they are not managed by it - await sutProvider.GetDependency() + // Remove the user from the second organization + await sutProvider.GetDependency() .Received(1) - .RemoveUserAsync(organization2.Id, user.Id); + .RevokeNonCompliantOrganizationUsersAsync( + Arg.Is(r => r.OrganizationId == organization2.Id && + r.OrganizationUsers.First().Id == organizationUserId2 && + r.OrganizationUsers.First().OrganizationId == organization2.Id)); await sutProvider.GetDependency() .Received(1) - .SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization2.DisplayName(), user.Email); + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization2.DisplayName(), user.Email); + } + + [Theory, BitAutoData] + public async Task DisableTwoFactorProviderAsync_WithAccountDeprovisioningEnabled_UserHasOneProviderEnabled_DoesNotRemoveUserFromOrganization( + SutProvider sutProvider, User user, Organization organization) + { + // Arrange + user.SetTwoFactorProviders(new Dictionary + { + [TwoFactorProviderType.Email] = new() { Enabled = true }, + [TwoFactorProviderType.Remember] = new() { Enabled = true } + }); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication) + .Returns( + [ + new OrganizationUserPolicyDetails + { + OrganizationId = organization.Id, + PolicyType = PolicyType.TwoFactorAuthentication, + PolicyEnabled = true + } + ]); + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary + { + [TwoFactorProviderType.Remember] = new() { Enabled = true } + }, JsonHelpers.LegacyEnumKeyResolver); + + // Act + await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync(Arg.Is(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders)); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .RevokeNonCompliantOrganizationUsersAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(default, default); } [Theory, BitAutoData] @@ -681,6 +730,46 @@ public class UserServiceTests .RemoveAsync(Arg.Any()); } + [Theory, BitAutoData] + public async Task RecoverTwoFactorAsync_CorrectCode_ReturnsTrueAndProcessesPolicies( + User user, SutProvider sutProvider) + { + // Arrange + var recoveryCode = "1234"; + user.TwoFactorRecoveryCode = recoveryCode; + + // Act + var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode); + + // Assert + Assert.True(response); + Assert.Null(user.TwoFactorProviders); + // Make sure a new code was generated for the user + Assert.NotEqual(recoveryCode, user.TwoFactorRecoveryCode); + await sutProvider.GetDependency() + .Received(1) + .SendRecoverTwoFactorEmail(Arg.Any(), Arg.Any(), Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .LogUserEventAsync(user.Id, EventType.User_Recovered2fa); + } + + [Theory, BitAutoData] + public async Task RecoverTwoFactorAsync_IncorrectCode_ReturnsFalse( + User user, SutProvider sutProvider) + { + // Arrange + var recoveryCode = "1234"; + user.TwoFactorRecoveryCode = "4567"; + + // Act + var response = await sutProvider.Sut.RecoverTwoFactorAsync(user, recoveryCode); + + // Assert + Assert.False(response); + Assert.NotNull(user.TwoFactorProviders); + } + private static void SetupUserAndDevice(User user, bool shouldHavePassword) { diff --git a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs new file mode 100644 index 0000000000..1e97856281 --- /dev/null +++ b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs @@ -0,0 +1,181 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Services; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Platform.Push; +using Bit.Core.Repositories; +using Bit.Core.Test.AutoFixture.CipherFixtures; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.ImportFeatures; +using Bit.Core.Tools.Models.Business; +using Bit.Core.Tools.Services; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Models.Data; +using Bit.Core.Vault.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + + +namespace Bit.Core.Test.Tools.ImportFeatures; + +[UserCipherCustomize] +[SutProviderCustomize] +public class ImportCiphersAsyncCommandTests +{ + [Theory, BitAutoData] + public async Task ImportIntoIndividualVaultAsync_Success( + Guid importingUserId, + List ciphers, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.PersonalOwnership) + .Returns(false); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(importingUserId) + .Returns(new List()); + + var folders = new List { new Folder { UserId = importingUserId } }; + + var folderRelationships = new List>(); + + // Act + await sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships); + + // Assert + await sutProvider.GetDependency().Received(1).CreateAsync(ciphers, Arg.Any>()); + await sutProvider.GetDependency().Received(1).PushSyncVaultAsync(importingUserId); + } + + [Theory, BitAutoData] + public async Task ImportIntoIndividualVaultAsync_ThrowsBadRequestException( + List folders, + List ciphers, + SutProvider sutProvider) + { + var userId = Guid.NewGuid(); + folders.ForEach(f => f.UserId = userId); + ciphers.ForEach(c => c.UserId = userId); + + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(userId, PolicyType.PersonalOwnership) + .Returns(true); + + var folderRelationships = new List>(); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ImportIntoIndividualVaultAsync(folders, ciphers, folderRelationships)); + + Assert.Equal("You cannot import items into your personal vault because you are a member of an organization which forbids it.", exception.Message); + } + + [Theory, BitAutoData] + public async Task ImportIntoOrganizationalVaultAsync_Success( + Organization organization, + Guid importingUserId, + OrganizationUser importingOrganizationUser, + List collections, + List ciphers, + SutProvider sutProvider) + { + organization.MaxCollections = null; + importingOrganizationUser.OrganizationId = organization.Id; + + foreach (var collection in collections) + { + collection.OrganizationId = organization.Id; + } + + foreach (var cipher in ciphers) + { + cipher.OrganizationId = organization.Id; + } + + KeyValuePair[] collectionRelationships = { + new(0, 0), + new(1, 1), + new(2, 2) + }; + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + sutProvider.GetDependency() + .GetByOrganizationAsync(organization.Id, importingUserId) + .Returns(importingOrganizationUser); + + // Set up a collection that already exists in the organization + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organization.Id) + .Returns(new List { collections[0] }); + + await sutProvider.Sut.ImportIntoOrganizationalVaultAsync(collections, ciphers, collectionRelationships, importingUserId); + + await sutProvider.GetDependency().Received(1).CreateAsync( + ciphers, + Arg.Is>(cols => cols.Count() == collections.Count - 1 && + !cols.Any(c => c.Id == collections[0].Id) && // Check that the collection that already existed in the organization was not added + cols.All(c => collections.Any(x => c.Name == x.Name))), + Arg.Is>(c => c.Count() == ciphers.Count), + Arg.Is>(cus => + cus.Count() == collections.Count - 1 && + !cus.Any(cu => cu.CollectionId == collections[0].Id) && // Check that access was not added for the collection that already existed in the organization + cus.All(cu => cu.OrganizationUserId == importingOrganizationUser.Id && cu.Manage == true))); + await sutProvider.GetDependency().Received(1).PushSyncVaultAsync(importingUserId); + await sutProvider.GetDependency().Received(1).RaiseEventAsync( + Arg.Is(e => e.Type == ReferenceEventType.VaultImported)); + } + + [Theory, BitAutoData] + public async Task ImportIntoOrganizationalVaultAsync_ThrowsBadRequestException( + Organization organization, + Guid importingUserId, + OrganizationUser importingOrganizationUser, + List collections, + List ciphers, + SutProvider sutProvider) + { + organization.MaxCollections = 1; + importingOrganizationUser.OrganizationId = organization.Id; + + foreach (var collection in collections) + { + collection.OrganizationId = organization.Id; + } + + foreach (var cipher in ciphers) + { + cipher.OrganizationId = organization.Id; + } + + KeyValuePair[] collectionRelationships = { + new(0, 0), + new(1, 1), + new(2, 2) + }; + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + sutProvider.GetDependency() + .GetByOrganizationAsync(organization.Id, importingUserId) + .Returns(importingOrganizationUser); + + // Set up a collection that already exists in the organization + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organization.Id) + .Returns(new List { collections[0] }); + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.ImportIntoOrganizationalVaultAsync(collections, ciphers, collectionRelationships, importingUserId)); + + Assert.Equal("This organization can only have a maximum of " + + $"{organization.MaxCollections} collections.", exception.Message); + } +} diff --git a/test/Core.Test/Utilities/CoreHelpersTests.cs b/test/Core.Test/Utilities/CoreHelpersTests.cs index 2cce276fcb..264a55b6ee 100644 --- a/test/Core.Test/Utilities/CoreHelpersTests.cs +++ b/test/Core.Test/Utilities/CoreHelpersTests.cs @@ -271,7 +271,7 @@ public class CoreHelpersTests [InlineData("ascii.com", "ascii.com")] [InlineData("", "")] [InlineData(null, null)] - public void PunyEncode_Success(string text, string expected) + public void PunyEncode_Success(string? text, string? expected) { var actual = CoreHelpers.PunyEncode(text); Assert.Equal(expected, actual); @@ -435,7 +435,7 @@ public class CoreHelpersTests [InlineData("name@", "name@")] // @ symbol but no domain [InlineData("", "")] // Empty string [InlineData(null, null)] // null - public void ObfuscateEmail_Success(string input, string expected) + public void ObfuscateEmail_Success(string? input, string? expected) { Assert.Equal(expected, CoreHelpers.ObfuscateEmail(input)); } @@ -456,7 +456,7 @@ public class CoreHelpersTests [InlineData("user@")] [InlineData("@example.com")] [InlineData("user@ex@ample.com")] - public void GetEmailDomain_ReturnsNull(string wrongEmail) + public void GetEmailDomain_ReturnsNull(string? wrongEmail) { Assert.Null(CoreHelpers.GetEmailDomain(wrongEmail)); } diff --git a/test/Core.Test/Utilities/EncryptedStringAttributeTests.cs b/test/Core.Test/Utilities/EncryptedStringAttributeTests.cs index 859d7cd6f2..b5989987fb 100644 --- a/test/Core.Test/Utilities/EncryptedStringAttributeTests.cs +++ b/test/Core.Test/Utilities/EncryptedStringAttributeTests.cs @@ -25,7 +25,7 @@ public class EncryptedStringAttributeTests [InlineData("Rsa2048_OaepSha256_HmacSha256_B64.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==")] // Valid Rsa2048_OaepSha256_HmacSha256_B64 as a string [InlineData("6.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==")] // Valid Rsa2048_OaepSha1_HmacSha256_B64 as a number [InlineData("Rsa2048_OaepSha1_HmacSha256_B64.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==")] - public void IsValid_ReturnsTrue_WhenValid(string input) + public void IsValid_ReturnsTrue_WhenValid(string? input) { var sut = new EncryptedStringAttribute(); diff --git a/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs b/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs index bcd3efcc13..3e375a1d9f 100644 --- a/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs +++ b/test/Core.Test/Utilities/StrictEmailAddressAttributeTests.cs @@ -47,7 +47,7 @@ public class StrictEmailAttributeTests [InlineData("hellothere@world.com-")] // domain ending in hyphen [InlineData("hellö@world.com")] // unicode at end of local-part [InlineData("héllo@world.com")] // unicode in middle of local-part - public void IsValid_ReturnsFalseWhenInvalid(string email) + public void IsValid_ReturnsFalseWhenInvalid(string? email) { var sut = new StrictEmailAddressAttribute(); diff --git a/test/Core.Test/Utilities/StrictEmailAddressListAttributeTests.cs b/test/Core.Test/Utilities/StrictEmailAddressListAttributeTests.cs index 2ec5a45689..745b5851e1 100644 --- a/test/Core.Test/Utilities/StrictEmailAddressListAttributeTests.cs +++ b/test/Core.Test/Utilities/StrictEmailAddressListAttributeTests.cs @@ -42,7 +42,7 @@ public class StrictEmailAddressListAttributeTests [Theory] [InlineData("single@email.com", false)] [InlineData(null, false)] - public void IsValid_ReturnsTrue_WhenValid(string email, bool valid) + public void IsValid_ReturnsTrue_WhenValid(string? email, bool valid) { var sut = new StrictEmailAddressListAttribute(); diff --git a/test/Core.Test/Vault/Commands/CreateManyTasksCommandTest.cs b/test/Core.Test/Vault/Commands/CreateManyTasksCommandTest.cs new file mode 100644 index 0000000000..23e92965f2 --- /dev/null +++ b/test/Core.Test/Vault/Commands/CreateManyTasksCommandTest.cs @@ -0,0 +1,85 @@ +using System.Security.Claims; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Test.Vault.AutoFixture; +using Bit.Core.Vault.Authorization.SecurityTasks; +using Bit.Core.Vault.Commands; +using Bit.Core.Vault.Entities; +using Bit.Core.Vault.Models.Api; +using Bit.Core.Vault.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Vault.Commands; + +[SutProviderCustomize] +[SecurityTaskCustomize] +public class CreateManyTasksCommandTest +{ + private static void Setup(SutProvider sutProvider, Guid? userId, + bool authorizedCreate = false) + { + sutProvider.GetDependency().UserId.Returns(userId); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Is>(reqs => + reqs.Contains(SecurityTaskOperations.Create))) + .Returns(authorizedCreate ? AuthorizationResult.Success() : AuthorizationResult.Failed()); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_NotLoggedIn_NotFoundException( + SutProvider sutProvider, + Guid organizationId, + IEnumerable tasks) + { + Setup(sutProvider, null, true); + + await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(organizationId, tasks)); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_NoTasksProvided_BadRequestException( + SutProvider sutProvider, + Guid organizationId) + { + Setup(sutProvider, Guid.NewGuid()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(organizationId, null)); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_AuthorizationFailed_NotFoundException( + SutProvider sutProvider, + Guid organizationId, + IEnumerable tasks) + { + Setup(sutProvider, Guid.NewGuid()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(organizationId, tasks)); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_AuthorizationSucceeded_ReturnsSecurityTasks( + SutProvider sutProvider, + Guid organizationId, + IEnumerable tasks, + ICollection securityTasks) + { + Setup(sutProvider, Guid.NewGuid(), true); + sutProvider.GetDependency() + .CreateManyAsync(Arg.Any>()) + .Returns(securityTasks); + + var result = await sutProvider.Sut.CreateAsync(organizationId, tasks); + + Assert.Equal(securityTasks, result); + } +} diff --git a/test/Core.Test/Vault/Commands/MarkTaskAsCompletedCommandTest.cs b/test/Core.Test/Vault/Commands/MarkTaskAsCompletedCommandTest.cs index 82550df48d..ca9a42cdb3 100644 --- a/test/Core.Test/Vault/Commands/MarkTaskAsCompletedCommandTest.cs +++ b/test/Core.Test/Vault/Commands/MarkTaskAsCompletedCommandTest.cs @@ -3,7 +3,7 @@ using System.Security.Claims; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Test.Vault.AutoFixture; -using Bit.Core.Vault.Authorization; +using Bit.Core.Vault.Authorization.SecurityTasks; using Bit.Core.Vault.Commands; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Repositories; diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index dd34127efe..4f02d94c9c 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -7,9 +7,6 @@ using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.CipherFixtures; -using Bit.Core.Tools.Enums; -using Bit.Core.Tools.Models.Business; -using Bit.Core.Tools.Services; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Models.Data; @@ -26,64 +23,6 @@ namespace Bit.Core.Test.Services; [SutProviderCustomize] public class CipherServiceTests { - [Theory, BitAutoData] - public async Task ImportCiphersAsync_IntoOrganization_Success( - Organization organization, - Guid importingUserId, - OrganizationUser importingOrganizationUser, - List collections, - List ciphers, - SutProvider sutProvider) - { - organization.MaxCollections = null; - importingOrganizationUser.OrganizationId = organization.Id; - - foreach (var collection in collections) - { - collection.OrganizationId = organization.Id; - } - - foreach (var cipher in ciphers) - { - cipher.OrganizationId = organization.Id; - } - - KeyValuePair[] collectionRelationships = { - new(0, 0), - new(1, 1), - new(2, 2) - }; - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency() - .GetByOrganizationAsync(organization.Id, importingUserId) - .Returns(importingOrganizationUser); - - // Set up a collection that already exists in the organization - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organization.Id) - .Returns(new List { collections[0] }); - - await sutProvider.Sut.ImportCiphersAsync(collections, ciphers, collectionRelationships, importingUserId); - - await sutProvider.GetDependency().Received(1).CreateAsync( - ciphers, - Arg.Is>(cols => cols.Count() == collections.Count - 1 && - !cols.Any(c => c.Id == collections[0].Id) && // Check that the collection that already existed in the organization was not added - cols.All(c => collections.Any(x => c.Name == x.Name))), - Arg.Is>(c => c.Count() == ciphers.Count), - Arg.Is>(cus => - cus.Count() == collections.Count - 1 && - !cus.Any(cu => cu.CollectionId == collections[0].Id) && // Check that access was not added for the collection that already existed in the organization - cus.All(cu => cu.OrganizationUserId == importingOrganizationUser.Id && cu.Manage == true))); - await sutProvider.GetDependency().Received(1).PushSyncVaultAsync(importingUserId); - await sutProvider.GetDependency().Received(1).RaiseEventAsync( - Arg.Is(e => e.Type == ReferenceEventType.VaultImported)); - } - [Theory, BitAutoData] public async Task SaveAsync_WrongRevisionDate_Throws(SutProvider sutProvider, Cipher cipher) { diff --git a/test/Events.IntegrationTest/Controllers/CollectControllerTests.cs b/test/Events.IntegrationTest/Controllers/CollectControllerTests.cs new file mode 100644 index 0000000000..7f86758144 --- /dev/null +++ b/test/Events.IntegrationTest/Controllers/CollectControllerTests.cs @@ -0,0 +1,29 @@ +using System.Net.Http.Json; +using Bit.Core.Enums; +using Bit.Events.Models; + +namespace Bit.Events.IntegrationTest.Controllers; + +public class CollectControllerTests +{ + // This is a very simple test, and should be updated to assert more things, but for now + // it ensures that the events startup doesn't throw any errors with fairly basic configuration. + [Fact] + public async Task Post_Works() + { + var eventsApplicationFactory = new EventsApplicationFactory(); + var (accessToken, _) = await eventsApplicationFactory.LoginWithNewAccount(); + var client = eventsApplicationFactory.CreateAuthedClient(accessToken); + + var response = await client.PostAsJsonAsync>("collect", + [ + new EventModel + { + Type = EventType.User_ClientExportedVault, + Date = DateTime.UtcNow, + }, + ]); + + response.EnsureSuccessStatusCode(); + } +} diff --git a/test/Events.IntegrationTest/Events.IntegrationTest.csproj b/test/Events.IntegrationTest/Events.IntegrationTest.csproj new file mode 100644 index 0000000000..dbfe147892 --- /dev/null +++ b/test/Events.IntegrationTest/Events.IntegrationTest.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/test/Events.IntegrationTest/EventsApplicationFactory.cs b/test/Events.IntegrationTest/EventsApplicationFactory.cs new file mode 100644 index 0000000000..3faf5e81bf --- /dev/null +++ b/test/Events.IntegrationTest/EventsApplicationFactory.cs @@ -0,0 +1,57 @@ +using Bit.Identity.Models.Request.Accounts; +using Bit.IntegrationTestCommon.Factories; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Events.IntegrationTest; + +public class EventsApplicationFactory : WebApplicationFactoryBase +{ + private readonly IdentityApplicationFactory _identityApplicationFactory; + private const string _connectionString = "DataSource=:memory:"; + + public EventsApplicationFactory() + { + SqliteConnection = new SqliteConnection(_connectionString); + SqliteConnection.Open(); + + _identityApplicationFactory = new IdentityApplicationFactory(); + _identityApplicationFactory.SqliteConnection = SqliteConnection; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + base.ConfigureWebHost(builder); + + builder.ConfigureTestServices(services => + { + services.Configure(JwtBearerDefaults.AuthenticationScheme, options => + { + options.BackchannelHttpHandler = _identityApplicationFactory.Server.CreateHandler(); + }); + }); + } + + /// + /// Helper for registering and logging in to a new account + /// + public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash") + { + await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel + { + Email = email, + MasterPasswordHash = masterPasswordHash, + }); + + return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + SqliteConnection!.Dispose(); + } +} diff --git a/test/Events.IntegrationTest/GlobalUsings.cs b/test/Events.IntegrationTest/GlobalUsings.cs new file mode 100644 index 0000000000..9df1d42179 --- /dev/null +++ b/test/Events.IntegrationTest/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/test/Icons.Test/Models/IconLinkTests.cs b/test/Icons.Test/Models/IconLinkTests.cs index 3801558d8d..a4087f2b74 100644 --- a/test/Icons.Test/Models/IconLinkTests.cs +++ b/test/Icons.Test/Models/IconLinkTests.cs @@ -45,7 +45,7 @@ public class IconLinkTests [InlineData(" ", false)] [InlineData("unusable", false)] [InlineData("ico", true)] - public void WithNoRel_IsUsable(string extension, bool expectedResult) + public void WithNoRel_IsUsable(string? extension, bool expectedResult) { SetAttributeValue("href", $"/favicon.{extension}"); diff --git a/test/Identity.IntegrationTest/openid-configuration.json b/test/Identity.IntegrationTest/openid-configuration.json index 23e5a67c06..4d74f66009 100644 --- a/test/Identity.IntegrationTest/openid-configuration.json +++ b/test/Identity.IntegrationTest/openid-configuration.json @@ -24,6 +24,7 @@ "sstamp", "premium", "device", + "devicetype", "orgowner", "orgadmin", "orguser", diff --git a/test/Identity.Test/Controllers/AccountsControllerTests.cs b/test/Identity.Test/Controllers/AccountsControllerTests.cs index 8acebbabe0..03db0a5904 100644 --- a/test/Identity.Test/Controllers/AccountsControllerTests.cs +++ b/test/Identity.Test/Controllers/AccountsControllerTests.cs @@ -1,4 +1,6 @@ -using Bit.Core; +using System.Reflection; +using System.Text; +using Bit.Core; using Bit.Core.Auth.Models.Api.Request.Accounts; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Services; @@ -11,6 +13,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Settings; using Bit.Core.Tokens; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Business; @@ -42,6 +45,7 @@ public class AccountsControllerTests : IDisposable private readonly IReferenceEventService _referenceEventService; private readonly IFeatureService _featureService; private readonly IDataProtectorTokenFactory _registrationEmailVerificationTokenDataFactory; + private readonly GlobalSettings _globalSettings; public AccountsControllerTests() @@ -57,6 +61,7 @@ public class AccountsControllerTests : IDisposable _referenceEventService = Substitute.For(); _featureService = Substitute.For(); _registrationEmailVerificationTokenDataFactory = Substitute.For>(); + _globalSettings = Substitute.For(); _sut = new AccountsController( _currentContext, @@ -69,7 +74,8 @@ public class AccountsControllerTests : IDisposable _sendVerificationEmailForRegistrationCommand, _referenceEventService, _featureService, - _registrationEmailVerificationTokenDataFactory + _registrationEmailVerificationTokenDataFactory, + _globalSettings ); } @@ -95,8 +101,9 @@ public class AccountsControllerTests : IDisposable } [Fact] - public async Task PostPrelogin_WhenUserDoesNotExist_ShouldDefaultToPBKDF() + public async Task PostPrelogin_WhenUserDoesNotExistAndNoDefaultKdfHmacKeySet_ShouldDefaultToPBKDF() { + SetDefaultKdfHmacKey(null); _userRepository.GetKdfInformationByEmailAsync(Arg.Any()).Returns(Task.FromResult(null)); var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = "user@example.com" }); @@ -105,6 +112,38 @@ public class AccountsControllerTests : IDisposable Assert.Equal(AuthConstants.PBKDF2_ITERATIONS.Default, response.KdfIterations); } + [Theory] + [BitAutoData] + public async Task PostPrelogin_WhenUserDoesNotExistAndDefaultKdfHmacKeyIsSet_ShouldComputeHmacAndReturnExpectedKdf(string email) + { + // Arrange: + var defaultKey = Encoding.UTF8.GetBytes("my-secret-key"); + SetDefaultKdfHmacKey(defaultKey); + + _userRepository.GetKdfInformationByEmailAsync(Arg.Any()).Returns(Task.FromResult(null)); + + var fieldInfo = typeof(AccountsController).GetField("_defaultKdfResults", BindingFlags.NonPublic | BindingFlags.Static); + if (fieldInfo == null) + throw new InvalidOperationException("Field '_defaultKdfResults' not found."); + + var defaultKdfResults = (List)fieldInfo.GetValue(null)!; + + var expectedIndex = GetExpectedKdfIndex(email, defaultKey, defaultKdfResults); + var expectedKdf = defaultKdfResults[expectedIndex]; + + // Act + var response = await _sut.PostPrelogin(new PreloginRequestModel { Email = email }); + + // Assert: Ensure the returned KDF matches the expected one from the computed hash + Assert.Equal(expectedKdf.Kdf, response.Kdf); + Assert.Equal(expectedKdf.KdfIterations, response.KdfIterations); + if (expectedKdf.Kdf == KdfType.Argon2id) + { + Assert.Equal(expectedKdf.KdfMemory, response.KdfMemory); + Assert.Equal(expectedKdf.KdfParallelism, response.KdfParallelism); + } + } + [Fact] public async Task PostRegister_ShouldRegisterUser() { @@ -484,6 +523,28 @@ public class AccountsControllerTests : IDisposable )); } + private void SetDefaultKdfHmacKey(byte[]? newKey) + { + var fieldInfo = typeof(AccountsController).GetField("_defaultKdfHmacKey", BindingFlags.NonPublic | BindingFlags.Instance); + if (fieldInfo == null) + { + throw new InvalidOperationException("Field '_defaultKdfHmacKey' not found."); + } + fieldInfo.SetValue(_sut, newKey); + } + private int GetExpectedKdfIndex(string email, byte[] defaultKey, List defaultKdfResults) + { + // Compute the HMAC hash of the email + var hmacMessage = Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant()); + using var hmac = new System.Security.Cryptography.HMACSHA256(defaultKey); + var hmacHash = hmac.ComputeHash(hmacMessage); + + // Convert the hash to a number and calculate the index + var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant(); + var hashFirst8Bytes = hashHex.Substring(0, 16); + var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber); + return (int)(Math.Abs(hashNumber) % defaultKdfResults.Count); + } } diff --git a/test/Identity.Test/Identity.Test.csproj b/test/Identity.Test/Identity.Test.csproj index f18ecee6e8..34010d811b 100644 --- a/test/Identity.Test/Identity.Test.csproj +++ b/test/Identity.Test/Identity.Test.csproj @@ -1,8 +1,9 @@  - enable - false + false + + $(WarningsNotAsErrors);CS0672;CS1998 diff --git a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs index 02b6982419..589aac2842 100644 --- a/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs @@ -105,7 +105,7 @@ public class BaseRequestValidatorTests // Assert await _eventService.Received(1) .LogUserEventAsync(context.CustomValidatorRequestContext.User.Id, - Core.Enums.EventType.User_FailedLogIn); + EventType.User_FailedLogIn); Assert.True(context.GrantResult.IsError); Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message); } @@ -369,7 +369,6 @@ public class BaseRequestValidatorTests context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false; context.ValidatedTokenRequest.ClientId = "Not Web"; _sut.isValid = true; - _featureService.IsEnabled(FeatureFlagKeys.BlockLegacyUsers).Returns(true); _twoFactorAuthenticationValidator .RequiresTwoFactorAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(new Tuple(false, null))); diff --git a/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs b/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs index fa3a117c55..6e6406f16b 100644 --- a/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/DeviceValidatorTests.cs @@ -227,7 +227,7 @@ public class DeviceValidatorTests } [Theory, BitAutoData] - public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_SendsEmail_ReturnsTrue( + public async void ValidateRequestDeviceAsync_ExistingUserNewDeviceLogin_SendNewDeviceLoginEmail_ReturnsTrue( CustomValidatorRequestContext context, [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) { @@ -237,8 +237,6 @@ public class DeviceValidatorTests _globalSettings.DisableEmailNewDevice = false; _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) .Returns(null as Device); - _featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification) - .Returns(false); // set user creation to more than 10 minutes ago context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11); @@ -253,7 +251,7 @@ public class DeviceValidatorTests } [Theory, BitAutoData] - public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_NewUser_DoesNotSendEmail_ReturnsTrue( + public async void ValidateRequestDeviceAsync_NewUserNewDeviceLogin_DoesNotSendNewDeviceLoginEmail_ReturnsTrue( CustomValidatorRequestContext context, [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) { @@ -263,8 +261,6 @@ public class DeviceValidatorTests _globalSettings.DisableEmailNewDevice = false; _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) .Returns(null as Device); - _featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification) - .Returns(false); // set user creation to less than 10 minutes ago context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(9); @@ -279,7 +275,7 @@ public class DeviceValidatorTests } [Theory, BitAutoData] - public async void ValidateRequestDeviceAsync_NewDeviceVerificationFeatureFlagFalse_DisableEmailTrue_DoesNotSendEmail_ReturnsTrue( + public async void ValidateRequestDeviceAsynce_DisableNewDeviceLoginEmailTrue_DoesNotSendNewDeviceEmail_ReturnsTrue( CustomValidatorRequestContext context, [AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request) { @@ -289,8 +285,6 @@ public class DeviceValidatorTests _globalSettings.DisableEmailNewDevice = true; _deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id) .Returns(null as Device); - _featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification) - .Returns(false); // Act var result = await _sut.ValidateRequestDeviceAsync(request, context); diff --git a/test/Identity.Test/IdentityServer/TwoFactorAuthenticationValidatorTests.cs b/test/Identity.Test/IdentityServer/TwoFactorAuthenticationValidatorTests.cs index dfb877b8d6..e59a66a9e7 100644 --- a/test/Identity.Test/IdentityServer/TwoFactorAuthenticationValidatorTests.cs +++ b/test/Identity.Test/IdentityServer/TwoFactorAuthenticationValidatorTests.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Core; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Identity.TokenProviders; using Bit.Core.Auth.Models.Business.Tokenables; @@ -328,7 +329,7 @@ public class TwoFactorAuthenticationValidatorTests _userManager.TWO_FACTOR_PROVIDERS = ["email"]; // Act - var result = await _sut.VerifyTwoFactor( + var result = await _sut.VerifyTwoFactorAsync( user, null, TwoFactorProviderType.U2f, token); // Assert @@ -348,7 +349,7 @@ public class TwoFactorAuthenticationValidatorTests _userManager.TWO_FACTOR_PROVIDERS = ["email"]; // Act - var result = await _sut.VerifyTwoFactor( + var result = await _sut.VerifyTwoFactorAsync( user, null, TwoFactorProviderType.Email, token); // Assert @@ -368,7 +369,7 @@ public class TwoFactorAuthenticationValidatorTests _userManager.TWO_FACTOR_PROVIDERS = ["OrganizationDuo"]; // Act - var result = await _sut.VerifyTwoFactor( + var result = await _sut.VerifyTwoFactorAsync( user, null, TwoFactorProviderType.OrganizationDuo, token); // Assert @@ -394,7 +395,7 @@ public class TwoFactorAuthenticationValidatorTests _userManager.TWO_FACTOR_TOKEN_VERIFIED = true; // Act - var result = await _sut.VerifyTwoFactor(user, null, providerType, token); + var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token); // Assert Assert.True(result); @@ -419,7 +420,7 @@ public class TwoFactorAuthenticationValidatorTests _userManager.TWO_FACTOR_TOKEN_VERIFIED = false; // Act - var result = await _sut.VerifyTwoFactor(user, null, providerType, token); + var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token); // Assert Assert.False(result); @@ -445,13 +446,56 @@ public class TwoFactorAuthenticationValidatorTests organization.Enabled = true; // Act - var result = await _sut.VerifyTwoFactor( + var result = await _sut.VerifyTwoFactorAsync( user, organization, providerType, token); // Assert Assert.True(result); } + [Theory] + [BitAutoData(TwoFactorProviderType.RecoveryCode)] + public async void VerifyTwoFactorAsync_RecoveryCode_ValidToken_ReturnsTrue( + TwoFactorProviderType providerType, + User user, + Organization organization) + { + var token = "1234"; + user.TwoFactorRecoveryCode = token; + + _userService.RecoverTwoFactorAsync(Arg.Is(user), Arg.Is(token)).Returns(true); + _featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin).Returns(true); + + // Act + var result = await _sut.VerifyTwoFactorAsync( + user, organization, providerType, token); + + // Assert + Assert.True(result); + } + + [Theory] + [BitAutoData(TwoFactorProviderType.RecoveryCode)] + public async void VerifyTwoFactorAsync_RecoveryCode_InvalidToken_ReturnsFalse( + TwoFactorProviderType providerType, + User user, + Organization organization) + { + // Arrange + var token = "1234"; + user.TwoFactorRecoveryCode = token; + + _userService.RecoverTwoFactorAsync(Arg.Is(user), Arg.Is(token)).Returns(false); + _featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeLogin).Returns(true); + + // Act + var result = await _sut.VerifyTwoFactorAsync( + user, organization, providerType, token); + + // Assert + Assert.False(result); + } + private static UserManagerTestWrapper SubstituteUserManager() { return new UserManagerTestWrapper( diff --git a/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj b/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj index 82d63bd3c1..fdef3c6cac 100644 --- a/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj +++ b/test/Infrastructure.Dapper.Test/Infrastructure.Dapper.Test.csproj @@ -3,27 +3,25 @@ net8.0 enable - enable - false true - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/TaxRateFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/TaxRateFixtures.cs deleted file mode 100644 index c8cd8c692c..0000000000 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/TaxRateFixtures.cs +++ /dev/null @@ -1,56 +0,0 @@ -using AutoFixture; -using AutoFixture.Kernel; -using Bit.Core.Entities; -using Bit.Infrastructure.EFIntegration.Test.AutoFixture.Relays; -using Bit.Infrastructure.EntityFramework.Repositories; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; - -namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture; - -internal class TaxRateBuilder : ISpecimenBuilder -{ - public object Create(object request, ISpecimenContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var type = request as Type; - if (type == null || type != typeof(TaxRate)) - { - return new NoSpecimen(); - } - - var fixture = new Fixture(); - fixture.Customizations.Insert(0, new MaxLengthStringRelay()); - var obj = fixture.WithAutoNSubstitutions().Create(); - return obj; - } -} - -internal class EfTaxRate : ICustomization -{ - public void Customize(IFixture fixture) - { - fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); - fixture.Customizations.Add(new GlobalSettingsBuilder()); - fixture.Customizations.Add(new TaxRateBuilder()); - fixture.Customizations.Add(new EfRepositoryListBuilder()); - } -} - -internal class EfTaxRateAutoDataAttribute : CustomAutoDataAttribute -{ - public EfTaxRateAutoDataAttribute() : base(new SutProviderCustomization(), new EfTaxRate()) - { } -} - -internal class InlineEfTaxRateAutoDataAttribute : InlineCustomAutoDataAttribute -{ - public InlineEfTaxRateAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), - typeof(EfTaxRate) }, values) - { } -} - diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/TaxRateRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Repositories/TaxRateRepositoryTests.cs deleted file mode 100644 index e2c6d03b44..0000000000 --- a/test/Infrastructure.EFIntegration.Test/Repositories/TaxRateRepositoryTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Bit.Core.Entities; -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; - -namespace Bit.Infrastructure.EFIntegration.Test.Repositories; - -public class TaxRateRepositoryTests -{ - [CiSkippedTheory, EfTaxRateAutoData] - public async Task CreateAsync_Works_DataMatches( - TaxRate taxRate, - TaxRateCompare equalityComparer, - List suts, - SqlRepo.TaxRateRepository sqlTaxRateRepo - ) - { - var savedTaxRates = new List(); - foreach (var sut in suts) - { - var i = suts.IndexOf(sut); - var postEfTaxRate = await sut.CreateAsync(taxRate); - sut.ClearChangeTracking(); - - var savedTaxRate = await sut.GetByIdAsync(postEfTaxRate.Id); - savedTaxRates.Add(savedTaxRate); - } - - var sqlTaxRate = await sqlTaxRateRepo.CreateAsync(taxRate); - var savedSqlTaxRate = await sqlTaxRateRepo.GetByIdAsync(sqlTaxRate.Id); - savedTaxRates.Add(savedSqlTaxRate); - - var distinctItems = savedTaxRates.Distinct(equalityComparer); - Assert.True(!distinctItems.Skip(1).Any()); - } -} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs new file mode 100644 index 0000000000..e631280bb3 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/OrganizationTestHelpers.cs @@ -0,0 +1,57 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole; + +/// +/// A set of extension methods used to arrange simple test data. +/// This should only be used for basic, repetitive data arrangement, not for anything complex or for +/// the repository method under test. +/// +public static class OrganizationTestHelpers +{ + public static Task CreateTestUserAsync(this IUserRepository userRepository, string identifier = "test") + { + var id = Guid.NewGuid(); + return userRepository.CreateAsync(new User + { + Id = id, + Name = $"{identifier}-{id}", + Email = $"{id}@example.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + } + + public static Task CreateTestOrganizationAsync(this IOrganizationRepository organizationRepository, + string identifier = "test") + => organizationRepository.CreateAsync(new Organization + { + Name = $"{identifier}-{Guid.NewGuid()}", + BillingEmail = "billing@example.com", // TODO: EF does not enforce this being NOT NULL + Plan = "Test", // TODO: EF does not enforce this being NOT NULl + }); + + public static Task CreateTestOrganizationUserAsync( + this IOrganizationUserRepository organizationUserRepository, + Organization organization, + User user) + => organizationUserRepository.CreateAsync(new OrganizationUser + { + OrganizationId = organization.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.Owner + }); + + public static Task CreateTestGroupAsync( + this IGroupRepository groupRepository, + Organization organization, + string identifier = "test") + => groupRepository.CreateAsync( + new Group { OrganizationId = organization.Id, Name = $"{identifier} {Guid.NewGuid()}" } + ); +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/GroupRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/GroupRepositoryTests.cs new file mode 100644 index 0000000000..e2c2cbfa02 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/GroupRepositoryTests.cs @@ -0,0 +1,129 @@ +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Repositories; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories; + +public class GroupRepositoryTests +{ + [DatabaseTheory, DatabaseData] + public async Task AddGroupUsersByIdAsync_CreatesGroupUsers( + IGroupRepository groupRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository) + { + // Arrange + var user1 = await userRepository.CreateTestUserAsync("user1"); + var user2 = await userRepository.CreateTestUserAsync("user2"); + var user3 = await userRepository.CreateTestUserAsync("user3"); + + var org = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user2); + var orgUser3 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user3); + var orgUserIds = new List([orgUser1.Id, orgUser2.Id, orgUser3.Id]); + var group = await groupRepository.CreateTestGroupAsync(org); + + // Act + await groupRepository.AddGroupUsersByIdAsync(group.Id, orgUserIds); + + // Assert + var actual = await groupRepository.GetManyUserIdsByIdAsync(group.Id); + Assert.Equal(orgUserIds!.Order(), actual.Order()); + } + + [DatabaseTheory, DatabaseData] + public async Task AddGroupUsersByIdAsync_IgnoresExistingGroupUsers( + IGroupRepository groupRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository) + { + // Arrange + var user1 = await userRepository.CreateTestUserAsync("user1"); + var user2 = await userRepository.CreateTestUserAsync("user2"); + var user3 = await userRepository.CreateTestUserAsync("user3"); + + var org = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user2); + var orgUser3 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user3); + var orgUserIds = new List([orgUser1.Id, orgUser2.Id, orgUser3.Id]); + var group = await groupRepository.CreateTestGroupAsync(org); + + // Add user 2 to the group already, make sure this is executed correctly before proceeding + await groupRepository.UpdateUsersAsync(group.Id, [orgUser2.Id]); + var existingUsers = await groupRepository.GetManyUserIdsByIdAsync(group.Id); + Assert.Equal([orgUser2.Id], existingUsers); + + // Act + await groupRepository.AddGroupUsersByIdAsync(group.Id, orgUserIds); + + // Assert - group should contain all users + var actual = await groupRepository.GetManyUserIdsByIdAsync(group.Id); + Assert.Equal(orgUserIds!.Order(), actual.Order()); + } + + [DatabaseTheory, DatabaseData] + public async Task AddGroupUsersByIdAsync_IgnoresUsersNotInOrganization( + IGroupRepository groupRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository) + { + // Arrange + var user1 = await userRepository.CreateTestUserAsync("user1"); + var user2 = await userRepository.CreateTestUserAsync("user2"); + var user3 = await userRepository.CreateTestUserAsync("user3"); + + var org = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user2); + + // User3 belongs to a different org + var otherOrg = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser3 = await organizationUserRepository.CreateTestOrganizationUserAsync(otherOrg, user3); + + var orgUserIds = new List([orgUser1.Id, orgUser2.Id, orgUser3.Id]); + var group = await groupRepository.CreateTestGroupAsync(org); + + // Act + await groupRepository.AddGroupUsersByIdAsync(group.Id, orgUserIds); + + // Assert + var actual = await groupRepository.GetManyUserIdsByIdAsync(group.Id); + Assert.Equal(2, actual.Count); + Assert.Contains(orgUser1.Id, actual); + Assert.Contains(orgUser2.Id, actual); + Assert.DoesNotContain(orgUser3.Id, actual); + } + + [DatabaseTheory, DatabaseData] + public async Task AddGroupUsersByIdAsync_IgnoresDuplicateUsers( + IGroupRepository groupRepository, + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository) + { + // Arrange + var user1 = await userRepository.CreateTestUserAsync("user1"); + var user2 = await userRepository.CreateTestUserAsync("user2"); + + var org = await organizationRepository.CreateTestOrganizationAsync(); + var orgUser1 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user1); + var orgUser2 = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user2); + + var orgUserIds = new List([orgUser1.Id, orgUser2.Id, orgUser2.Id]); // duplicate orgUser2 + var group = await groupRepository.CreateTestGroupAsync(org); + + // Act + await groupRepository.AddGroupUsersByIdAsync(group.Id, orgUserIds); + + // Assert + var actual = await groupRepository.GetManyUserIdsByIdAsync(group.Id); + Assert.Equal(2, actual.Count); + Assert.Contains(orgUser1.Id, actual); + Assert.Contains(orgUser2.Id, actual); + } +} diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs index 8e0b502a47..a1c5f9bd07 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationDomainRepositoryTests.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities; using Bit.Core.Repositories; using Xunit; -namespace Bit.Infrastructure.IntegrationTest.Repositories; +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories; public class OrganizationDomainRepositoryTests { @@ -188,4 +188,122 @@ public class OrganizationDomainRepositoryTests var expectedDomain2 = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain2.DomainName); Assert.Null(expectedDomain2); } + + [DatabaseTheory, DatabaseData] + public async Task GetManyByNextRunDateAsync_ShouldReturnUnverifiedDomains( + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var organization1 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = $"test+{id}@example.com", + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var organizationDomain = new OrganizationDomain + { + OrganizationId = organization1.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + + var within36HoursWindow = 1; + organizationDomain.SetNextRunDate(within36HoursWindow); + + await organizationDomainRepository.CreateAsync(organizationDomain); + + var date = organizationDomain.NextRunDate; + + // Act + var domains = await organizationDomainRepository.GetManyByNextRunDateAsync(date); + + // Assert + var expectedDomain = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain.DomainName); + Assert.NotNull(expectedDomain); + } + + [DatabaseTheory, DatabaseData] + public async Task GetManyByNextRunDateAsync_ShouldNotReturnUnverifiedDomains_WhenNextRunDateIsOutside36hoursWindow( + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var organization1 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = $"test+{id}@example.com", + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var organizationDomain = new OrganizationDomain + { + OrganizationId = organization1.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + + var outside36HoursWindow = 50; + organizationDomain.SetNextRunDate(outside36HoursWindow); + + await organizationDomainRepository.CreateAsync(organizationDomain); + + var date = DateTime.UtcNow.AddDays(1); + + // Act + var domains = await organizationDomainRepository.GetManyByNextRunDateAsync(date); + + // Assert + var expectedDomain = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain.DomainName); + Assert.Null(expectedDomain); + } + + [DatabaseTheory, DatabaseData] + public async Task GetManyByNextRunDateAsync_ShouldNotReturnVerifiedDomains( + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + // Arrange + var id = Guid.NewGuid(); + + var organization1 = await organizationRepository.CreateAsync(new Organization + { + Name = $"Test Org {id}", + BillingEmail = $"test+{id}@example.com", + Plan = "Test", + PrivateKey = "privatekey", + + }); + + var organizationDomain = new OrganizationDomain + { + OrganizationId = organization1.Id, + DomainName = $"domain2+{id}@example.com", + Txt = "btw+12345" + }; + + var within36HoursWindow = 1; + organizationDomain.SetNextRunDate(within36HoursWindow); + organizationDomain.SetVerifiedDate(); + + await organizationDomainRepository.CreateAsync(organizationDomain); + + var date = DateTimeOffset.UtcNow.Date.AddDays(1); + + // Act + var domains = await organizationDomainRepository.GetManyByNextRunDateAsync(date); + + // Assert + var expectedDomain = domains.FirstOrDefault(domain => domain.DomainName == organizationDomain.DomainName); + Assert.Null(expectedDomain); + } } diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs index f6dc4a989d..f7c61ad957 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationRepositoryTests.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Repositories; using Xunit; -namespace Bit.Infrastructure.IntegrationTest.Repositories; +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories; public class OrganizationRepositoryTests { diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs index aee4beb8ce..e82be49173 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Repositories; using Xunit; -namespace Bit.Infrastructure.IntegrationTest.Repositories; +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories; public class OrganizationUserRepositoryTests { diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByUserIdTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByUserIdTests.cs new file mode 100644 index 0000000000..07cb82dc02 --- /dev/null +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/PolicyRepository/GetPolicyDetailsByUserIdTests.cs @@ -0,0 +1,385 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities.Provider; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Enums; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.Utilities; +using Xunit; + +namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.PolicyRepository; + +public class GetPolicyDetailsByUserIdTests +{ + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_NonInvitedUsers_Works( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + // OrgUser1 - owner of org1 - confirmed + var user = await userRepository.CreateTestUserAsync(); + var org1 = await CreateEnterpriseOrg(organizationRepository); + var orgUser1 = new OrganizationUser + { + OrganizationId = org1.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Confirmed, + Type = OrganizationUserType.Owner, + Email = null // confirmed OrgUsers use the email on the User table + }; + await organizationUserRepository.CreateAsync(orgUser1); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org1.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + Data = CoreHelpers.ClassToJsonData(new TestPolicyData { BoolSetting = true, IntSetting = 5 }) + }); + + // OrgUser2 - custom user of org2 - accepted + var org2 = await CreateEnterpriseOrg(organizationRepository); + var orgUser2 = new OrganizationUser + { + OrganizationId = org2.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Accepted, + Type = OrganizationUserType.Custom, + Email = null // accepted OrgUsers use the email on the User table + }; + orgUser2.SetPermissions(new Permissions + { + ManagePolicies = true + }); + await organizationUserRepository.CreateAsync(orgUser2); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org2.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + Data = CoreHelpers.ClassToJsonData(new TestPolicyData { BoolSetting = false, IntSetting = 15 }) + }); + + // Act + var policyDetails = (await policyRepository.GetPolicyDetailsByUserId(user.Id)).ToList(); + + // Assert + Assert.Equal(2, policyDetails.Count); + + var actualPolicyDetails1 = policyDetails.Find(p => p.OrganizationUserId == orgUser1.Id); + var expectedPolicyDetails1 = new PolicyDetails + { + OrganizationUserId = orgUser1.Id, + OrganizationId = org1.Id, + PolicyType = PolicyType.SingleOrg, + PolicyData = CoreHelpers.ClassToJsonData(new TestPolicyData { BoolSetting = true, IntSetting = 5 }), + OrganizationUserType = OrganizationUserType.Owner, + OrganizationUserStatus = OrganizationUserStatusType.Confirmed, + OrganizationUserPermissionsData = null, + IsProvider = false + }; + Assert.Equivalent(expectedPolicyDetails1, actualPolicyDetails1); + Assert.Equivalent(expectedPolicyDetails1.GetDataModel(), new TestPolicyData { BoolSetting = true, IntSetting = 5 }); + + var actualPolicyDetails2 = policyDetails.Find(p => p.OrganizationUserId == orgUser2.Id); + var expectedPolicyDetails2 = new PolicyDetails + { + OrganizationUserId = orgUser2.Id, + OrganizationId = org2.Id, + PolicyType = PolicyType.SingleOrg, + PolicyData = CoreHelpers.ClassToJsonData(new TestPolicyData { BoolSetting = false, IntSetting = 15 }), + OrganizationUserType = OrganizationUserType.Custom, + OrganizationUserStatus = OrganizationUserStatusType.Accepted, + OrganizationUserPermissionsData = CoreHelpers.ClassToJsonData(new Permissions { ManagePolicies = true }), + IsProvider = false + }; + Assert.Equivalent(expectedPolicyDetails2, actualPolicyDetails2); + Assert.Equivalent(expectedPolicyDetails2.GetDataModel(), new TestPolicyData { BoolSetting = false, IntSetting = 15 }); + Assert.Equivalent(new Permissions { ManagePolicies = true }, actualPolicyDetails2.GetOrganizationUserCustomPermissions(), strict: true); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_InvitedUser_Works( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + var orgUser = new OrganizationUser + { + OrganizationId = org.Id, + UserId = null, // invited users have null userId + Status = OrganizationUserStatusType.Invited, + Type = OrganizationUserType.Custom, + Email = user.Email // invited users have matching Email + }; + await organizationUserRepository.CreateAsync(orgUser); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + var expectedPolicyDetails = new PolicyDetails + { + OrganizationUserId = orgUser.Id, + OrganizationId = org.Id, + PolicyType = PolicyType.SingleOrg, + OrganizationUserType = OrganizationUserType.Custom, + OrganizationUserStatus = OrganizationUserStatusType.Invited, + IsProvider = false + }; + + Assert.Equivalent(expectedPolicyDetails, actualPolicyDetails.Single()); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_RevokedConfirmedUser_Works( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + // User has been confirmed to the org but then revoked + var orgUser = new OrganizationUser + { + OrganizationId = org.Id, + UserId = user.Id, + Status = OrganizationUserStatusType.Revoked, + Type = OrganizationUserType.Owner, + Email = null + }; + await organizationUserRepository.CreateAsync(orgUser); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + var expectedPolicyDetails = new PolicyDetails + { + OrganizationUserId = orgUser.Id, + OrganizationId = org.Id, + PolicyType = PolicyType.SingleOrg, + OrganizationUserType = OrganizationUserType.Owner, + OrganizationUserStatus = OrganizationUserStatusType.Revoked, + IsProvider = false + }; + + Assert.Equivalent(expectedPolicyDetails, actualPolicyDetails.Single()); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_RevokedInvitedUser_DoesntReturnPolicies( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + // User has been invited to the org but then revoked - without ever being confirmed and linked to a user. + // This is an unhandled edge case because those users will go through policy enforcement later, + // as part of accepting their invite after being restored. For now this is just documented as expected behavior. + var orgUser = new OrganizationUser + { + OrganizationId = org.Id, + UserId = null, + Status = OrganizationUserStatusType.Revoked, + Type = OrganizationUserType.Owner, + Email = user.Email + }; + await organizationUserRepository.CreateAsync(orgUser); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + Assert.Empty(actualPolicyDetails); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_SetsIsProvider( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository, + IProviderRepository providerRepository, + IProviderUserRepository providerUserRepository, + IProviderOrganizationRepository providerOrganizationRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(org, user); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Arrange provider + var provider = await providerRepository.CreateAsync(new Provider + { + Name = Guid.NewGuid().ToString(), + Enabled = true + }); + await providerUserRepository.CreateAsync(new ProviderUser + { + ProviderId = provider.Id, + UserId = user.Id, + Status = ProviderUserStatusType.Confirmed + }); + await providerOrganizationRepository.CreateAsync(new ProviderOrganization + { + OrganizationId = org.Id, + ProviderId = provider.Id + }); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + var expectedPolicyDetails = new PolicyDetails + { + OrganizationUserId = orgUser.Id, + OrganizationId = org.Id, + PolicyType = PolicyType.SingleOrg, + OrganizationUserType = OrganizationUserType.Owner, + OrganizationUserStatus = OrganizationUserStatusType.Confirmed, + IsProvider = true + }; + + Assert.Equivalent(expectedPolicyDetails, actualPolicyDetails.Single()); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_IgnoresDisabledOrganizations( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + await organizationUserRepository.CreateTestOrganizationUserAsync(org, user); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Org is disabled; its policies remain, but it is now inactive + org.Enabled = false; + await organizationRepository.ReplaceAsync(org); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + Assert.Empty(actualPolicyDetails); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_IgnoresDowngradedOrganizations( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + await organizationUserRepository.CreateTestOrganizationUserAsync(org, user); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = true, + Type = PolicyType.SingleOrg, + }); + + // Org is downgraded; its policies remain but its plan no longer supports them + org.UsePolicies = false; + org.PlanType = PlanType.TeamsAnnually; + await organizationRepository.ReplaceAsync(org); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + Assert.Empty(actualPolicyDetails); + } + + [DatabaseTheory, DatabaseData] + public async Task GetPolicyDetailsByUserId_IgnoresDisabledPolicies( + IUserRepository userRepository, + IOrganizationUserRepository organizationUserRepository, + IOrganizationRepository organizationRepository, + IPolicyRepository policyRepository) + { + // Arrange + var user = await userRepository.CreateTestUserAsync(); + var org = await CreateEnterpriseOrg(organizationRepository); + await organizationUserRepository.CreateTestOrganizationUserAsync(org, user); + await policyRepository.CreateAsync(new Policy + { + OrganizationId = org.Id, + Enabled = false, + Type = PolicyType.SingleOrg, + }); + + // Act + var actualPolicyDetails = await policyRepository.GetPolicyDetailsByUserId(user.Id); + + // Assert + Assert.Empty(actualPolicyDetails); + } + + private class TestPolicyData : IPolicyDataModel + { + public bool BoolSetting { get; set; } + public int IntSetting { get; set; } + } + + private Task CreateEnterpriseOrg(IOrganizationRepository organizationRepository) + => organizationRepository.CreateAsync(new Organization + { + Name = Guid.NewGuid().ToString(), + BillingEmail = "billing@example.com", // TODO: EF does not enforce this being NOT NULL + Plan = "Test", // TODO: EF does not enforce this being NOT NULl + PlanType = PlanType.EnterpriseAnnually, + UsePolicies = true + }); +} diff --git a/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs index a9eec23194..95b88d5662 100644 --- a/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Auth/Repositories/DeviceRepositoryTests.cs @@ -73,6 +73,71 @@ public class DeviceRepositoryTests Assert.Equal(response.First().AuthRequestId, freshAuthRequest.Id); } + [DatabaseTheory] + [DatabaseData] + public async Task GetManyByUserIdWithDeviceAuth_WorksWithMultipleUsersOnSameDevice_ReturnsExpectedResults( + IDeviceRepository sutRepository, + IUserRepository userRepository, + IAuthRequestRepository authRequestRepository) + { + // Arrange + var userA = await userRepository.CreateAsync(new User + { + Name = "Test User A", + Email = $"test_user_A+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var userB = await userRepository.CreateAsync(new User + { + Name = "Test User B", + Email = $"test_user_B+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var sharedDeviceIdentifier = Guid.NewGuid().ToString(); + + var deviceForUserA = await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "chrome-test", + UserId = userA.Id, + Type = DeviceType.ChromeBrowser, + Identifier = sharedDeviceIdentifier, + }); + + var deviceForUserB = await sutRepository.CreateAsync(new Device + { + Active = true, + Name = "chrome-test", + UserId = userB.Id, + Type = DeviceType.ChromeBrowser, + Identifier = sharedDeviceIdentifier, + }); + + var userAAuthRequest = await authRequestRepository.CreateAsync(new AuthRequest + { + ResponseDeviceId = null, + Approved = null, + Type = AuthRequestType.AuthenticateAndUnlock, + OrganizationId = null, + UserId = userA.Id, + RequestIpAddress = ":1", + RequestDeviceIdentifier = deviceForUserA.Identifier, + AccessCode = "AccessCode_1234", + PublicKey = "PublicKey_1234" + }); + + // Act + var response = await sutRepository.GetManyByUserIdWithDeviceAuth(userB.Id); + + // Assert + Assert.Null(response.First().AuthRequestId); + Assert.Null(response.First().AuthRequestCreatedAt); + } + [DatabaseTheory] [DatabaseData] public async Task GetManyByUserIdWithDeviceAuth_WorksWithNoAuthRequestAndMultipleDevices_ReturnsExpectedResults( @@ -117,7 +182,7 @@ public class DeviceRepositoryTests [DatabaseTheory] [DatabaseData] - public async Task GetManyByUserIdWithDeviceAuth_FailsToRespondWithAnyAuthData_ReturnsExpectedResults( + public async Task GetManyByUserIdWithDeviceAuth_FailsToRespondWithAnyAuthData_ReturnsEmptyResults( IDeviceRepository sutRepository, IUserRepository userRepository, IAuthRequestRepository authRequestRepository) diff --git a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj index 159572f387..6d9e0d6667 100644 --- a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj +++ b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj @@ -1,7 +1,6 @@ - enable false 6570f288-5c2c-47ad-8978-f3da255079c2 diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/SecurityTaskRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Vault/Repositories/SecurityTaskRepositoryTests.cs index 2010c90a5e..eb5a310db3 100644 --- a/test/Infrastructure.IntegrationTest/Vault/Repositories/SecurityTaskRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Vault/Repositories/SecurityTaskRepositoryTests.cs @@ -223,4 +223,47 @@ public class SecurityTaskRepositoryTests Assert.DoesNotContain(task1, completedTasks, new SecurityTaskComparer()); Assert.DoesNotContain(task3, completedTasks, new SecurityTaskComparer()); } + + [DatabaseTheory, DatabaseData] + public async Task CreateManyAsync( + IOrganizationRepository organizationRepository, + ICipherRepository cipherRepository, + ISecurityTaskRepository securityTaskRepository) + { + var organization = await organizationRepository.CreateAsync(new Organization + { + Name = "Test Org", + PlanType = PlanType.EnterpriseAnnually, + Plan = "Test Plan", + BillingEmail = "" + }); + + var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", }; + await cipherRepository.CreateAsync(cipher1); + + var cipher2 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", }; + await cipherRepository.CreateAsync(cipher2); + + var tasks = new List + { + new() + { + OrganizationId = organization.Id, + CipherId = cipher1.Id, + Status = SecurityTaskStatus.Pending, + Type = SecurityTaskType.UpdateAtRiskCredential, + }, + new() + { + OrganizationId = organization.Id, + CipherId = cipher2.Id, + Status = SecurityTaskStatus.Completed, + Type = SecurityTaskType.UpdateAtRiskCredential, + } + }; + + var taskIds = await securityTaskRepository.CreateManyAsync(tasks); + + Assert.Equal(2, taskIds.Count); + } } diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index 9474ffb862..7c7f790cdc 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -1,5 +1,6 @@ using AspNetCoreRateLimit; using Bit.Core.Auth.Services; +using Bit.Core.Billing.Services; using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; @@ -13,6 +14,7 @@ using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; @@ -187,44 +189,27 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory // QUESTION: The normal licensing service should run fine on developer machines but not in CI // should we have a fork here to leave the normal service for developers? // TODO: Eventually add the license file to CI - var licensingService = services.First(sd => sd.ServiceType == typeof(ILicensingService)); - services.Remove(licensingService); - services.AddSingleton(); + Replace(services); // FUTURE CONSIDERATION: Add way to run this self hosted/cloud, for now it is cloud only - var pushRegistrationService = services.First(sd => sd.ServiceType == typeof(IPushRegistrationService)); - services.Remove(pushRegistrationService); - services.AddSingleton(); + Replace(services); // Even though we are cloud we currently set this up as cloud, we can use the EF/selfhosted service // instead of using Noop for this service // TODO: Install and use azurite in CI pipeline - var eventWriteService = services.First(sd => sd.ServiceType == typeof(IEventWriteService)); - services.Remove(eventWriteService); - services.AddSingleton(); + Replace(services); - var eventRepositoryService = services.First(sd => sd.ServiceType == typeof(IEventRepository)); - services.Remove(eventRepositoryService); - services.AddSingleton(); + Replace(services); - var mailDeliveryService = services.First(sd => sd.ServiceType == typeof(IMailDeliveryService)); - services.Remove(mailDeliveryService); - services.AddSingleton(); + Replace(services); - var captchaValidationService = services.First(sd => sd.ServiceType == typeof(ICaptchaValidationService)); - services.Remove(captchaValidationService); - services.AddSingleton(); + Replace(services); // TODO: Install and use azurite in CI pipeline - var installationDeviceRepository = - services.First(sd => sd.ServiceType == typeof(IInstallationDeviceRepository)); - services.Remove(installationDeviceRepository); - services.AddSingleton(); + Replace(services); // TODO: Install and use azurite in CI pipeline - var referenceEventService = services.First(sd => sd.ServiceType == typeof(IReferenceEventService)); - services.Remove(referenceEventService); - services.AddSingleton(); + Replace(services); // Our Rate limiter works so well that it begins to fail tests unless we carve out // one whitelisted ip. We should still test the rate limiter though and they should change the Ip @@ -244,9 +229,9 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory services.AddSingleton(); // Noop StripePaymentService - this could be changed to integrate with our Stripe test account - var stripePaymentService = services.First(sd => sd.ServiceType == typeof(IPaymentService)); - services.Remove(stripePaymentService); - services.AddSingleton(Substitute.For()); + Replace(services, Substitute.For()); + + Replace(services, Substitute.For()); }); foreach (var configureTestService in _configureTestServices) @@ -255,6 +240,35 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory } } + private static void Replace(IServiceCollection services) + where TService : class + where TNewImplementation : class, TService + { + services.RemoveAll(); + services.AddSingleton(); + } + + private static void Replace(IServiceCollection services, TService implementation) + where TService : class + { + services.RemoveAll(); + services.AddSingleton(implementation); + } + + public HttpClient CreateAuthedClient(string accessToken) + { + var handler = Server.CreateHandler((context) => + { + context.Request.Headers.Authorization = $"Bearer {accessToken}"; + }); + + return new HttpClient(handler) + { + BaseAddress = Server.BaseAddress, + Timeout = TimeSpan.FromSeconds(200), + }; + } + public DatabaseContext GetDatabaseContext() { var scope = Services.CreateScope(); diff --git a/util/Migrator/DbScripts/2025-01-10_00_ReadActiveWithPendingAuthRequestsByUserId.sql b/util/Migrator/DbScripts/2025-01-10_00_ReadActiveWithPendingAuthRequestsByUserId.sql new file mode 100644 index 0000000000..10319f3207 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-10_00_ReadActiveWithPendingAuthRequestsByUserId.sql @@ -0,0 +1,29 @@ +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 + Id, + CreationDate, + RequestDeviceIdentifier, + Approved, + ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) as rn + FROM dbo.AuthRequestView + WHERE Type IN (0, 1) -- AuthenticateAndUnlock and Unlock types only + AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired + AND UserId = @UserId -- Requests for this user only + ) AR -- This join will get the most recent request per device, regardless of approval status + ON D.Identifier = AR.RequestDeviceIdentifier AND AR.rn = 1 AND AR.Approved IS NULL -- Get only the most recent unapproved request per device + WHERE + D.UserId = @UserId -- Include only devices for this user + AND D.Active = 1; -- Include only active devices +END; diff --git a/util/Migrator/DbScripts/2025-01-16_00_DropOrganizationLimitCollectionCreationDeletion.sql b/util/Migrator/DbScripts/2025-01-16_00_DropOrganizationLimitCollectionCreationDeletion.sql new file mode 100644 index 0000000000..34a14900f6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-16_00_DropOrganizationLimitCollectionCreationDeletion.sql @@ -0,0 +1,485 @@ +-- Finalise removal of Organization.LimitCollectionCreationDeletion column + +-- Drop default constraint +IF OBJECT_ID('[dbo].[DF_Organization_LimitCollectionCreationDeletion]', 'D') IS NOT NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + DROP CONSTRAINT + [DF_Organization_LimitCollectionCreationDeletion] +END +GO + +-- Drop the column +IF COL_LENGTH('[dbo].[Organization]', 'LimitCollectionCreationDeletion') IS NOT NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + DROP COLUMN + [LimitCollectionCreationDeletion] +END +GO + +-- Refresh Views +CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + OU.[Id] OrganizationUserId, + O.[Name], + O.[Enabled], + O.[PlanType], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[UseSecretsManager], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + OU.[Key], + OU.[ResetPasswordKey], + O.[PublicKey], + O.[PrivateKey], + OU.[Status], + OU.[Type], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + PO.[ProviderId], + P.[Name] ProviderName, + P.[Type] ProviderType, + SS.[Data] SsoConfig, + OS.[FriendlyName] FamilySponsorshipFriendlyName, + OS.[LastSyncDate] FamilySponsorshipLastSyncDate, + OS.[ToDelete] FamilySponsorshipToDelete, + OS.[ValidUntil] FamilySponsorshipValidUntil, + OU.[AccessSecretsManager], + O.[UsePasswordManager], + O.[SmSeats], + O.[SmServiceAccounts], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] +LEFT JOIN + [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] +LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id] +GO + +CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] +AS +SELECT + PU.[UserId], + PO.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + PO.[Key], + O.[PublicKey], + O.[PrivateKey], + PU.[Status], + PU.[Type], + PO.[ProviderId], + PU.[Id] ProviderUserId, + P.[Name] ProviderName, + O.[PlanType], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + 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 + +-- Refresh Stored Procedures +CREATE OR ALTER PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT= null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = NULL, + @LimitCollectionDeletion BIT = NULL, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Identifier], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UsePolicies], + [UseSso], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [UseApi], + [UseResetPassword], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [Enabled], + [LicenseKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate], + [OwnersNotifiedOfAutoscaling], + [MaxAutoscaleSeats], + [UseKeyConnector], + [UseScim], + [UseCustomPermissions], + [UseSecretsManager], + [Status], + [UsePasswordManager], + [SmSeats], + [SmServiceAccounts], + [MaxAutoscaleSmSeats], + [MaxAutoscaleSmServiceAccounts], + [SecretsManagerBeta], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector, + @UseScim, + @UseCustomPermissions, + @UseSecretsManager, + @Status, + @UsePasswordManager, + @SmSeats, + @SmServiceAccounts, + @MaxAutoscaleSmSeats, + @MaxAutoscaleSmServiceAccounts, + @SecretsManagerBeta, + @LimitCollectionCreation, + @LimitCollectionDeletion, + @AllowAdminAccessToAllCollectionItems, + @UseRiskInsights + ) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadAbilities] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [UseEvents], + [Use2fa], + CASE + WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN + 1 + ELSE + 0 + END AS [Using2fa], + [UsersGetPremium], + [UseCustomPermissions], + [UseSso], + [UseKeyConnector], + [UseScim], + [UseResetPassword], + [UsePolicies], + [Enabled], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights] + FROM + [dbo].[Organization] +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT = null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = null, + @LimitCollectionDeletion BIT = null, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Organization] + SET + [Identifier] = @Identifier, + [Name] = @Name, + [BusinessName] = @BusinessName, + [BusinessAddress1] = @BusinessAddress1, + [BusinessAddress2] = @BusinessAddress2, + [BusinessAddress3] = @BusinessAddress3, + [BusinessCountry] = @BusinessCountry, + [BusinessTaxNumber] = @BusinessTaxNumber, + [BillingEmail] = @BillingEmail, + [Plan] = @Plan, + [PlanType] = @PlanType, + [Seats] = @Seats, + [MaxCollections] = @MaxCollections, + [UsePolicies] = @UsePolicies, + [UseSso] = @UseSso, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseEvents] = @UseEvents, + [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, + [UseApi] = @UseApi, + [UseResetPassword] = @UseResetPassword, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [Enabled] = @Enabled, + [LicenseKey] = @LicenseKey, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [TwoFactorProviders] = @TwoFactorProviders, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector, + [UseScim] = @UseScim, + [UseCustomPermissions] = @UseCustomPermissions, + [UseSecretsManager] = @UseSecretsManager, + [Status] = @Status, + [UsePasswordManager] = @UsePasswordManager, + [SmSeats] = @SmSeats, + [SmServiceAccounts] = @SmServiceAccounts, + [MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats, + [MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts, + [SecretsManagerBeta] = @SecretsManagerBeta, + [LimitCollectionCreation] = @LimitCollectionCreation, + [LimitCollectionDeletion] = @LimitCollectionDeletion, + [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems, + [UseRiskInsights] = @UseRiskInsights + WHERE + [Id] = @Id +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationView] +AS +SELECT + * +FROM + [dbo].[Organization] diff --git a/util/Migrator/DbScripts/2025-01-16_01_LimitItemDeletion.sql b/util/Migrator/DbScripts/2025-01-16_01_LimitItemDeletion.sql new file mode 100644 index 0000000000..f207365471 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-16_01_LimitItemDeletion.sql @@ -0,0 +1,487 @@ + +-- Add Columns +IF COL_LENGTH('[dbo].[Organization]', 'LimitItemDeletion') IS NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [LimitItemDeletion] BIT NOT NULL CONSTRAINT [DF_Organization_LimitItemDeletion] DEFAULT (0) +END +GO + + +-- Refresh Views + +CREATE OR ALTER VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] +AS +SELECT + PU.[UserId], + PO.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + PO.[Key], + O.[PublicKey], + O.[PrivateKey], + PU.[Status], + PU.[Type], + PO.[ProviderId], + PU.[Id] ProviderUserId, + P.[Name] ProviderName, + O.[PlanType], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + P.[Type] ProviderType, + O.[LimitItemDeletion] +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 + +CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + OU.[Id] OrganizationUserId, + O.[Name], + O.[Enabled], + O.[PlanType], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[UseSecretsManager], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + OU.[Key], + OU.[ResetPasswordKey], + O.[PublicKey], + O.[PrivateKey], + OU.[Status], + OU.[Type], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + PO.[ProviderId], + P.[Name] ProviderName, + P.[Type] ProviderType, + SS.[Data] SsoConfig, + OS.[FriendlyName] FamilySponsorshipFriendlyName, + OS.[LastSyncDate] FamilySponsorshipLastSyncDate, + OS.[ToDelete] FamilySponsorshipToDelete, + OS.[ValidUntil] FamilySponsorshipValidUntil, + OU.[AccessSecretsManager], + O.[UsePasswordManager], + O.[SmSeats], + O.[SmServiceAccounts], + O.[LimitCollectionCreation], + O.[LimitCollectionDeletion], + O.[AllowAdminAccessToAllCollectionItems], + O.[UseRiskInsights], + O.[LimitItemDeletion] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] +LEFT JOIN + [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] +LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id] + +GO + + +-- Refresh Stored Procedures + +CREATE OR ALTER PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT= null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = NULL, + @LimitCollectionDeletion BIT = NULL, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Identifier], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UsePolicies], + [UseSso], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [UseApi], + [UseResetPassword], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [Enabled], + [LicenseKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate], + [OwnersNotifiedOfAutoscaling], + [MaxAutoscaleSeats], + [UseKeyConnector], + [UseScim], + [UseCustomPermissions], + [UseSecretsManager], + [Status], + [UsePasswordManager], + [SmSeats], + [SmServiceAccounts], + [MaxAutoscaleSmSeats], + [MaxAutoscaleSmServiceAccounts], + [SecretsManagerBeta], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [LimitItemDeletion] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector, + @UseScim, + @UseCustomPermissions, + @UseSecretsManager, + @Status, + @UsePasswordManager, + @SmSeats, + @SmServiceAccounts, + @MaxAutoscaleSmSeats, + @MaxAutoscaleSmServiceAccounts, + @SecretsManagerBeta, + @LimitCollectionCreation, + @LimitCollectionDeletion, + @AllowAdminAccessToAllCollectionItems, + @UseRiskInsights, + @LimitItemDeletion + ) +END + +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadAbilities] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [UseEvents], + [Use2fa], + CASE + WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN + 1 + ELSE + 0 + END AS [Using2fa], + [UsersGetPremium], + [UseCustomPermissions], + [UseSso], + [UseKeyConnector], + [UseScim], + [UseResetPassword], + [UsePolicies], + [Enabled], + [LimitCollectionCreation], + [LimitCollectionDeletion], + [AllowAdminAccessToAllCollectionItems], + [UseRiskInsights], + [LimitItemDeletion] + FROM + [dbo].[Organization] +END + +GO + + + +CREATE OR ALTER PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0, + @UsePasswordManager BIT = 1, + @SmSeats INT = null, + @SmServiceAccounts INT = null, + @MaxAutoscaleSmSeats INT = null, + @MaxAutoscaleSmServiceAccounts INT = null, + @SecretsManagerBeta BIT = 0, + @LimitCollectionCreation BIT = null, + @LimitCollectionDeletion BIT = null, + @AllowAdminAccessToAllCollectionItems BIT = 0, + @UseRiskInsights BIT = 0, + @LimitItemDeletion BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Organization] + SET + [Identifier] = @Identifier, + [Name] = @Name, + [BusinessName] = @BusinessName, + [BusinessAddress1] = @BusinessAddress1, + [BusinessAddress2] = @BusinessAddress2, + [BusinessAddress3] = @BusinessAddress3, + [BusinessCountry] = @BusinessCountry, + [BusinessTaxNumber] = @BusinessTaxNumber, + [BillingEmail] = @BillingEmail, + [Plan] = @Plan, + [PlanType] = @PlanType, + [Seats] = @Seats, + [MaxCollections] = @MaxCollections, + [UsePolicies] = @UsePolicies, + [UseSso] = @UseSso, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseEvents] = @UseEvents, + [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, + [UseApi] = @UseApi, + [UseResetPassword] = @UseResetPassword, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [Enabled] = @Enabled, + [LicenseKey] = @LicenseKey, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [TwoFactorProviders] = @TwoFactorProviders, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector, + [UseScim] = @UseScim, + [UseCustomPermissions] = @UseCustomPermissions, + [UseSecretsManager] = @UseSecretsManager, + [Status] = @Status, + [UsePasswordManager] = @UsePasswordManager, + [SmSeats] = @SmSeats, + [SmServiceAccounts] = @SmServiceAccounts, + [MaxAutoscaleSmSeats] = @MaxAutoscaleSmSeats, + [MaxAutoscaleSmServiceAccounts] = @MaxAutoscaleSmServiceAccounts, + [SecretsManagerBeta] = @SecretsManagerBeta, + [LimitCollectionCreation] = @LimitCollectionCreation, + [LimitCollectionDeletion] = @LimitCollectionDeletion, + [AllowAdminAccessToAllCollectionItems] = @AllowAdminAccessToAllCollectionItems, + [UseRiskInsights] = @UseRiskInsights, + [LimitItemDeletion] = @LimitItemDeletion + WHERE + [Id] = @Id +END + + +GO diff --git a/util/Migrator/DbScripts/2025-01-22_00_SecurityTaskCreateMany.sql b/util/Migrator/DbScripts/2025-01-22_00_SecurityTaskCreateMany.sql new file mode 100644 index 0000000000..6bf797eccd --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-22_00_SecurityTaskCreateMany.sql @@ -0,0 +1,55 @@ +-- SecurityTask_CreateMany +CREATE OR ALTER PROCEDURE [dbo].[SecurityTask_CreateMany] + @SecurityTasksJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + CREATE TABLE #TempSecurityTasks + ( + [Id] UNIQUEIDENTIFIER, + [OrganizationId] UNIQUEIDENTIFIER, + [CipherId] UNIQUEIDENTIFIER, + [Type] TINYINT, + [Status] TINYINT, + [CreationDate] DATETIME2(7), + [RevisionDate] DATETIME2(7) + ) + + INSERT INTO #TempSecurityTasks + ([Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate]) + SELECT CAST(JSON_VALUE([value], '$.Id') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.OrganizationId') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.CipherId') AS UNIQUEIDENTIFIER), + CAST(JSON_VALUE([value], '$.Type') AS TINYINT), + CAST(JSON_VALUE([value], '$.Status') AS TINYINT), + CAST(JSON_VALUE([value], '$.CreationDate') AS DATETIME2(7)), + CAST(JSON_VALUE([value], '$.RevisionDate') AS DATETIME2(7)) + FROM OPENJSON(@SecurityTasksJson) ST + + INSERT INTO [dbo].[SecurityTask] + ([Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate]) + SELECT [Id], + [OrganizationId], + [CipherId], + [Type], + [Status], + [CreationDate], + [RevisionDate] + FROM #TempSecurityTasks + + DROP TABLE #TempSecurityTasks +END +GO diff --git a/util/Migrator/DbScripts/2025-01-28_00_Add_Organization_ReadAddableToProviderByUserId.sql b/util/Migrator/DbScripts/2025-01-28_00_Add_Organization_ReadAddableToProviderByUserId.sql new file mode 100644 index 0000000000..1255544d19 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-28_00_Add_Organization_ReadAddableToProviderByUserId.sql @@ -0,0 +1,31 @@ +-- Drop existing SPROC +IF OBJECT_ID('[dbo].[Organization_ReadAddableToProviderByUserId') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_ReadAddableToProviderByUserId] +END +GO + +CREATE PROCEDURE [dbo].[Organization_ReadAddableToProviderByUserId] + @UserId UNIQUEIDENTIFIER, + @ProviderType TINYINT +AS +BEGIN + SET NOCOUNT ON + SELECT O.* FROM [dbo].[OrganizationUser] AS OU + JOIN [dbo].[Organization] AS O ON O.[Id] = OU.[OrganizationId] + WHERE + OU.[UserId] = @UserId AND + OU.[Type] = 0 AND + OU.[Status] = 2 AND + O.[Enabled] = 1 AND + O.[GatewayCustomerId] IS NOT NULL AND + O.[GatewaySubscriptionId] IS NOT NULL AND + O.[Seats] > 0 AND + O.[Status] = 1 AND + O.[UseSecretsManager] = 0 AND + -- All Teams & Enterprise for MSP + (@ProviderType = 0 AND O.[PlanType] IN (2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20) OR + -- All Enterprise for MOE + @ProviderType = 2 AND O.[PlanType] IN (4, 5, 10, 11, 14, 15, 19, 20)); +END +GO diff --git a/util/Migrator/DbScripts/2025-01-28_00_UpdateOrganization_UnassignedToProviderSearch.sql b/util/Migrator/DbScripts/2025-01-28_00_UpdateOrganization_UnassignedToProviderSearch.sql new file mode 100644 index 0000000000..07ec9ae8ac --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-28_00_UpdateOrganization_UnassignedToProviderSearch.sql @@ -0,0 +1,54 @@ +-- Drop existing SPROC +IF OBJECT_ID('[dbo].[Organization_UnassignedToProviderSearch]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] + END +GO + +CREATE PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] + @Name NVARCHAR(55), + @OwnerEmail NVARCHAR(256), + @Skip INT = 0, + @Take INT = 25 + WITH RECOMPILE +AS +BEGIN + SET NOCOUNT ON + DECLARE @NameLikeSearch NVARCHAR(55) = '%' + @Name + '%' + DECLARE @OwnerLikeSearch NVARCHAR(55) = @OwnerEmail + '%' + + IF @OwnerEmail IS NOT NULL + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + INNER JOIN + [dbo].[OrganizationUser] OU ON O.[Id] = OU.[OrganizationId] + INNER JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] + WHERE + O.[PlanType] NOT IN (0, 1, 6, 7) -- Not 'Free', 'Custom' or 'Families' + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + AND (U.[Email] LIKE @OwnerLikeSearch) + ORDER BY O.[CreationDate] DESC, O.[Id] + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END + ELSE + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + WHERE + O.[PlanType] NOT IN (0, 1, 6, 7) -- Not 'Free', 'Custom' or 'Families' + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + ORDER BY O.[CreationDate] DESC, O.[Id] + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END +END +GO diff --git a/util/Migrator/DbScripts/2025-01-29_00_AddPlanTypeToProviderOrganizationOrganizationDetailsView.sql b/util/Migrator/DbScripts/2025-01-29_00_AddPlanTypeToProviderOrganizationOrganizationDetailsView.sql new file mode 100644 index 0000000000..df4c145b71 --- /dev/null +++ b/util/Migrator/DbScripts/2025-01-29_00_AddPlanTypeToProviderOrganizationOrganizationDetailsView.sql @@ -0,0 +1,23 @@ +-- Add column 'PlanType' +CREATE OR AlTER VIEW [dbo].[ProviderOrganizationOrganizationDetailsView] +AS +SELECT + PO.[Id], + PO.[ProviderId], + PO.[OrganizationId], + O.[Name] OrganizationName, + PO.[Key], + PO.[Settings], + PO.[CreationDate], + PO.[RevisionDate], + (SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status = 2) UserCount, + (SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status >= 0) OccupiedSeats, + O.[Seats], + O.[Plan], + O.[PlanType], + O.[Status] +FROM + [dbo].[ProviderOrganization] PO + LEFT JOIN + [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId] +GO diff --git a/util/Migrator/DbScripts/2025-02-03_00_OrgUserReadManyDetailsByRole.sql b/util/Migrator/DbScripts/2025-02-03_00_OrgUserReadManyDetailsByRole.sql new file mode 100644 index 0000000000..4d687f0bb1 --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-03_00_OrgUserReadManyDetailsByRole.sql @@ -0,0 +1,16 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadManyDetailsByRole] + @OrganizationId UNIQUEIDENTIFIER, + @Role TINYINT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationUserUserDetailsView] + WHERE + OrganizationId = @OrganizationId + AND Status = 2 -- 2 = Confirmed + AND [Type] = @Role +END diff --git a/util/Migrator/DbScripts/2025-02-03_01_RefreshView_For_LimitItemDeletion.sql b/util/Migrator/DbScripts/2025-02-03_01_RefreshView_For_LimitItemDeletion.sql new file mode 100644 index 0000000000..98893bb030 --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-03_01_RefreshView_For_LimitItemDeletion.sql @@ -0,0 +1,7 @@ +-- Refresh Views + +IF OBJECT_ID('[dbo].[OrganizationView]') IS NOT NULL + BEGIN + EXECUTE sp_refreshview N'[dbo].[OrganizationView]'; + END +GO diff --git a/util/Migrator/DbScripts/2025-02-04_00_CollectionPermissionEditExceptPWPerm.sql b/util/Migrator/DbScripts/2025-02-04_00_CollectionPermissionEditExceptPWPerm.sql new file mode 100644 index 0000000000..95013afaa4 --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-04_00_CollectionPermissionEditExceptPWPerm.sql @@ -0,0 +1,118 @@ +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_ReadByIdUserId] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp], + MAX ([Edit]) AS [Edit], + MAX ([ViewPassword]) AS [ViewPassword] +FROM + [dbo].[UserCipherDetails](@UserId) +WHERE + [Id] = @Id +GROUP BY + [Id], + [UserId], + [OrganizationId], + [Type], + [Data], + [Attachments], + [CreationDate], + [RevisionDate], + [Favorite], + [FolderId], + [DeletedDate], + [Reprompt], + [Key], + [OrganizationUseTotp] +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollections] + @CipherId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Cipher] + WHERE + [Id] = @CipherId + ) + SELECT + C.[Id] + INTO #TempAvailableCollections + FROM + [dbo].[Collection] C + INNER JOIN + [Organization] O ON O.[Id] = C.[OrganizationId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrgId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + CU.[ReadOnly] = 0 + OR CG.[ReadOnly] = 0 + ) + -- Insert new collection assignments + INSERT INTO [dbo].[CollectionCipher] ( + [CollectionId], + [CipherId] + ) + SELECT + [Id], + @CipherId + FROM @CollectionIds + WHERE [Id] IN (SELECT [Id] FROM [#TempAvailableCollections]) + AND NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionCipher] + WHERE [CollectionId] = [@CollectionIds].[Id] + AND [CipherId] = @CipherId + ); + + -- Delete removed collection assignments + DELETE CC + FROM [dbo].[CollectionCipher] CC + WHERE CC.[CipherId] = @CipherId + AND CC.[CollectionId] IN (SELECT [Id] FROM [#TempAvailableCollections]) + AND CC.[CollectionId] NOT IN (SELECT [Id] FROM @CollectionIds); + + IF @OrgId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId + END + DROP TABLE #TempAvailableCollections; +END +GO diff --git a/util/Migrator/DbScripts/2025-02-07_00_AddOptionalNotificationTaskId.sql b/util/Migrator/DbScripts/2025-02-07_00_AddOptionalNotificationTaskId.sql new file mode 100644 index 0000000000..0e19cb5fe2 --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-07_00_AddOptionalNotificationTaskId.sql @@ -0,0 +1,107 @@ +-- Add optional TaskId column to Notification table +IF COL_LENGTH('[dbo].[Notification]', 'TaskId') IS NULL +BEGIN + ALTER TABLE [dbo].[Notification] + ADD [TaskId] UNIQUEIDENTIFIER NULL + + ALTER TABLE [dbo].[Notification] + ADD CONSTRAINT [FK_Notification_SecurityTask] FOREIGN KEY ([TaskId]) REFERENCES [dbo].[SecurityTask] ([Id]) +END +GO + +IF NOT EXISTS (SELECT * + FROM sys.indexes + WHERE name = 'IX_Notification_TaskId') + BEGIN + CREATE NONCLUSTERED INDEX [IX_Notification_TaskId] + ON [dbo].[Notification] ([TaskId] ASC) WHERE TaskId IS NOT NULL; + END +GO + +-- Alter Notification_Create and Notification_Update stored procedures to include TaskId +CREATE OR ALTER PROCEDURE [dbo].[Notification_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Priority TINYINT, + @Global BIT, + @ClientType TINYINT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Title NVARCHAR(256), + @Body NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @TaskId UNIQUEIDENTIFIER = NULL +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Notification] ( + [Id], + [Priority], + [Global], + [ClientType], + [UserId], + [OrganizationId], + [Title], + [Body], + [CreationDate], + [RevisionDate], + [TaskId] + ) + VALUES ( + @Id, + @Priority, + @Global, + @ClientType, + @UserId, + @OrganizationId, + @Title, + @Body, + @CreationDate, + @RevisionDate, + @TaskId + ) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Notification_Update] + @Id UNIQUEIDENTIFIER, + @Priority TINYINT, + @Global BIT, + @ClientType TINYINT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Title NVARCHAR(256), + @Body NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @TaskId UNIQUEIDENTIFIER = NULL +AS +BEGIN + SET NOCOUNT ON + + UPDATE [dbo].[Notification] + SET [Priority] = @Priority, + [Global] = @Global, + [ClientType] = @ClientType, + [UserId] = @UserId, + [OrganizationId] = @OrganizationId, + [Title] = @Title, + [Body] = @Body, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [TaskId] = @TaskId + WHERE [Id] = @Id +END +GO + +-- Recreate NotificationView +CREATE OR ALTER VIEW [dbo].[NotificationView] +AS +SELECT + * +FROM + [dbo].[Notification] +GO + + diff --git a/util/Migrator/DbScripts/2025-02-13_00_GroupUser_AddUsers.sql b/util/Migrator/DbScripts/2025-02-13_00_GroupUser_AddUsers.sql new file mode 100644 index 0000000000..46ea72003e --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-13_00_GroupUser_AddUsers.sql @@ -0,0 +1,39 @@ +CREATE OR ALTER PROCEDURE [dbo].[GroupUser_AddUsers] + @GroupId UNIQUEIDENTIFIER, + @OrganizationUserIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Group] + WHERE + [Id] = @GroupId + ) + + -- Insert + INSERT INTO + [dbo].[GroupUser] (GroupId, OrganizationUserId) + SELECT DISTINCT + @GroupId, + [Source].[Id] + FROM + @OrganizationUserIds AS [Source] + INNER JOIN + [dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[GroupUser] + WHERE + [GroupId] = @GroupId + AND [OrganizationUserId] = [Source].[Id] + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId +END diff --git a/util/Migrator/DbScripts/2025-02-14_00_PolicyDetails_ReadByUserId.sql b/util/Migrator/DbScripts/2025-02-14_00_PolicyDetails_ReadByUserId.sql new file mode 100644 index 0000000000..d50a092e18 --- /dev/null +++ b/util/Migrator/DbScripts/2025-02-14_00_PolicyDetails_ReadByUserId.sql @@ -0,0 +1,43 @@ +CREATE OR ALTER PROCEDURE [dbo].[PolicyDetails_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON +SELECT + OU.[Id] AS OrganizationUserId, + P.[OrganizationId], + P.[Type] AS PolicyType, + P.[Data] AS PolicyData, + OU.[Type] AS OrganizationUserType, + OU.[Status] AS OrganizationUserStatus, + OU.[Permissions] AS OrganizationUserPermissionsData, + CASE WHEN EXISTS ( + SELECT 1 + FROM [dbo].[ProviderUserView] PU + INNER JOIN [dbo].[ProviderOrganizationView] PO ON PO.[ProviderId] = PU.[ProviderId] + WHERE PU.[UserId] = OU.[UserId] AND PO.[OrganizationId] = P.[OrganizationId] + ) THEN 1 ELSE 0 END AS IsProvider +FROM [dbo].[PolicyView] P +INNER JOIN [dbo].[OrganizationUserView] OU + ON P.[OrganizationId] = OU.[OrganizationId] +INNER JOIN [dbo].[OrganizationView] O + ON P.[OrganizationId] = O.[Id] +WHERE + P.Enabled = 1 + AND O.Enabled = 1 + AND O.UsePolicies = 1 + AND ( + -- OrgUsers who have accepted their invite and are linked to a UserId + -- (Note: this excludes "invited but revoked" users who don't have an OU.UserId yet, + -- but those users will go through policy enforcement later as part of accepting their invite after being restored. + -- This is an intentionally unhandled edge case for now.) + (OU.[Status] != 0 AND OU.[UserId] = @UserId) + + -- 'Invited' OrgUsers are not linked to a UserId yet, so we have to look up their email + OR EXISTS ( + SELECT 1 + FROM [dbo].[UserView] U + WHERE U.[Id] = @UserId AND OU.[Email] = U.[Email] AND OU.[Status] = 0 + ) + ) +END diff --git a/util/MySqlMigrations/Migrations/20231214162533_GrantIdWithIndexes.cs b/util/MySqlMigrations/Migrations/20231214162533_GrantIdWithIndexes.cs index 1e4c178ade..e65a4dc6bf 100644 --- a/util/MySqlMigrations/Migrations/20231214162533_GrantIdWithIndexes.cs +++ b/util/MySqlMigrations/Migrations/20231214162533_GrantIdWithIndexes.cs @@ -74,13 +74,13 @@ public partial class GrantIdWithIndexes : Migration migrationBuilder.Sql(@" DROP PROCEDURE IF EXISTS GrantSchemaChange; - + CREATE PROCEDURE GrantSchemaChange() BEGIN - IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Grant' AND COLUMN_NAME = 'Id') THEN + IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Grant' AND COLUMN_NAME = 'Id' AND TABLE_SCHEMA=database()) THEN ALTER TABLE `Grant` DROP COLUMN `Id`; END IF; - + ALTER TABLE `Grant` ADD COLUMN `Id` INT AUTO_INCREMENT UNIQUE; END; diff --git a/util/MySqlMigrations/Migrations/20250116163214_DropLimitCollectionCreationDeletion.Designer.cs b/util/MySqlMigrations/Migrations/20250116163214_DropLimitCollectionCreationDeletion.Designer.cs new file mode 100644 index 0000000000..8431d0d4ed --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250116163214_DropLimitCollectionCreationDeletion.Designer.cs @@ -0,0 +1,2994 @@ +// +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("20250116163214_DropLimitCollectionCreationDeletion")] + partial class DropLimitCollectionCreationDeletion + { + /// + 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("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasColumnType("longtext"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116163214_DropLimitCollectionCreationDeletion.cs b/util/MySqlMigrations/Migrations/20250116163214_DropLimitCollectionCreationDeletion.cs new file mode 100644 index 0000000000..3248d1df9d --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250116163214_DropLimitCollectionCreationDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class DropLimitCollectionCreationDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } +} diff --git a/util/MySqlMigrations/Migrations/20250116221304_LimitItemDeletion.Designer.cs b/util/MySqlMigrations/Migrations/20250116221304_LimitItemDeletion.Designer.cs new file mode 100644 index 0000000000..19dbdcdead --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250116221304_LimitItemDeletion.Designer.cs @@ -0,0 +1,2997 @@ +// +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("20250116221304_LimitItemDeletion")] + partial class LimitItemDeletion + { + /// + 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("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasColumnType("longtext"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116221304_LimitItemDeletion.cs b/util/MySqlMigrations/Migrations/20250116221304_LimitItemDeletion.cs new file mode 100644 index 0000000000..19aa5a55a9 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250116221304_LimitItemDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class LimitItemDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitItemDeletion", + table: "Organization", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitItemDeletion", + table: "Organization"); + } +} diff --git a/util/MySqlMigrations/Migrations/20250207204741_AddOptionalNotificationTaskId.Designer.cs b/util/MySqlMigrations/Migrations/20250207204741_AddOptionalNotificationTaskId.Designer.cs new file mode 100644 index 0000000000..886f35f6d1 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250207204741_AddOptionalNotificationTaskId.Designer.cs @@ -0,0 +1,3009 @@ +// +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("20250207204741_AddOptionalNotifificationTaskId")] + partial class AddOptionalNotifificationTaskId + { + /// + 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("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasColumnType("longtext"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250207204741_AddOptionalNotificationTaskId.cs b/util/MySqlMigrations/Migrations/20250207204741_AddOptionalNotificationTaskId.cs new file mode 100644 index 0000000000..1ead4bcc99 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250207204741_AddOptionalNotificationTaskId.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class AddOptionalNotifificationTaskId : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TaskId", + table: "Notification", + type: "char(36)", + nullable: true, + collation: "ascii_general_ci"); + + migrationBuilder.CreateIndex( + name: "IX_Notification_TaskId", + table: "Notification", + column: "TaskId"); + + migrationBuilder.AddForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification", + column: "TaskId", + principalTable: "SecurityTask", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification"); + + migrationBuilder.DropIndex( + name: "IX_Notification_TaskId", + table: "Notification"); + + migrationBuilder.DropColumn( + name: "TaskId", + table: "Notification"); + } +} diff --git a/util/MySqlMigrations/Migrations/20250213120818_NotificationCenterBodyLength.Designer.cs b/util/MySqlMigrations/Migrations/20250213120818_NotificationCenterBodyLength.Designer.cs new file mode 100644 index 0000000000..78771a45b7 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250213120818_NotificationCenterBodyLength.Designer.cs @@ -0,0 +1,3010 @@ +// +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("20250213120818_NotificationCenterBodyLength")] + partial class NotificationCenterBodyLength + { + /// + 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("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250213120818_NotificationCenterBodyLength.cs b/util/MySqlMigrations/Migrations/20250213120818_NotificationCenterBodyLength.cs new file mode 100644 index 0000000000..47888410b1 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250213120818_NotificationCenterBodyLength.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class NotificationCenterBodyLength : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Body", + table: "Notification", + type: "varchar(3000)", + maxLength: 3000, + nullable: true, + oldClrType: typeof(string), + oldType: "longtext", + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Body", + table: "Notification", + type: "longtext", + nullable: true, + oldClrType: typeof(string), + oldType: "varchar(3000)", + oldMaxLength: 3000, + oldNullable: true) + .Annotation("MySql:CharSet", "utf8mb4") + .OldAnnotation("MySql:CharSet", "utf8mb4"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index dcc525c433..73ed8e0c6b 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -91,10 +91,10 @@ namespace Bit.MySqlMigrations.Migrations b.Property("LimitCollectionCreation") .HasColumnType("tinyint(1)"); - b.Property("LimitCollectionCreationDeletion") + b.Property("LimitCollectionDeletion") .HasColumnType("tinyint(1)"); - b.Property("LimitCollectionDeletion") + b.Property("LimitItemDeletion") .HasColumnType("tinyint(1)"); b.Property("MaxAutoscaleSeats") @@ -1144,35 +1144,6 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("GroupUser", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => - { - b.Property("Id") - .HasColumnType("char(36)"); - - b.Property("CreationDate") - .HasColumnType("datetime(6)"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("varchar(256)"); - - b.Property("Enabled") - .HasColumnType("tinyint(1)"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("varchar(150)"); - - b.Property("LastActivityDate") - .HasColumnType("datetime(6)"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => { b.Property("Id") @@ -1683,7 +1654,8 @@ namespace Bit.MySqlMigrations.Migrations .HasColumnType("char(36)"); b.Property("Body") - .HasColumnType("longtext"); + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); b.Property("ClientType") .HasColumnType("tinyint unsigned"); @@ -1703,6 +1675,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("RevisionDate") .HasColumnType("datetime(6)"); + b.Property("TaskId") + .HasColumnType("char(36)"); + b.Property("Title") .HasMaxLength(256) .HasColumnType("varchar(256)"); @@ -1716,6 +1691,9 @@ namespace Bit.MySqlMigrations.Migrations b.HasIndex("OrganizationId") .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("UserId") .HasAnnotation("SqlServer:Clustered", false); @@ -1748,6 +1726,35 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("NotificationStatus", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -2373,7 +2380,7 @@ namespace Bit.MySqlMigrations.Migrations modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation") + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") .WithMany() .HasForeignKey("InstallationId") .OnDelete(DeleteBehavior.Cascade) @@ -2628,12 +2635,18 @@ namespace Bit.MySqlMigrations.Migrations .WithMany() .HasForeignKey("OrganizationId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") .WithMany() .HasForeignKey("UserId"); b.Navigation("Organization"); + b.Navigation("Task"); + b.Navigation("User"); }); diff --git a/util/PostgresMigrations/Migrations/20250116163219_DropLimitCollectionCreationDeletion.Designer.cs b/util/PostgresMigrations/Migrations/20250116163219_DropLimitCollectionCreationDeletion.Designer.cs new file mode 100644 index 0000000000..e88fa1485c --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250116163219_DropLimitCollectionCreationDeletion.Designer.cs @@ -0,0 +1,3000 @@ +// +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("20250116163219_DropLimitCollectionCreationDeletion")] + partial class DropLimitCollectionCreationDeletion + { + /// + 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("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116163219_DropLimitCollectionCreationDeletion.cs b/util/PostgresMigrations/Migrations/20250116163219_DropLimitCollectionCreationDeletion.cs new file mode 100644 index 0000000000..677dec53ac --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250116163219_DropLimitCollectionCreationDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class DropLimitCollectionCreationDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization", + type: "boolean", + nullable: false, + defaultValue: false); + } +} diff --git a/util/PostgresMigrations/Migrations/20250116221314_LimitItemDeletion.Designer.cs b/util/PostgresMigrations/Migrations/20250116221314_LimitItemDeletion.Designer.cs new file mode 100644 index 0000000000..90799c7699 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250116221314_LimitItemDeletion.Designer.cs @@ -0,0 +1,3003 @@ +// +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("20250116221314_LimitItemDeletion")] + partial class LimitItemDeletion + { + /// + 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("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116221314_LimitItemDeletion.cs b/util/PostgresMigrations/Migrations/20250116221314_LimitItemDeletion.cs new file mode 100644 index 0000000000..380ecf507e --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250116221314_LimitItemDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class LimitItemDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitItemDeletion", + table: "Organization", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitItemDeletion", + table: "Organization"); + } +} diff --git a/util/PostgresMigrations/Migrations/20250207204729_AddOptionalNotificationTaskId.Designer.cs b/util/PostgresMigrations/Migrations/20250207204729_AddOptionalNotificationTaskId.Designer.cs new file mode 100644 index 0000000000..57da6883fa --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250207204729_AddOptionalNotificationTaskId.Designer.cs @@ -0,0 +1,3015 @@ +// +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("20250207204729_AddOptionalNotifificationTaskId")] + partial class AddOptionalNotifificationTaskId + { + /// + 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("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250207204729_AddOptionalNotificationTaskId.cs b/util/PostgresMigrations/Migrations/20250207204729_AddOptionalNotificationTaskId.cs new file mode 100644 index 0000000000..3824d7af4d --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250207204729_AddOptionalNotificationTaskId.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class AddOptionalNotifificationTaskId : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TaskId", + table: "Notification", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Notification_TaskId", + table: "Notification", + column: "TaskId"); + + migrationBuilder.AddForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification", + column: "TaskId", + principalTable: "SecurityTask", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification"); + + migrationBuilder.DropIndex( + name: "IX_Notification_TaskId", + table: "Notification"); + + migrationBuilder.DropColumn( + name: "TaskId", + table: "Notification"); + } +} diff --git a/util/PostgresMigrations/Migrations/20250213120809_NotificationCenterBodyLength.Designer.cs b/util/PostgresMigrations/Migrations/20250213120809_NotificationCenterBodyLength.Designer.cs new file mode 100644 index 0000000000..12c3821158 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250213120809_NotificationCenterBodyLength.Designer.cs @@ -0,0 +1,3016 @@ +// +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("20250213120809_NotificationCenterBodyLength")] + partial class NotificationCenterBodyLength + { + /// + 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("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250213120809_NotificationCenterBodyLength.cs b/util/PostgresMigrations/Migrations/20250213120809_NotificationCenterBodyLength.cs new file mode 100644 index 0000000000..11aac4ef56 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250213120809_NotificationCenterBodyLength.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class NotificationCenterBodyLength : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Body", + table: "Notification", + type: "character varying(3000)", + maxLength: 3000, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Body", + table: "Notification", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(3000)", + oldMaxLength: 3000, + oldNullable: true); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 971ba96310..a6017652bf 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -93,10 +93,10 @@ namespace Bit.PostgresMigrations.Migrations b.Property("LimitCollectionCreation") .HasColumnType("boolean"); - b.Property("LimitCollectionCreationDeletion") + b.Property("LimitCollectionDeletion") .HasColumnType("boolean"); - b.Property("LimitCollectionDeletion") + b.Property("LimitItemDeletion") .HasColumnType("boolean"); b.Property("MaxAutoscaleSeats") @@ -1149,35 +1149,6 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("GroupUser", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => - { - b.Property("Id") - .HasColumnType("uuid"); - - b.Property("CreationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("character varying(150)"); - - b.Property("LastActivityDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => { b.Property("Id") @@ -1689,7 +1660,8 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("Body") - .HasColumnType("text"); + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); b.Property("ClientType") .HasColumnType("smallint"); @@ -1709,6 +1681,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("RevisionDate") .HasColumnType("timestamp with time zone"); + b.Property("TaskId") + .HasColumnType("uuid"); + b.Property("Title") .HasMaxLength(256) .HasColumnType("character varying(256)"); @@ -1722,6 +1697,9 @@ namespace Bit.PostgresMigrations.Migrations b.HasIndex("OrganizationId") .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("UserId") .HasAnnotation("SqlServer:Clustered", false); @@ -1754,6 +1732,35 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("NotificationStatus", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -2379,7 +2386,7 @@ namespace Bit.PostgresMigrations.Migrations modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation") + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") .WithMany() .HasForeignKey("InstallationId") .OnDelete(DeleteBehavior.Cascade) @@ -2634,12 +2641,18 @@ namespace Bit.PostgresMigrations.Migrations .WithMany() .HasForeignKey("OrganizationId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") .WithMany() .HasForeignKey("UserId"); b.Navigation("Organization"); + b.Navigation("Task"); + b.Navigation("User"); }); diff --git a/util/SqliteMigrations/Migrations/20250116163222_DropLimitCollectionCreationDeletion.Designer.cs b/util/SqliteMigrations/Migrations/20250116163222_DropLimitCollectionCreationDeletion.Designer.cs new file mode 100644 index 0000000000..53111def25 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250116163222_DropLimitCollectionCreationDeletion.Designer.cs @@ -0,0 +1,2983 @@ +// +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("20250116163222_DropLimitCollectionCreationDeletion")] + partial class DropLimitCollectionCreationDeletion + { + /// + 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("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116163222_DropLimitCollectionCreationDeletion.cs b/util/SqliteMigrations/Migrations/20250116163222_DropLimitCollectionCreationDeletion.cs new file mode 100644 index 0000000000..34275a491e --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250116163222_DropLimitCollectionCreationDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class DropLimitCollectionCreationDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitCollectionCreationDeletion", + table: "Organization", + type: "INTEGER", + nullable: false, + defaultValue: false); + } +} diff --git a/util/SqliteMigrations/Migrations/20250116221310_LimitItemDeletion.Designer.cs b/util/SqliteMigrations/Migrations/20250116221310_LimitItemDeletion.Designer.cs new file mode 100644 index 0000000000..91015f9300 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250116221310_LimitItemDeletion.Designer.cs @@ -0,0 +1,2986 @@ +// +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("20250116221310_LimitItemDeletion")] + partial class LimitItemDeletion + { + /// + 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("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250116221310_LimitItemDeletion.cs b/util/SqliteMigrations/Migrations/20250116221310_LimitItemDeletion.cs new file mode 100644 index 0000000000..ded7357312 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250116221310_LimitItemDeletion.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class LimitItemDeletion : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LimitItemDeletion", + table: "Organization", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LimitItemDeletion", + table: "Organization"); + } +} diff --git a/util/SqliteMigrations/Migrations/20250207204735_AddOptionalNotificationTaskId.Designer.cs b/util/SqliteMigrations/Migrations/20250207204735_AddOptionalNotificationTaskId.Designer.cs new file mode 100644 index 0000000000..98d0871ec1 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250207204735_AddOptionalNotificationTaskId.Designer.cs @@ -0,0 +1,2998 @@ +// +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("20250207204735_AddOptionalNotifificationTaskId")] + partial class AddOptionalNotifificationTaskId + { + /// + 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("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250207204735_AddOptionalNotificationTaskId.cs b/util/SqliteMigrations/Migrations/20250207204735_AddOptionalNotificationTaskId.cs new file mode 100644 index 0000000000..8b3b1d3096 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250207204735_AddOptionalNotificationTaskId.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class AddOptionalNotifificationTaskId : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "TaskId", + table: "Notification", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Notification_TaskId", + table: "Notification", + column: "TaskId"); + + migrationBuilder.AddForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification", + column: "TaskId", + principalTable: "SecurityTask", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Notification_SecurityTask_TaskId", + table: "Notification"); + + migrationBuilder.DropIndex( + name: "IX_Notification_TaskId", + table: "Notification"); + + migrationBuilder.DropColumn( + name: "TaskId", + table: "Notification"); + } +} diff --git a/util/SqliteMigrations/Migrations/20250213120814_NotificationCenterBodyLength.Designer.cs b/util/SqliteMigrations/Migrations/20250213120814_NotificationCenterBodyLength.Designer.cs new file mode 100644 index 0000000000..91b7b87e88 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250213120814_NotificationCenterBodyLength.Designer.cs @@ -0,0 +1,2999 @@ +// +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("20250213120814_NotificationCenterBodyLength")] + partial class NotificationCenterBodyLength + { + /// + 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("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Date", "OrganizationId", "ActingUserId", "CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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/20250213120814_NotificationCenterBodyLength.cs b/util/SqliteMigrations/Migrations/20250213120814_NotificationCenterBodyLength.cs new file mode 100644 index 0000000000..0dac35755d --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250213120814_NotificationCenterBodyLength.cs @@ -0,0 +1,21 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class NotificationCenterBodyLength : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index d9be32398b..185caf3074 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -86,10 +86,10 @@ namespace Bit.SqliteMigrations.Migrations b.Property("LimitCollectionCreation") .HasColumnType("INTEGER"); - b.Property("LimitCollectionCreationDeletion") + b.Property("LimitCollectionDeletion") .HasColumnType("INTEGER"); - b.Property("LimitCollectionDeletion") + b.Property("LimitItemDeletion") .HasColumnType("INTEGER"); b.Property("MaxAutoscaleSeats") @@ -1133,35 +1133,6 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("GroupUser", (string)null); }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("CreationDate") - .HasColumnType("TEXT"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("Enabled") - .HasColumnType("INTEGER"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(150) - .HasColumnType("TEXT"); - - b.Property("LastActivityDate") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Installation", (string)null); - }); - modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => { b.Property("Id") @@ -1672,6 +1643,7 @@ namespace Bit.SqliteMigrations.Migrations .HasColumnType("TEXT"); b.Property("Body") + .HasMaxLength(3000) .HasColumnType("TEXT"); b.Property("ClientType") @@ -1692,6 +1664,9 @@ namespace Bit.SqliteMigrations.Migrations b.Property("RevisionDate") .HasColumnType("TEXT"); + b.Property("TaskId") + .HasColumnType("TEXT"); + b.Property("Title") .HasMaxLength(256) .HasColumnType("TEXT"); @@ -1705,6 +1680,9 @@ namespace Bit.SqliteMigrations.Migrations b.HasIndex("OrganizationId") .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + b.HasIndex("UserId") .HasAnnotation("SqlServer:Clustered", false); @@ -1737,6 +1715,35 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("NotificationStatus", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => { b.Property("Id") @@ -2362,7 +2369,7 @@ namespace Bit.SqliteMigrations.Migrations modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => { - b.HasOne("Bit.Infrastructure.EntityFramework.Models.Installation", "Installation") + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") .WithMany() .HasForeignKey("InstallationId") .OnDelete(DeleteBehavior.Cascade) @@ -2617,12 +2624,18 @@ namespace Bit.SqliteMigrations.Migrations .WithMany() .HasForeignKey("OrganizationId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId"); + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") .WithMany() .HasForeignKey("UserId"); b.Navigation("Organization"); + b.Navigation("Task"); + b.Navigation("User"); });