diff --git a/.devcontainer/bitwarden_common/docker-compose.yml b/.devcontainer/bitwarden_common/docker-compose.yml index 2f3a62877e..fbfca10d21 100644 --- a/.devcontainer/bitwarden_common/docker-compose.yml +++ b/.devcontainer/bitwarden_common/docker-compose.yml @@ -3,6 +3,9 @@ services: image: mcr.microsoft.com/devcontainers/dotnet:8.0 volumes: - ../../:/workspace:cached + env_file: + - path: ../../dev/.env + required: false # Overrides default command so things don't shut down after the process ends. command: sleep infinity diff --git a/.devcontainer/internal_dev/postCreateCommand.sh b/.devcontainer/internal_dev/postCreateCommand.sh index 071ffc0b29..3fd278be26 100755 --- a/.devcontainer/internal_dev/postCreateCommand.sh +++ b/.devcontainer/internal_dev/postCreateCommand.sh @@ -1,17 +1,42 @@ #!/usr/bin/env bash -export DEV_DIR=/workspace/dev +export REPO_ROOT="$(git rev-parse --show-toplevel)" export CONTAINER_CONFIG=/workspace/.devcontainer/internal_dev + git config --global --add safe.directory /workspace -get_installation_id_and_key() { - pushd ./dev >/dev/null || exit - echo "Please enter your installation id and key from https://bitwarden.com/host:" - read -r -p "Installation id: " INSTALLATION_ID - read -r -p "Installation key: " INSTALLATION_KEY - jq ".globalSettings.installation.id = \"$INSTALLATION_ID\" | - .globalSettings.installation.key = \"$INSTALLATION_KEY\"" \ - secrets.json.example >secrets.json # create/overwrite secrets.json - popd >/dev/null || exit +if [[ -z "${CODESPACES}" ]]; then + allow_interactive=1 +else + echo "Doing non-interactive setup" + allow_interactive=0 +fi + +get_option() { + # Helper function for reading the value of an environment variable + # primarily but then falling back to an interactive question if allowed + # and lastly falling back to a default value input when either other + # option is available. + name_of_var="$1" + question_text="$2" + default_value="$3" + is_secret="$4" + + if [[ -n "${!name_of_var}" ]]; then + # If the env variable they gave us has a value, then use that value + echo "${!name_of_var}" + elif [[ "$allow_interactive" == 1 ]]; then + # If we can be interactive, then use the text they gave us to request input + if [[ "$is_secret" == 1 ]]; then + read -r -s -p "$question_text" response + echo "$response" + else + read -r -p "$question_text" response + echo "$response" + fi + else + # If no environment variable and not interactive, then just give back default value + echo "$default_value" + fi } remove_comments() { @@ -26,51 +51,70 @@ remove_comments() { configure_other_vars() { pushd ./dev >/dev/null || exit - cp secrets.json .secrets.json.tmp + cp "$REPO_ROOT/dev/secrets.json" "$REPO_ROOT/dev/.secrets.json.tmp" # set DB_PASSWORD equal to .services.mssql.environment.MSSQL_SA_PASSWORD, accounting for quotes - DB_PASSWORD="$(grep -oP 'MSSQL_SA_PASSWORD=["'"'"']?\K[^"'"'"'\s]+' $DEV_DIR/.env)" + DB_PASSWORD="$(grep -oP 'MSSQL_SA_PASSWORD=["'"'"']?\K[^"'"'"'\s]+' $REPO_ROOT/dev/.env)" SQL_CONNECTION_STRING="Server=localhost;Database=vault_dev;User Id=SA;Password=$DB_PASSWORD;Encrypt=True;TrustServerCertificate=True" jq \ ".globalSettings.sqlServer.connectionString = \"$SQL_CONNECTION_STRING\" | .globalSettings.postgreSql.connectionString = \"Host=localhost;Username=postgres;Password=$DB_PASSWORD;Database=vault_dev;Include Error Detail=true\" | .globalSettings.mySql.connectionString = \"server=localhost;uid=root;pwd=$DB_PASSWORD;database=vault_dev\"" \ .secrets.json.tmp >secrets.json - rm .secrets.json.tmp + rm "$REPO_ROOT/dev/.secrets.json.tmp" popd >/dev/null || exit } one_time_setup() { - read -r -p \ - "Would you like to configure your secrets and certificates for the first time? -WARNING: This will overwrite any existing secrets.json and certificate files. -Proceed? [y/N] " response - if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - echo "Running one-time setup script..." - sleep 1 - read -r -p \ - "Place the secrets.json and dev.pfx files from our shared Collection in the ./dev directory. + if [[ ! -f "$REPO_ROOT/dev/dev.pfx" ]]; then + # We do not have the cert file + if [[ ! -z "${DEV_CERT_CONTENTS}" ]]; then + # Make file for them + echo "Making $REPO_ROOT/dev/dev.pfx file for you based on DEV_CERT_CONTENTS environment variable." + # Assume content is base64 encoded + echo "$DEV_CERT_CONTENTS" | base64 -d > "$REPO_ROOT/dev/dev.pfx" + else + if [[ $allow_interactive -eq 1 ]]; then + read -r -p \ + "Place the dev.pfx files from our shared Collection in the $REPO_ROOT/dev directory. Press to continue." - remove_comments ./dev/secrets.json + fi + fi + fi + + if [[ -f "$REPO_ROOT/dev/dev.pfx" ]]; then + dotnet tool install dotnet-certificate-tool -g >/dev/null + cert_password="$(get_option "DEV_CERT_PASSWORD" "Paste the \"Licensing Certificate - Dev\" password: " "" 1)" + certificate-tool add --file "$REPO_ROOT/dev/dev.pfx" --password "$cert_password" + else + echo "You don't have a $REPO_ROOT/dev/dev.pfx file setup." >/dev/stderr + fi + + do_secrets_json_setup="$(get_option "SETUP_SECRETS_JSON" "Would you like us to setup your secrets.json file for you? [y/N] " "n")" + if [[ "$do_secrets_json_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + remove_comments "$REPO_ROOT/dev/secrets.json" configure_other_vars + # setup_secrets needs to be ran from the dev folder + pushd "$REPO_ROOT/dev" >/dev/null || exit + echo "Injecting dotnet secrets..." + pwsh "$REPO_ROOT/dev/setup_secrets.ps1" || true + popd >/dev/null || exit + fi + + do_azurite_setup="$(get_option "SETUP_AZURITE" "Would you like us to setup your azurite environment? [y/N] " "n")" + if [[ "$do_azurite_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "Installing Az module. This will take ~a minute..." pwsh -Command "Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force" - pwsh ./dev/setup_azurite.ps1 - - dotnet tool install dotnet-certificate-tool -g >/dev/null - - read -r -s -p "Paste the \"Licensing Certificate - Dev\" password: " CERT_PASSWORD - echo - pushd ./dev >/dev/null || exit - certificate-tool add --file ./dev.pfx --password "$CERT_PASSWORD" - echo "Injecting dotnet secrets..." - pwsh ./setup_secrets.ps1 || true - popd >/dev/null || exit + pwsh "$REPO_ROOT/dev/setup_azurite.ps1" + fi + run_mssql_migrations="$(get_option "RUN_MSSQL_MIGRATIONS" "Would you like us to run MSSQL Migrations for you? [y/N] " "n")" + if [[ "$do_azurite_setup" =~ ^([yY][eE][sS]|[yY])+$ ]]; then echo "Running migrations..." sleep 5 # wait for DB container to start - dotnet run --project ./util/MsSqlMigratorUtility "$SQL_CONNECTION_STRING" + dotnet run --project "$REPO_ROOT/util/MsSqlMigratorUtility" "$SQL_CONNECTION_STRING" fi - read -r -p "Would you like to install the Stripe CLI? [y/N] " stripe_response + + stripe_response="$(get_option "INSTALL_STRIPE_CLI" "Would you like to install the Stripe CLI? [y/N] " "n")" if [[ "$stripe_response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then install_stripe_cli fi @@ -88,11 +132,4 @@ install_stripe_cli() { sudo apt install -y stripe } -# main -if [[ -z "${CODESPACES}" ]]; then - one_time_setup -else - # Ignore interactive elements when running in codespaces since they are not supported there - # TODO Write codespaces specific instructions and link here - echo "Running in codespaces, follow instructions here: https://contributing.bitwarden.com/getting-started/server/guide/ to continue the setup" -fi \ No newline at end of file +one_time_setup diff --git a/.github/workflows/enforce-labels.yml b/.github/workflows/enforce-labels.yml index 11d5654937..353127c751 100644 --- a/.github/workflows/enforce-labels.yml +++ b/.github/workflows/enforce-labels.yml @@ -4,6 +4,9 @@ on: workflow_call: pull_request: types: [labeled, unlabeled, opened, reopened, synchronize] + +permissions: {} + jobs: enforce-label: if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') || contains(github.event.*.labels.*.name, 'ephemeral-environment') }} diff --git a/.github/workflows/protect-files.yml b/.github/workflows/protect-files.yml index 89d6d4c6d9..546b8344a6 100644 --- a/.github/workflows/protect-files.yml +++ b/.github/workflows/protect-files.yml @@ -16,6 +16,9 @@ jobs: changed-files: name: Check for file changes runs-on: ubuntu-22.04 + permissions: + contents: read + pull-requests: write outputs: changes: ${{steps.check-changes.outputs.changes_detected}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a9cc2d966..c62587fe39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,8 @@ jobs: name: Create GitHub release runs-on: ubuntu-22.04 needs: setup + permissions: + contents: write steps: - name: Download latest release Docker stubs if: ${{ inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index c59938deb8..a59bbcfa6c 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -22,11 +22,12 @@ on: required: false type: string +permissions: {} + jobs: setup: name: Setup runs-on: ubuntu-24.04 - permissions: {} outputs: branch: ${{ steps.set-branch.outputs.branch }} steps: @@ -45,74 +46,12 @@ jobs: echo "branch=$BRANCH" >> $GITHUB_OUTPUT - - cut_branch: - name: Cut branch - if: ${{ needs.setup.outputs.branch != 'none' }} - needs: setup - permissions: - contents: write - id-token: write - runs-on: ubuntu-24.04 - steps: - - name: Log in to Azure - id: azure-login - uses: bitwarden/gh-actions/azure-login@main - with: - subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - tenant_id: ${{ secrets.AZURE_TENANT_ID }} - client_id: ${{ secrets.AZURE_CLIENT_ID }} - - - name: Get Azure Key Vault secrets - id: get-kv-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: gh-org-bitwarden - secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - - - name: Log out from Azure - uses: bitwarden/gh-actions/azure-logout@main - - - name: Generate GH App token - uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 - id: app-token - with: - app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} - private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} - - - name: Check out target ref - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ inputs.target_ref }} - token: ${{ steps.app-token.outputs.token }} - - - name: Check if ${{ needs.setup.outputs.branch }} branch exists - env: - BRANCH_NAME: ${{ needs.setup.outputs.branch }} - run: | - if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then - echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: Cut branch - env: - BRANCH_NAME: ${{ needs.setup.outputs.branch }} - run: | - git switch --quiet --create $BRANCH_NAME - git push --quiet --set-upstream origin $BRANCH_NAME - - bump_version: name: Bump Version if: ${{ always() }} runs-on: ubuntu-24.04 needs: - - cut_branch - setup - permissions: - contents: write - id-token: write outputs: version: ${{ steps.set-final-version-output.outputs.version }} steps: @@ -122,30 +61,12 @@ jobs: with: version: ${{ inputs.version_number_override }} - - name: Log in to Azure - id: azure-login - uses: bitwarden/gh-actions/azure-login@main - with: - subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - tenant_id: ${{ secrets.AZURE_TENANT_ID }} - client_id: ${{ secrets.AZURE_CLIENT_ID }} - - - name: Get Azure Key Vault secrets - id: get-kv-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: gh-org-bitwarden - secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - - - name: Log out from Azure - uses: bitwarden/gh-actions/azure-logout@main - - name: Generate GH App token uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: - app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} - private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} - name: Check out branch uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -230,120 +151,45 @@ jobs: - name: Push changes run: git push - - cherry_pick: - name: Cherry-Pick Commit(s) + cut_branch: + name: Cut branch if: ${{ needs.setup.outputs.branch != 'none' }} - runs-on: ubuntu-24.04 needs: - - bump_version - setup - permissions: - contents: write - id-token: write + - bump_version + runs-on: ubuntu-24.04 steps: - - name: Log in to Azure - id: azure-login - uses: bitwarden/gh-actions/azure-login@main - with: - subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - tenant_id: ${{ secrets.AZURE_TENANT_ID }} - client_id: ${{ secrets.AZURE_CLIENT_ID }} - - - name: Get Azure Key Vault secrets - id: get-kv-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@main - with: - keyvault: gh-org-bitwarden - secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - - - name: Log out from Azure - uses: bitwarden/gh-actions/azure-logout@main - - name: Generate GH App token uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1 id: app-token with: - app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} - private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + app-id: ${{ secrets.BW_GHAPP_ID }} + private-key: ${{ secrets.BW_GHAPP_KEY }} - - name: Check out main branch + - name: Check out target ref uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - fetch-depth: 0 - ref: main + ref: ${{ inputs.target_ref }} token: ${{ steps.app-token.outputs.token }} - - name: Configure Git - run: | - git config --local user.email "actions@github.com" - git config --local user.name "Github Actions" - - - name: Install xmllint - run: | - sudo apt-get update - sudo apt-get install -y libxml2-utils - - - name: Perform cherry-pick(s) + - name: Check if ${{ needs.setup.outputs.branch }} branch exists env: - CUT_BRANCH: ${{ needs.setup.outputs.branch }} + BRANCH_NAME: ${{ needs.setup.outputs.branch }} run: | - # Function for cherry-picking - cherry_pick () { - local source_branch=$1 - local destination_branch=$2 - - # Get project commit/version from source branch - git switch $source_branch - SOURCE_COMMIT=$(git log --reverse --pretty=format:"%H" --max-count=1 Directory.Build.props) - SOURCE_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props) - - # Get project commit/version from destination branch - git switch $destination_branch - DESTINATION_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props) - - if [[ "$DESTINATION_VERSION" != "$SOURCE_VERSION" ]]; then - git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT - git push -u origin $destination_branch - fi - } - - # If we are cutting 'hotfix-rc': - if [[ "$CUT_BRANCH" == "hotfix-rc" ]]; then - - # If the 'rc' branch exists: - if [[ $(git ls-remote --heads origin rc) ]]; then - - # Chery-pick from 'rc' into 'hotfix-rc' - cherry_pick rc hotfix-rc - - # Cherry-pick from 'main' into 'rc' - cherry_pick main rc - - # If the 'rc' branch does not exist: - else - - # Cherry-pick from 'main' into 'hotfix-rc' - cherry_pick main hotfix-rc - - fi - - # If we are cutting 'rc': - elif [[ "$CUT_BRANCH" == "rc" ]]; then - - # Cherry-pick from 'main' into 'rc' - cherry_pick main rc - + if [[ $(git ls-remote --heads origin $BRANCH_NAME) ]]; then + echo "$BRANCH_NAME already exists! Please delete $BRANCH_NAME before running again." >> $GITHUB_STEP_SUMMARY + exit 1 fi + - name: Cut branch + env: + BRANCH_NAME: ${{ needs.setup.outputs.branch }} + run: | + git switch --quiet --create $BRANCH_NAME + git push --quiet --set-upstream origin $BRANCH_NAME move_future_db_scripts: name: Move finalization database scripts - needs: cherry_pick + needs: cut_branch uses: ./.github/workflows/_move_finalization_db_scripts.yml secrets: inherit - permissions: - contents: write - pull-requests: write - id-token: write - actions: read diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 9420f71cb3..83d492645e 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -8,6 +8,11 @@ jobs: stale: name: Check for stale issues and PRs runs-on: ubuntu-22.04 + permissions: + actions: write + contents: read + issues: write + pull-requests: write steps: - name: Check uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index 26db5ea0a4..23722e2e8d 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -31,10 +31,17 @@ on: - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests - "src/**/Entities/**/*.cs" # Database entity definitions +permissions: + contents: read + jobs: test: name: Run tests runs-on: ubuntu-22.04 + permissions: + contents: read + actions: read + checks: write steps: - name: Check out repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/Directory.Build.props b/Directory.Build.props index a798c7f335..e68ae70fb1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net8.0 - 2025.6.1 + 2025.7.0 Bit.$(MSBuildProjectName) enable diff --git a/dev/.env.example b/dev/.env.example index 7f049728d7..f31b5b9eeb 100644 --- a/dev/.env.example +++ b/dev/.env.example @@ -26,3 +26,12 @@ IDENTITY_PROXY_PORT=33756 # Optional RabbitMQ configuration RABBITMQ_DEFAULT_USER=bitwarden RABBITMQ_DEFAULT_PASS=SET_A_PASSWORD_HERE_123 + +# Environment variables that help customize dev container start +# Without these the dev container will ask these questions in an interactive manner +# when possible (excluding running in GitHub Codespaces) +# SETUP_SECRETS_JSON=yes +# SETUP_AZURITE=yes +# RUN_MSSQL_MIGRATIONS=yes +# DEV_CERT_PASSWORD=dev_cert_password_here +# INSTALL_STRIPE_CLI=no diff --git a/src/Admin/AdminConsole/Controllers/ProvidersController.cs b/src/Admin/AdminConsole/Controllers/ProvidersController.cs index b4abf81ee2..7f11b65d9e 100644 --- a/src/Admin/AdminConsole/Controllers/ProvidersController.cs +++ b/src/Admin/AdminConsole/Controllers/ProvidersController.cs @@ -6,6 +6,7 @@ using Bit.Admin.Utilities; using Bit.Core; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.AdminConsole.Providers.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; @@ -34,14 +35,13 @@ namespace Bit.Admin.AdminConsole.Controllers; public class ProvidersController : Controller { private readonly IOrganizationRepository _organizationRepository; - private readonly IOrganizationService _organizationService; + private readonly IResellerClientOrganizationSignUpCommand _resellerClientOrganizationSignUpCommand; private readonly IProviderRepository _providerRepository; private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderOrganizationRepository _providerOrganizationRepository; private readonly GlobalSettings _globalSettings; private readonly IApplicationCacheService _applicationCacheService; private readonly IProviderService _providerService; - private readonly IUserService _userService; private readonly ICreateProviderCommand _createProviderCommand; private readonly IFeatureService _featureService; private readonly IProviderPlanRepository _providerPlanRepository; @@ -54,14 +54,13 @@ public class ProvidersController : Controller public ProvidersController( IOrganizationRepository organizationRepository, - IOrganizationService organizationService, + IResellerClientOrganizationSignUpCommand resellerClientOrganizationSignUpCommand, IProviderRepository providerRepository, IProviderUserRepository providerUserRepository, IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService, GlobalSettings globalSettings, IApplicationCacheService applicationCacheService, - IUserService userService, ICreateProviderCommand createProviderCommand, IFeatureService featureService, IProviderPlanRepository providerPlanRepository, @@ -71,14 +70,13 @@ public class ProvidersController : Controller IStripeAdapter stripeAdapter) { _organizationRepository = organizationRepository; - _organizationService = organizationService; + _resellerClientOrganizationSignUpCommand = resellerClientOrganizationSignUpCommand; _providerRepository = providerRepository; _providerUserRepository = providerUserRepository; _providerOrganizationRepository = providerOrganizationRepository; _providerService = providerService; _globalSettings = globalSettings; _applicationCacheService = applicationCacheService; - _userService = userService; _createProviderCommand = createProviderCommand; _featureService = featureService; _providerPlanRepository = providerPlanRepository; @@ -459,7 +457,7 @@ public class ProvidersController : Controller } var organization = model.CreateOrganization(provider); - await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted); + await _resellerClientOrganizationSignUpCommand.SignUpResellerClientAsync(organization, model.Owners); await _providerService.AddOrganization(providerId, organization.Id, null); return RedirectToAction("Edit", "Providers", new { id = providerId }); diff --git a/src/Admin/Dockerfile b/src/Admin/Dockerfile index d6b42eadfb..0d6fd4cc78 100644 --- a/src/Admin/Dockerfile +++ b/src/Admin/Dockerfile @@ -59,6 +59,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ gosu \ curl \ + krb5-user \ && rm -rf /var/lib/apt/lists/* # Copy app from the build stage diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 6b23edf347..7765eb2665 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -403,16 +403,15 @@ public class OrganizationUsersController : Controller } [HttpPost("{id}/confirm")] - public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model) + public async Task Confirm(Guid orgId, Guid id, [FromBody] OrganizationUserConfirmRequestModel model) { - var orgGuidId = new Guid(orgId); - if (!await _currentContext.ManageUsers(orgGuidId)) + if (!await _currentContext.ManageUsers(orgId)) { throw new NotFoundException(); } var userId = _userService.GetProperUserId(User); - var result = await _confirmOrganizationUserCommand.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value); + var result = await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, id, model.Key, userId.Value, model.DefaultUserCollectionName); } [HttpPost("confirm")] @@ -521,7 +520,9 @@ public class OrganizationUsersController : Controller .Concat(readonlyCollectionAccess) .ToList(); - await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), userId, + var existingUserType = organizationUser.Type; + + await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), existingUserType, userId, collectionsToSave, groupsToSave); } diff --git a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs b/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs index c0ab5c059b..3d749d25d7 100644 --- a/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs +++ b/src/Api/AdminConsole/Controllers/SlackIntegrationController.cs @@ -2,7 +2,7 @@ using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Core; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs index ccab2b36ae..d4d69f77c1 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; #nullable enable @@ -15,6 +15,8 @@ public class OrganizationIntegrationConfigurationRequestModel [Required] public EventType EventType { get; set; } + public string? Filters { get; set; } + public string? Template { get; set; } public bool IsValidForType(IntegrationType integrationType) @@ -24,9 +26,13 @@ public class OrganizationIntegrationConfigurationRequestModel case IntegrationType.CloudBillingSync or IntegrationType.Scim: return false; case IntegrationType.Slack: - return !string.IsNullOrWhiteSpace(Template) && IsConfigurationValid(); + return !string.IsNullOrWhiteSpace(Template) && + IsConfigurationValid() && + IsFiltersValid(); case IntegrationType.Webhook: - return !string.IsNullOrWhiteSpace(Template) && IsConfigurationValid(); + return !string.IsNullOrWhiteSpace(Template) && + IsConfigurationValid() && + IsFiltersValid(); default: return false; @@ -39,6 +45,7 @@ public class OrganizationIntegrationConfigurationRequestModel { OrganizationIntegrationId = organizationIntegrationId, Configuration = Configuration, + Filters = Filters, EventType = EventType, Template = Template }; @@ -48,6 +55,7 @@ public class OrganizationIntegrationConfigurationRequestModel { currentConfiguration.Configuration = Configuration; currentConfiguration.EventType = EventType; + currentConfiguration.Filters = Filters; currentConfiguration.Template = Template; return currentConfiguration; @@ -70,4 +78,22 @@ public class OrganizationIntegrationConfigurationRequestModel return false; } } + + private bool IsFiltersValid() + { + if (Filters is null) + { + return true; + } + + try + { + var filters = JsonSerializer.Deserialize(Filters); + return filters is not null; + } + catch + { + return false; + } + } } diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs index bbbb571f42..e6d4f85d3b 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs @@ -60,6 +60,10 @@ public class OrganizationUserConfirmRequestModel { [Required] public string Key { get; set; } + + [EncryptedString] + [EncryptedStringLength(1000)] + public string DefaultUserCollectionName { get; set; } } public class OrganizationUserBulkConfirmRequestModelEntry diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs index 8d074509c5..590a32ee3d 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationIntegrationConfigurationResponseModel.cs @@ -17,11 +17,13 @@ public class OrganizationIntegrationConfigurationResponseModel : ResponseModel Configuration = organizationIntegrationConfiguration.Configuration; CreationDate = organizationIntegrationConfiguration.CreationDate; EventType = organizationIntegrationConfiguration.EventType; + Filters = organizationIntegrationConfiguration.Filters; Template = organizationIntegrationConfiguration.Template; } public Guid Id { get; set; } public string? Configuration { get; set; } + public string? Filters { get; set; } public DateTime CreationDate { get; set; } public EventType EventType { get; set; } public string? Template { get; set; } diff --git a/src/Api/AdminConsole/Public/Controllers/MembersController.cs b/src/Api/AdminConsole/Public/Controllers/MembersController.cs index 6552684ca3..90c78c9eb7 100644 --- a/src/Api/AdminConsole/Public/Controllers/MembersController.cs +++ b/src/Api/AdminConsole/Public/Controllers/MembersController.cs @@ -177,9 +177,10 @@ public class MembersController : Controller { return new NotFoundResult(); } + var existingUserType = existingUser.Type; var updatedUser = model.ToOrganizationUser(existingUser); var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection()).ToList(); - await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, null, associations, model.Groups); + await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, existingUserType, null, associations, model.Groups); MemberResponseModel response = null; if (existingUser.UserId.HasValue) { diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index 2499b269f5..ec542daec7 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -1,34 +1,21 @@ -using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.AdminConsole.Models.Response; -using Bit.Api.Auth.Models.Request; +using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Auth.Models.Request.Accounts; -using Bit.Api.Auth.Models.Request.WebAuthn; -using Bit.Api.KeyManagement.Validators; using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Response; -using Bit.Api.Tools.Models.Request; -using Bit.Api.Vault.Models.Request; using Bit.Core; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; -using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Api.Request.Accounts; -using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; -using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.KeyManagement.Models.Data; -using Bit.Core.KeyManagement.UserKey; using Bit.Core.Models.Api.Response; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Tools.Entities; using Bit.Core.Utilities; -using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -45,22 +32,9 @@ public class AccountsController : Controller private readonly IPolicyService _policyService; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand; - private readonly IRotateUserKeyCommand _rotateUserKeyCommand; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; private readonly IFeatureService _featureService; - private readonly IRotationValidator, IEnumerable> _cipherValidator; - private readonly IRotationValidator, IEnumerable> _folderValidator; - private readonly IRotationValidator, IReadOnlyList> _sendValidator; - private readonly IRotationValidator, IEnumerable> - _emergencyAccessValidator; - private readonly IRotationValidator, - IReadOnlyList> - _organizationUserValidator; - private readonly IRotationValidator, IEnumerable> - _webauthnKeyValidator; - - public AccountsController( IOrganizationService organizationService, IOrganizationUserRepository organizationUserRepository, @@ -69,17 +43,8 @@ public class AccountsController : Controller IPolicyService policyService, ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand, ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand, - IRotateUserKeyCommand rotateUserKeyCommand, ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, - IFeatureService featureService, - IRotationValidator, IEnumerable> cipherValidator, - IRotationValidator, IEnumerable> folderValidator, - IRotationValidator, IReadOnlyList> sendValidator, - IRotationValidator, IEnumerable> - emergencyAccessValidator, - IRotationValidator, IReadOnlyList> - organizationUserValidator, - IRotationValidator, IEnumerable> webAuthnKeyValidator + IFeatureService featureService ) { _organizationService = organizationService; @@ -89,15 +54,8 @@ public class AccountsController : Controller _policyService = policyService; _setInitialMasterPasswordCommand = setInitialMasterPasswordCommand; _tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand; - _rotateUserKeyCommand = rotateUserKeyCommand; _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; _featureService = featureService; - _cipherValidator = cipherValidator; - _folderValidator = folderValidator; - _sendValidator = sendValidator; - _emergencyAccessValidator = emergencyAccessValidator; - _organizationUserValidator = organizationUserValidator; - _webauthnKeyValidator = webAuthnKeyValidator; } @@ -313,45 +271,6 @@ public class AccountsController : Controller throw new BadRequestException(ModelState); } - [Obsolete("Replaced by the safer rotate-user-account-keys endpoint.")] - [HttpPost("key")] - public async Task PostKey([FromBody] UpdateKeyRequestModel model) - { - var user = await _userService.GetUserByPrincipalAsync(User); - if (user == null) - { - throw new UnauthorizedAccessException(); - } - - var dataModel = new RotateUserKeyData - { - MasterPasswordHash = model.MasterPasswordHash, - Key = model.Key, - PrivateKey = model.PrivateKey, - Ciphers = await _cipherValidator.ValidateAsync(user, model.Ciphers), - Folders = await _folderValidator.ValidateAsync(user, model.Folders), - Sends = await _sendValidator.ValidateAsync(user, model.Sends), - EmergencyAccesses = await _emergencyAccessValidator.ValidateAsync(user, model.EmergencyAccessKeys), - OrganizationUsers = await _organizationUserValidator.ValidateAsync(user, model.ResetPasswordKeys), - WebAuthnKeys = await _webauthnKeyValidator.ValidateAsync(user, model.WebAuthnKeys) - }; - - var result = await _rotateUserKeyCommand.RotateUserKeyAsync(user, dataModel); - - if (result.Succeeded) - { - return; - } - - foreach (var error in result.Errors) - { - ModelState.AddModelError(string.Empty, error.Description); - } - - await Task.Delay(2000); - throw new BadRequestException(ModelState); - } - [HttpPost("security-stamp")] public async Task PostSecurityStamp([FromBody] SecretVerificationRequestModel model) { diff --git a/src/Api/Auth/Controllers/AuthRequestsController.cs b/src/Api/Auth/Controllers/AuthRequestsController.cs index f7edc7dec4..d1d6a8a524 100644 --- a/src/Api/Auth/Controllers/AuthRequestsController.cs +++ b/src/Api/Auth/Controllers/AuthRequestsController.cs @@ -1,5 +1,6 @@ using Bit.Api.Auth.Models.Response; using Bit.Api.Models.Response; +using Bit.Core; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Api.Request.AuthRequest; using Bit.Core.Auth.Services; @@ -7,6 +8,7 @@ using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -14,31 +16,23 @@ namespace Bit.Api.Auth.Controllers; [Route("auth-requests")] [Authorize("Application")] -public class AuthRequestsController : Controller +public class AuthRequestsController( + IUserService userService, + IAuthRequestRepository authRequestRepository, + IGlobalSettings globalSettings, + IAuthRequestService authRequestService) : Controller { - private readonly IUserService _userService; - private readonly IAuthRequestRepository _authRequestRepository; - private readonly IGlobalSettings _globalSettings; - private readonly IAuthRequestService _authRequestService; - - public AuthRequestsController( - IUserService userService, - IAuthRequestRepository authRequestRepository, - IGlobalSettings globalSettings, - IAuthRequestService authRequestService) - { - _userService = userService; - _authRequestRepository = authRequestRepository; - _globalSettings = globalSettings; - _authRequestService = authRequestService; - } + private readonly IUserService _userService = userService; + private readonly IAuthRequestRepository _authRequestRepository = authRequestRepository; + private readonly IGlobalSettings _globalSettings = globalSettings; + private readonly IAuthRequestService _authRequestService = authRequestService; [HttpGet("")] public async Task> Get() { var userId = _userService.GetProperUserId(User).Value; var authRequests = await _authRequestRepository.GetManyByUserIdAsync(userId); - var responses = authRequests.Select(a => new AuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault)).ToList(); + var responses = authRequests.Select(a => new AuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault)); return new ListResponseModel(responses); } @@ -56,6 +50,16 @@ public class AuthRequestsController : Controller return new AuthRequestResponseModel(authRequest, _globalSettings.BaseServiceUri.Vault); } + [HttpGet("pending")] + [RequireFeature(FeatureFlagKeys.BrowserExtensionLoginApproval)] + public async Task> GetPendingAuthRequestsAsync() + { + var userId = _userService.GetProperUserId(User).Value; + var rawResponse = await _authRequestRepository.GetManyPendingAuthRequestByUserId(userId); + var responses = rawResponse.Select(a => new PendingAuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault)); + return new ListResponseModel(responses); + } + [HttpGet("{id}/response")] [AllowAnonymous] public async Task GetResponse(Guid id, [FromQuery] string code) diff --git a/src/Api/Auth/Controllers/EmergencyAccessController.cs b/src/Api/Auth/Controllers/EmergencyAccessController.cs index 5d1f47de73..8b40444634 100644 --- a/src/Api/Auth/Controllers/EmergencyAccessController.cs +++ b/src/Api/Auth/Controllers/EmergencyAccessController.cs @@ -4,7 +4,6 @@ using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Response; using Bit.Api.Models.Response; using Bit.Api.Vault.Models.Response; -using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Services; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -72,7 +71,7 @@ public class EmergencyAccessController : Controller { var user = await _userService.GetUserByPrincipalAsync(User); var policies = await _emergencyAccessService.GetPoliciesAsync(id, user); - var responses = policies.Select(policy => new PolicyResponseModel(policy)); + var responses = policies?.Select(policy => new PolicyResponseModel(policy)); return new ListResponseModel(responses); } diff --git a/src/Api/Auth/Models/Response/PendingAuthRequestResponseModel.cs b/src/Api/Auth/Models/Response/PendingAuthRequestResponseModel.cs new file mode 100644 index 0000000000..8428593068 --- /dev/null +++ b/src/Api/Auth/Models/Response/PendingAuthRequestResponseModel.cs @@ -0,0 +1,15 @@ +using Bit.Core.Auth.Models.Data; + +namespace Bit.Api.Auth.Models.Response; + +public class PendingAuthRequestResponseModel : AuthRequestResponseModel +{ + public PendingAuthRequestResponseModel(PendingAuthRequestDetails authRequest, string vaultUri, string obj = "auth-request") + : base(authRequest, vaultUri, obj) + { + ArgumentNullException.ThrowIfNull(authRequest); + RequestDeviceId = authRequest.RequestDeviceId; + } + + public Guid? RequestDeviceId { get; set; } +} diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index e0f1c0d2c8..c8a12b9c22 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -20,6 +20,8 @@ public class CollectionsController : Controller { private readonly ICollectionRepository _collectionRepository; private readonly ICollectionService _collectionService; + private readonly ICreateCollectionCommand _createCollectionCommand; + private readonly IUpdateCollectionCommand _updateCollectionCommand; private readonly IDeleteCollectionCommand _deleteCollectionCommand; private readonly IUserService _userService; private readonly IAuthorizationService _authorizationService; @@ -29,6 +31,8 @@ public class CollectionsController : Controller public CollectionsController( ICollectionRepository collectionRepository, ICollectionService collectionService, + ICreateCollectionCommand createCollectionCommand, + IUpdateCollectionCommand updateCollectionCommand, IDeleteCollectionCommand deleteCollectionCommand, IUserService userService, IAuthorizationService authorizationService, @@ -37,6 +41,8 @@ public class CollectionsController : Controller { _collectionRepository = collectionRepository; _collectionService = collectionService; + _createCollectionCommand = createCollectionCommand; + _updateCollectionCommand = updateCollectionCommand; _deleteCollectionCommand = deleteCollectionCommand; _userService = userService; _authorizationService = authorizationService; @@ -153,7 +159,7 @@ public class CollectionsController : Controller var groups = model.Groups?.Select(g => g.ToSelectionReadOnly()); var users = model.Users?.Select(g => g.ToSelectionReadOnly()).ToList() ?? new List(); - await _collectionService.SaveAsync(collection, groups, users); + await _createCollectionCommand.CreateAsync(collection, groups, users); if (!_currentContext.UserId.HasValue || (_currentContext.GetOrganization(orgId) == null && await _currentContext.ProviderUserForOrgAsync(orgId))) { @@ -179,7 +185,7 @@ public class CollectionsController : Controller var groups = model.Groups?.Select(g => g.ToSelectionReadOnly()); var users = model.Users?.Select(g => g.ToSelectionReadOnly()); - await _collectionService.SaveAsync(model.ToCollection(collection), groups, users); + await _updateCollectionCommand.UpdateAsync(model.ToCollection(collection), groups, users); if (!_currentContext.UserId.HasValue || (_currentContext.GetOrganization(collection.OrganizationId) == null && await _currentContext.ProviderUserForOrgAsync(collection.OrganizationId))) { @@ -192,19 +198,6 @@ public class CollectionsController : Controller return new CollectionAccessDetailsResponseModel(collectionWithPermissions); } - [HttpPut("{id}/users")] - public async Task PutUsers(Guid orgId, Guid id, [FromBody] IEnumerable model) - { - var collection = await _collectionRepository.GetByIdAsync(id); - var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded; - if (!authorized) - { - throw new NotFoundException(); - } - - await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly())); - } - [HttpPost("bulk-access")] public async Task PostBulkCollectionAccess(Guid orgId, [FromBody] BulkCollectionAccessRequestModel model) { @@ -255,18 +248,4 @@ public class CollectionsController : Controller await _deleteCollectionCommand.DeleteManyAsync(collections); } - - [HttpDelete("{id}/user/{orgUserId}")] - [HttpPost("{id}/delete-user/{orgUserId}")] - public async Task DeleteUser(Guid orgId, Guid id, Guid orgUserId) - { - var collection = await _collectionRepository.GetByIdAsync(id); - var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ModifyUserAccess)).Succeeded; - if (!authorized) - { - throw new NotFoundException(); - } - - await _collectionService.DeleteUserAsync(collection, orgUserId); - } } diff --git a/src/Api/Controllers/DevicesController.cs b/src/Api/Controllers/DevicesController.cs index 0ff4e93abe..eaa572b7ec 100644 --- a/src/Api/Controllers/DevicesController.cs +++ b/src/Api/Controllers/DevicesController.cs @@ -206,7 +206,11 @@ public class DevicesController : Controller throw new NotFoundException(); } - await _deviceService.SaveAsync(model.ToData(), device); + await _deviceService.SaveAsync( + model.ToData(), + device, + _currentContext.Organizations.Select(org => org.Id.ToString()) + ); } [AllowAnonymous] diff --git a/src/Api/Dirt/Controllers/ReportsController.cs b/src/Api/Dirt/Controllers/ReportsController.cs index 2f7a5a4328..8bb8b5e487 100644 --- a/src/Api/Dirt/Controllers/ReportsController.cs +++ b/src/Api/Dirt/Controllers/ReportsController.cs @@ -1,7 +1,8 @@ using Bit.Api.Dirt.Models; using Bit.Api.Dirt.Models.Response; +using Bit.Api.Tools.Models.Response; using Bit.Core.Context; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.Models.Data; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; @@ -17,24 +18,36 @@ namespace Bit.Api.Dirt.Controllers; public class ReportsController : Controller { private readonly ICurrentContext _currentContext; - private readonly IMemberAccessCipherDetailsQuery _memberAccessCipherDetailsQuery; + private readonly IMemberAccessReportQuery _memberAccessReportQuery; + private readonly IRiskInsightsReportQuery _riskInsightsReportQuery; private readonly IAddPasswordHealthReportApplicationCommand _addPwdHealthReportAppCommand; private readonly IGetPasswordHealthReportApplicationQuery _getPwdHealthReportAppQuery; private readonly IDropPasswordHealthReportApplicationCommand _dropPwdHealthReportAppCommand; + private readonly IAddOrganizationReportCommand _addOrganizationReportCommand; + private readonly IDropOrganizationReportCommand _dropOrganizationReportCommand; + private readonly IGetOrganizationReportQuery _getOrganizationReportQuery; public ReportsController( ICurrentContext currentContext, - IMemberAccessCipherDetailsQuery memberAccessCipherDetailsQuery, + IMemberAccessReportQuery memberAccessReportQuery, + IRiskInsightsReportQuery riskInsightsReportQuery, IAddPasswordHealthReportApplicationCommand addPasswordHealthReportApplicationCommand, IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery, - IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand + IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand, + IGetOrganizationReportQuery getOrganizationReportQuery, + IAddOrganizationReportCommand addOrganizationReportCommand, + IDropOrganizationReportCommand dropOrganizationReportCommand ) { _currentContext = currentContext; - _memberAccessCipherDetailsQuery = memberAccessCipherDetailsQuery; + _memberAccessReportQuery = memberAccessReportQuery; + _riskInsightsReportQuery = riskInsightsReportQuery; _addPwdHealthReportAppCommand = addPasswordHealthReportApplicationCommand; _getPwdHealthReportAppQuery = getPasswordHealthReportApplicationQuery; _dropPwdHealthReportAppCommand = dropPwdHealthReportAppCommand; + _getOrganizationReportQuery = getOrganizationReportQuery; + _addOrganizationReportCommand = addOrganizationReportCommand; + _dropOrganizationReportCommand = dropOrganizationReportCommand; } /// @@ -54,9 +67,9 @@ public class ReportsController : Controller throw new NotFoundException(); } - var memberCipherDetails = await GetMemberCipherDetails(new MemberAccessCipherDetailsRequest { OrganizationId = orgId }); + var riskDetails = await GetRiskInsightsReportDetails(new RiskInsightsReportRequest { OrganizationId = orgId }); - var responses = memberCipherDetails.Select(x => new MemberCipherDetailsResponseModel(x)); + var responses = riskDetails.Select(x => new MemberCipherDetailsResponseModel(x)); return responses; } @@ -69,16 +82,16 @@ public class ReportsController : Controller /// IEnumerable of MemberAccessReportResponseModel /// If Access reports permission is not assigned [HttpGet("member-access/{orgId}")] - public async Task> GetMemberAccessReport(Guid orgId) + public async Task> GetMemberAccessReport(Guid orgId) { if (!await _currentContext.AccessReports(orgId)) { throw new NotFoundException(); } - var memberCipherDetails = await GetMemberCipherDetails(new MemberAccessCipherDetailsRequest { OrganizationId = orgId }); + var accessDetails = await GetMemberAccessDetails(new MemberAccessReportRequest { OrganizationId = orgId }); - var responses = memberCipherDetails.Select(x => new MemberAccessReportResponseModel(x)); + var responses = accessDetails.Select(x => new MemberAccessDetailReportResponseModel(x)); return responses; } @@ -87,13 +100,28 @@ public class ReportsController : Controller /// Contains the organization member info, the cipher ids associated with the member, /// and details on their collections, groups, and permissions /// - /// Request to the MemberAccessCipherDetailsQuery - /// IEnumerable of MemberAccessCipherDetails - private async Task> GetMemberCipherDetails(MemberAccessCipherDetailsRequest request) + /// Request parameters + /// + /// List of a user's permissions at a group and collection level as well as the number of ciphers + /// associated with that group/collection + /// + private async Task> GetMemberAccessDetails( + MemberAccessReportRequest request) { - var memberCipherDetails = - await _memberAccessCipherDetailsQuery.GetMemberAccessCipherDetails(request); - return memberCipherDetails; + var accessDetails = await _memberAccessReportQuery.GetMemberAccessReportsAsync(request); + return accessDetails; + } + + /// + /// Gets the risk insights report details from the risk insights query. Associates a user to their cipher ids + /// + /// Request parameters + /// A list of risk insights data associating the user to cipher ids + private async Task> GetRiskInsightsReportDetails( + RiskInsightsReportRequest request) + { + var riskDetails = await _riskInsightsReportQuery.GetRiskInsightsReportDetails(request); + return riskDetails; } /// @@ -185,4 +213,72 @@ public class ReportsController : Controller await _dropPwdHealthReportAppCommand.DropPasswordHealthReportApplicationAsync(request); } + + /// + /// Adds a new organization report + /// + /// A single instance of AddOrganizationReportRequest + /// A single instance of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpPost("organization-reports")] + public async Task AddOrganizationReport([FromBody] AddOrganizationReportRequest request) + { + if (!await _currentContext.AccessReports(request.OrganizationId)) + { + throw new NotFoundException(); + } + return await _addOrganizationReportCommand.AddOrganizationReportAsync(request); + } + + /// + /// Drops organization reports for an organization + /// + /// A single instance of DropOrganizationReportRequest + /// + /// If user does not have access to the organization + /// If the organization does not have any records + [HttpDelete("organization-reports")] + public async Task DropOrganizationReport([FromBody] DropOrganizationReportRequest request) + { + if (!await _currentContext.AccessReports(request.OrganizationId)) + { + throw new NotFoundException(); + } + await _dropOrganizationReportCommand.DropOrganizationReportAsync(request); + } + + /// + /// Gets organization reports for an organization + /// + /// A valid Organization Id + /// An Enumerable of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpGet("organization-reports/{orgId}")] + public async Task> GetOrganizationReports(Guid orgId) + { + if (!await _currentContext.AccessReports(orgId)) + { + throw new NotFoundException(); + } + return await _getOrganizationReportQuery.GetOrganizationReportAsync(orgId); + } + + /// + /// Gets the latest organization report for an organization + /// + /// A valid Organization Id + /// A single instance of OrganizationReport + /// If user does not have access to the organization + /// If the organization Id is not valid + [HttpGet("organization-reports/latest/{orgId}")] + public async Task GetLatestOrganizationReport(Guid orgId) + { + if (!await _currentContext.AccessReports(orgId)) + { + throw new NotFoundException(); + } + return await _getOrganizationReportQuery.GetLatestOrganizationReportAsync(orgId); + } } diff --git a/src/Api/Dirt/Models/Response/MemberAccessDetailReportResponseModel.cs b/src/Api/Dirt/Models/Response/MemberAccessDetailReportResponseModel.cs new file mode 100644 index 0000000000..2d5a7b1556 --- /dev/null +++ b/src/Api/Dirt/Models/Response/MemberAccessDetailReportResponseModel.cs @@ -0,0 +1,39 @@ +using Bit.Core.Dirt.Reports.Models.Data; + +namespace Bit.Api.Tools.Models.Response; + +public class MemberAccessDetailReportResponseModel +{ + public Guid? UserGuid { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public bool TwoFactorEnabled { get; set; } + public bool AccountRecoveryEnabled { get; set; } + public bool UsesKeyConnector { get; set; } + public Guid? CollectionId { get; set; } + public Guid? GroupId { get; set; } + public string GroupName { get; set; } + public string CollectionName { get; set; } + public bool? ReadOnly { get; set; } + public bool? HidePasswords { get; set; } + public bool? Manage { get; set; } + public IEnumerable CipherIds { get; set; } + + public MemberAccessDetailReportResponseModel(MemberAccessReportDetail reportDetail) + { + UserGuid = reportDetail.UserGuid; + UserName = reportDetail.UserName; + Email = reportDetail.Email; + TwoFactorEnabled = reportDetail.TwoFactorEnabled; + AccountRecoveryEnabled = reportDetail.AccountRecoveryEnabled; + UsesKeyConnector = reportDetail.UsesKeyConnector; + CollectionId = reportDetail.CollectionId; + GroupId = reportDetail.GroupId; + GroupName = reportDetail.GroupName; + CollectionName = reportDetail.CollectionName; + ReadOnly = reportDetail.ReadOnly; + HidePasswords = reportDetail.HidePasswords; + Manage = reportDetail.Manage; + CipherIds = reportDetail.CipherIds; + } +} diff --git a/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs b/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs index b8356e5d44..38a5ed90d8 100644 --- a/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs +++ b/src/Api/Dirt/Models/Response/MemberAccessReportModel.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Models.Data; namespace Bit.Api.Dirt.Models.Response; diff --git a/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs b/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs index 30065ad05a..886cf470db 100644 --- a/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs +++ b/src/Api/Dirt/Models/Response/MemberCipherDetailsResponseModel.cs @@ -1,5 +1,4 @@ using Bit.Core.Dirt.Reports.Models.Data; - namespace Bit.Api.Dirt.Models.Response; public class MemberCipherDetailsResponseModel @@ -15,12 +14,12 @@ public class MemberCipherDetailsResponseModel /// public IEnumerable CipherIds { get; set; } - public MemberCipherDetailsResponseModel(MemberAccessCipherDetails memberAccessCipherDetails) + public MemberCipherDetailsResponseModel(RiskInsightsReportDetail reportDetail) { - this.UserGuid = memberAccessCipherDetails.UserGuid; - this.UserName = memberAccessCipherDetails.UserName; - this.Email = memberAccessCipherDetails.Email; - this.UsesKeyConnector = memberAccessCipherDetails.UsesKeyConnector; - this.CipherIds = memberAccessCipherDetails.CipherIds; + this.UserGuid = reportDetail.UserGuid; + this.UserName = reportDetail.UserName; + this.Email = reportDetail.Email; + this.UsesKeyConnector = reportDetail.UsesKeyConnector; + this.CipherIds = reportDetail.CipherIds; } } diff --git a/src/Api/Platform/Push/Controllers/PushController.cs b/src/Api/Platform/Push/Controllers/PushController.cs index 2a1f2b987d..af24a7b2ca 100644 --- a/src/Api/Platform/Push/Controllers/PushController.cs +++ b/src/Api/Platform/Push/Controllers/PushController.cs @@ -1,8 +1,11 @@ -using Bit.Core.Context; +using System.Diagnostics; +using System.Text.Json; +using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Api; using Bit.Core.NotificationHub; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; @@ -20,14 +23,14 @@ namespace Bit.Api.Platform.Push; public class PushController : Controller { private readonly IPushRegistrationService _pushRegistrationService; - private readonly IPushNotificationService _pushNotificationService; + private readonly IPushRelayer _pushRelayer; private readonly IWebHostEnvironment _environment; private readonly ICurrentContext _currentContext; private readonly IGlobalSettings _globalSettings; public PushController( IPushRegistrationService pushRegistrationService, - IPushNotificationService pushNotificationService, + IPushRelayer pushRelayer, IWebHostEnvironment environment, ICurrentContext currentContext, IGlobalSettings globalSettings) @@ -35,7 +38,7 @@ public class PushController : Controller _currentContext = currentContext; _environment = environment; _pushRegistrationService = pushRegistrationService; - _pushNotificationService = pushNotificationService; + _pushRelayer = pushRelayer; _globalSettings = globalSettings; } @@ -74,31 +77,50 @@ public class PushController : Controller } [HttpPost("send")] - public async Task SendAsync([FromBody] PushSendRequestModel model) + public async Task SendAsync([FromBody] PushSendRequestModel model) { CheckUsage(); - if (!string.IsNullOrWhiteSpace(model.InstallationId)) + NotificationTarget target; + Guid targetId; + + if (model.InstallationId.HasValue) { - if (_currentContext.InstallationId!.Value.ToString() != model.InstallationId!) + if (_currentContext.InstallationId!.Value != model.InstallationId.Value) { throw new BadRequestException("InstallationId does not match current context."); } - await _pushNotificationService.SendPayloadToInstallationAsync( - _currentContext.InstallationId.Value.ToString(), model.Type, model.Payload, Prefix(model.Identifier), - Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.Installation; + targetId = _currentContext.InstallationId.Value; } - else if (!string.IsNullOrWhiteSpace(model.UserId)) + else if (model.UserId.HasValue) { - await _pushNotificationService.SendPayloadToUserAsync(Prefix(model.UserId), - model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.User; + targetId = model.UserId.Value; } - else if (!string.IsNullOrWhiteSpace(model.OrganizationId)) + else if (model.OrganizationId.HasValue) { - await _pushNotificationService.SendPayloadToOrganizationAsync(Prefix(model.OrganizationId), - model.Type, model.Payload, Prefix(model.Identifier), Prefix(model.DeviceId), model.ClientType); + target = NotificationTarget.Organization; + targetId = model.OrganizationId.Value; } + else + { + throw new UnreachableException("Model validation should have prevented getting here."); + } + + var notification = new RelayedNotification + { + Type = model.Type, + Target = target, + TargetId = targetId, + Payload = model.Payload, + Identifier = model.Identifier, + DeviceId = model.DeviceId, + ClientType = model.ClientType, + }; + + await _pushRelayer.RelayAsync(_currentContext.InstallationId.Value, notification); } private string Prefix(string value) diff --git a/src/Api/Public/Controllers/CollectionsController.cs b/src/Api/Public/Controllers/CollectionsController.cs index b16eb1a418..ec282a0e4d 100644 --- a/src/Api/Public/Controllers/CollectionsController.cs +++ b/src/Api/Public/Controllers/CollectionsController.cs @@ -2,6 +2,8 @@ using Bit.Api.Models.Public.Request; using Bit.Api.Models.Public.Response; using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; @@ -14,18 +16,18 @@ namespace Bit.Api.Public.Controllers; public class CollectionsController : Controller { private readonly ICollectionRepository _collectionRepository; - private readonly ICollectionService _collectionService; + private readonly IUpdateCollectionCommand _updateCollectionCommand; private readonly ICurrentContext _currentContext; private readonly IApplicationCacheService _applicationCacheService; public CollectionsController( ICollectionRepository collectionRepository, - ICollectionService collectionService, + IUpdateCollectionCommand updateCollectionCommand, ICurrentContext currentContext, IApplicationCacheService applicationCacheService) { _collectionRepository = collectionRepository; - _collectionService = collectionService; + _updateCollectionCommand = updateCollectionCommand; _currentContext = currentContext; _applicationCacheService = applicationCacheService; } @@ -93,7 +95,7 @@ public class CollectionsController : Controller } var updatedCollection = model.ToCollection(existingCollection); var associations = model.Groups?.Select(c => c.ToCollectionAccessSelection()).ToList(); - await _collectionService.SaveAsync(updatedCollection, associations); + await _updateCollectionCommand.UpdateAsync(updatedCollection, associations, null); var response = new CollectionResponseModel(updatedCollection, associations); return new JsonResult(response); } @@ -115,6 +117,12 @@ public class CollectionsController : Controller { return new NotFoundResult(); } + + if (collection.Type == CollectionType.DefaultUserCollection) + { + return new BadRequestObjectResult(new ErrorResponseModel("You cannot delete a collection with the type as DefaultUserCollection.")); + } + await _collectionRepository.DeleteAsync(collection); return new OkResult(); } diff --git a/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs b/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs index 25b669622f..5c2250824e 100644 --- a/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs +++ b/src/Core/AdminConsole/Entities/OrganizationIntegrationConfiguration.cs @@ -15,5 +15,6 @@ public class OrganizationIntegrationConfiguration : ITableObject public string? Template { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; set; } = DateTime.UtcNow; + public string? Filters { get; set; } public void SetNewId() => Id = CoreHelpers.GenerateComb(); } diff --git a/src/Core/AdminConsole/Enums/PolicyType.cs b/src/Core/AdminConsole/Enums/PolicyType.cs index f72637f862..ab39e543f8 100644 --- a/src/Core/AdminConsole/Enums/PolicyType.cs +++ b/src/Core/AdminConsole/Enums/PolicyType.cs @@ -7,7 +7,7 @@ public enum PolicyType : byte PasswordGenerator = 2, SingleOrg = 3, RequireSso = 4, - PersonalOwnership = 5, + OrganizationDataOwnership = 5, DisableSend = 6, SendOptions = 7, ResetPassword = 8, @@ -35,7 +35,7 @@ public static class PolicyTypeExtensions PolicyType.PasswordGenerator => "Password generator", PolicyType.SingleOrg => "Single organization", PolicyType.RequireSso => "Require single sign-on authentication", - PolicyType.PersonalOwnership => "Remove individual vault", + PolicyType.OrganizationDataOwnership => "Enforce organization data ownership", PolicyType.DisableSend => "Remove Send", PolicyType.SendOptions => "Send options", PolicyType.ResetPassword => "Account recovery administration", diff --git a/src/Core/AdminConsole/Models/Data/Integrations/IIntegrationMessage.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs similarity index 82% rename from src/Core/AdminConsole/Models/Data/Integrations/IIntegrationMessage.cs rename to src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs index c94794765b..f979b8af0e 100644 --- a/src/Core/AdminConsole/Models/Data/Integrations/IIntegrationMessage.cs +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IIntegrationMessage.cs @@ -2,7 +2,7 @@ using Bit.Core.Enums; -namespace Bit.Core.AdminConsole.Models.Data.Integrations; +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; public interface IIntegrationMessage { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs new file mode 100644 index 0000000000..bb0c2e01ba --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterGroup.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public class IntegrationFilterGroup +{ + public bool AndOperator { get; init; } = true; + public List? Rules { get; init; } + public List? Groups { get; init; } +} diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs new file mode 100644 index 0000000000..f09df47738 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterOperation.cs @@ -0,0 +1,10 @@ +#nullable enable +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public enum IntegrationFilterOperation +{ + Equals = 0, + NotEquals = 1, + In = 2, + NotIn = 3 +} diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs new file mode 100644 index 0000000000..b9d90a0442 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationFilterRule.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public class IntegrationFilterRule +{ + public required string Property { get; set; } + public required IntegrationFilterOperation Operation { get; set; } + public required object? Value { get; set; } +} + diff --git a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationHandlerResult.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs similarity index 88% rename from src/Core/AdminConsole/Models/Data/Integrations/IntegrationHandlerResult.cs rename to src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs index ecf5d25c51..d3b0c0d5ac 100644 --- a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationHandlerResult.cs +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationHandlerResult.cs @@ -1,6 +1,6 @@ #nullable enable -namespace Bit.Core.AdminConsole.Models.Data.Integrations; +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; public class IntegrationHandlerResult { diff --git a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationMessage.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs similarity index 94% rename from src/Core/AdminConsole/Models/Data/Integrations/IntegrationMessage.cs rename to src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs index 018d453cb9..1861ec4522 100644 --- a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationMessage.cs +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationMessage.cs @@ -3,7 +3,7 @@ using System.Text.Json; using Bit.Core.Enums; -namespace Bit.Core.AdminConsole.Models.Data.Integrations; +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; public class IntegrationMessage : IIntegrationMessage { diff --git a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationTemplateContext.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs similarity index 95% rename from src/Core/AdminConsole/Models/Data/Integrations/IntegrationTemplateContext.cs rename to src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs index 338c2b963d..82c236865f 100644 --- a/src/Core/AdminConsole/Models/Data/Integrations/IntegrationTemplateContext.cs +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs @@ -5,7 +5,7 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; -namespace Bit.Core.AdminConsole.Models.Data.Integrations; +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; public class IntegrationTemplateContext(EventMessage eventMessage) { diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs new file mode 100644 index 0000000000..e8bfaee303 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegration.cs @@ -0,0 +1,5 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public record SlackIntegration(string Token); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs new file mode 100644 index 0000000000..2c757aeb76 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfiguration.cs @@ -0,0 +1,5 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public record SlackIntegrationConfiguration(string ChannelId); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs new file mode 100644 index 0000000000..6c3d4c2fff --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/SlackIntegrationConfigurationDetails.cs @@ -0,0 +1,5 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public record SlackIntegrationConfigurationDetails(string ChannelId, string Token); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs new file mode 100644 index 0000000000..ff28edc301 --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfiguration.cs @@ -0,0 +1,5 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public record WebhookIntegrationConfiguration(string Url, string? Scheme = null, string? Token = null); diff --git a/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs b/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs new file mode 100644 index 0000000000..e0ed5dfcfa --- /dev/null +++ b/src/Core/AdminConsole/Models/Data/EventIntegrations/WebhookIntegrationConfigurationDetails.cs @@ -0,0 +1,5 @@ +#nullable enable + +namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations; + +public record WebhookIntegrationConfigurationDetails(string Url, string? Scheme = null, string? Token = null); diff --git a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegration.cs b/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegration.cs deleted file mode 100644 index 4f2c434ff6..0000000000 --- a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegration.cs +++ /dev/null @@ -1,5 +0,0 @@ -#nullable enable - -namespace Bit.Core.AdminConsole.Models.Data.Integrations; - -public record SlackIntegration(string token); diff --git a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfiguration.cs b/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfiguration.cs deleted file mode 100644 index 18b13248ec..0000000000 --- a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfiguration.cs +++ /dev/null @@ -1,5 +0,0 @@ -#nullable enable - -namespace Bit.Core.AdminConsole.Models.Data.Integrations; - -public record SlackIntegrationConfiguration(string channelId); diff --git a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfigurationDetails.cs b/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfigurationDetails.cs deleted file mode 100644 index a9b4150419..0000000000 --- a/src/Core/AdminConsole/Models/Data/Integrations/SlackIntegrationConfigurationDetails.cs +++ /dev/null @@ -1,5 +0,0 @@ -#nullable enable - -namespace Bit.Core.AdminConsole.Models.Data.Integrations; - -public record SlackIntegrationConfigurationDetails(string channelId, string token); diff --git a/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfiguration.cs b/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfiguration.cs deleted file mode 100644 index 47e014ee2a..0000000000 --- a/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfiguration.cs +++ /dev/null @@ -1,5 +0,0 @@ -#nullable enable - -namespace Bit.Core.AdminConsole.Models.Data.Integrations; - -public record WebhookIntegrationConfiguration(string url); diff --git a/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfigurationDetails.cs b/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfigurationDetails.cs deleted file mode 100644 index c4c41db24f..0000000000 --- a/src/Core/AdminConsole/Models/Data/Integrations/WebhookIntegrationConfigurationDetails.cs +++ /dev/null @@ -1,5 +0,0 @@ -#nullable enable - -namespace Bit.Core.AdminConsole.Models.Data.Integrations; - -public record WebhookIntegrationConfigurationDetails(string url); diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs index 139a7aff25..b5d224c012 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationIntegrationConfigurationDetails.cs @@ -12,6 +12,7 @@ public class OrganizationIntegrationConfigurationDetails public IntegrationType IntegrationType { get; set; } public EventType EventType { get; set; } public string? Configuration { get; set; } + public string? Filters { get; set; } public string? IntegrationConfiguration { get; set; } public string? Template { get; set; } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs index 1b53716537..3347e77c37 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs @@ -163,6 +163,11 @@ public class UpdateGroupCommand : IUpdateGroupCommand // Use generic error message to avoid enumeration throw new NotFoundException(); } + + if (collections.Any(c => c.Type == CollectionType.DefaultUserCollection)) + { + throw new BadRequestException("You cannot modify group access for collections with the type as DefaultUserCollection."); + } } private async Task ValidateMemberAccessAsync(Group originalGroup, diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs index 806cf5a533..62e5d60191 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs @@ -8,6 +8,7 @@ using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; @@ -28,6 +29,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand private readonly IDeviceRepository _deviceRepository; private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IFeatureService _featureService; + private readonly ICollectionRepository _collectionRepository; public ConfirmOrganizationUserCommand( IOrganizationRepository organizationRepository, @@ -41,7 +43,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand IPolicyService policyService, IDeviceRepository deviceRepository, IPolicyRequirementQuery policyRequirementQuery, - IFeatureService featureService) + IFeatureService featureService, + ICollectionRepository collectionRepository) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -55,10 +58,11 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand _deviceRepository = deviceRepository; _policyRequirementQuery = policyRequirementQuery; _featureService = featureService; + _collectionRepository = collectionRepository; } public async Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, - Guid confirmingUserId) + Guid confirmingUserId, string defaultUserCollectionName = null) { var result = await ConfirmUsersAsync( organizationId, @@ -75,6 +79,9 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand { throw new BadRequestException(error); } + + await HandleConfirmationSideEffectsAsync(organizationId, orgUser, defaultUserCollectionName); + return orgUser; } @@ -213,4 +220,54 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand .Where(d => !string.IsNullOrWhiteSpace(d.PushToken)) .Select(d => d.Id.ToString()); } + + private async Task HandleConfirmationSideEffectsAsync(Guid organizationId, OrganizationUser organizationUser, string defaultUserCollectionName) + { + // Create DefaultUserCollection type collection for the user if the OrganizationDataOwnership policy is enabled for the organization + var requiresDefaultCollection = await OrganizationRequiresDefaultCollectionAsync(organizationId, organizationUser.UserId.Value, defaultUserCollectionName); + if (requiresDefaultCollection) + { + await CreateDefaultCollectionAsync(organizationId, organizationUser.Id, defaultUserCollectionName); + } + } + + private async Task OrganizationRequiresDefaultCollectionAsync(Guid organizationId, Guid userId, string defaultUserCollectionName) + { + if (!_featureService.IsEnabled(FeatureFlagKeys.CreateDefaultLocation)) + { + return false; + } + + // Skip if no collection name provided (backwards compatibility) + if (string.IsNullOrWhiteSpace(defaultUserCollectionName)) + { + return false; + } + + var organizationDataOwnershipRequirement = await _policyRequirementQuery.GetAsync(userId); + return organizationDataOwnershipRequirement.RequiresDefaultCollection(organizationId); + } + + private async Task CreateDefaultCollectionAsync(Guid organizationId, Guid organizationUserId, string defaultCollectionName) + { + var collection = new Collection + { + OrganizationId = organizationId, + Name = defaultCollectionName, + Type = CollectionType.DefaultUserCollection + }; + + var userAccess = new List + { + new CollectionAccessSelection + { + Id = organizationUserId, + ReadOnly = false, + HidePasswords = false, + Manage = true + } + }; + + await _collectionRepository.CreateAsync(collection, groups: null, users: userAccess); + } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IConfirmOrganizationUserCommand.cs index e574d29e48..734b8d2b0c 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IConfirmOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IConfirmOrganizationUserCommand.cs @@ -15,9 +15,10 @@ public interface IConfirmOrganizationUserCommand /// The ID of the organization user to confirm. /// The encrypted organization key for the user. /// The ID of the user performing the confirmation. + /// Optional encrypted collection name for creating a default collection. /// The confirmed organization user. /// Thrown when the user is not valid or cannot be confirmed. - Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId); + Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId, string defaultUserCollectionName = null); /// /// Confirms multiple organization users who have accepted their invitations. diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs index 0cd5a3295f..d1fc9fbbec 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs @@ -1,11 +1,12 @@ #nullable enable using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Models.Data; namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; public interface IUpdateOrganizationUserCommand { - Task UpdateUserAsync(OrganizationUser organizationUser, Guid? savingUserId, + Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType, 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 bad7b14b87..b72505c195 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs @@ -55,11 +55,13 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand /// Update an organization user. /// /// The modified organization user to save. + /// The current type (member role) of the user. /// 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 organizationUser, Guid? savingUserId, + public async Task UpdateUserAsync(OrganizationUser organizationUser, OrganizationUserType existingUserType, + Guid? savingUserId, List? collectionAccess, IEnumerable? groupAccess) { // Avoid multiple enumeration @@ -83,15 +85,7 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand 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."); - } - } + await EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(organizationUser, existingUserType, organization); if (collectionAccessList.Count != 0) { @@ -151,6 +145,40 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Updated); } + private async Task EnsureUserCannotBeAdminOrOwnerForMultipleFreeOrganizationAsync(OrganizationUser updatedOrgUser, OrganizationUserType existingUserType, Entities.Organization organization) + { + + if (organization.PlanType != PlanType.Free) + { + return; + } + if (!updatedOrgUser.UserId.HasValue) + { + return; + } + if (updatedOrgUser.Type is not (OrganizationUserType.Admin or OrganizationUserType.Owner)) + { + return; + } + + // 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(updatedOrgUser.UserId!.Value); + + var isCurrentAdminOrOwner = existingUserType is OrganizationUserType.Admin or OrganizationUserType.Owner; + + if (isCurrentAdminOrOwner && adminCount <= 1) + { + return; + } + + if (!isCurrentAdminOrOwner && adminCount == 0) + { + return; + } + + throw new BadRequestException("User can only be an admin of one free organization."); + } + private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser, ICollection collectionAccess) { @@ -171,6 +199,11 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand // Use generic error message to avoid enumeration throw new NotFoundException(); } + + if (collections.Any(c => c.Type == CollectionType.DefaultUserCollection)) + { + throw new BadRequestException("You cannot modify member access for collections with the type as DefaultUserCollection."); + } } private async Task ValidateGroupAccessAsync(OrganizationUser originalUser, diff --git a/src/Core/AdminConsole/OrganizationFeatures/Organizations/ResellerClientOrganizationSignUpCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Organizations/ResellerClientOrganizationSignUpCommand.cs new file mode 100644 index 0000000000..446d7339ca --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Organizations/ResellerClientOrganizationSignUpCommand.cs @@ -0,0 +1,130 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Utilities; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations; + +public record ResellerClientOrganizationSignUpResponse( + Organization Organization, + OrganizationUser OwnerOrganizationUser); + +/// +/// Command for signing up reseller client organizations in a pending state. +/// +public interface IResellerClientOrganizationSignUpCommand +{ + /// + /// Sign up a reseller client organization. The organization will be created in a pending state + /// (disabled and with Pending status) and the owner will be invited via email. The organization + /// will become active once the owner accepts the invitation. + /// + /// The organization to create. + /// The email of the organization owner who will be invited. + /// A response containing the created pending organization and invited owner user. + Task SignUpResellerClientAsync( + Organization organization, + string ownerEmail); +} + +public class ResellerClientOrganizationSignUpCommand : IResellerClientOrganizationSignUpCommand +{ + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; + private readonly IApplicationCacheService _applicationCacheService; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IEventService _eventService; + private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand; + private readonly IPaymentService _paymentService; + + public ResellerClientOrganizationSignUpCommand( + IOrganizationRepository organizationRepository, + IOrganizationApiKeyRepository organizationApiKeyRepository, + IApplicationCacheService applicationCacheService, + IOrganizationUserRepository organizationUserRepository, + IEventService eventService, + ISendOrganizationInvitesCommand sendOrganizationInvitesCommand, + IPaymentService paymentService) + { + _organizationRepository = organizationRepository; + _organizationApiKeyRepository = organizationApiKeyRepository; + _applicationCacheService = applicationCacheService; + _organizationUserRepository = organizationUserRepository; + _eventService = eventService; + _sendOrganizationInvitesCommand = sendOrganizationInvitesCommand; + _paymentService = paymentService; + } + + public async Task SignUpResellerClientAsync( + Organization organization, + string ownerEmail) + { + try + { + var createdOrganization = await CreateOrganizationAsync(organization); + var ownerOrganizationUser = await CreateAndInviteOwnerAsync(createdOrganization, ownerEmail); + + await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited); + + return new ResellerClientOrganizationSignUpResponse(organization, ownerOrganizationUser); + } + catch + { + await _paymentService.CancelAndRecoverChargesAsync(organization); + + if (organization.Id != default) + { + // Deletes the organization and all related data, including its owner user + await _organizationRepository.DeleteAsync(organization); + await _applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id); + } + + throw; + } + } + + private async Task CreateOrganizationAsync(Organization organization) + { + organization.Id = CoreHelpers.GenerateComb(); + organization.Enabled = false; + organization.Status = OrganizationStatusType.Pending; + + await _organizationRepository.CreateAsync(organization); + await _organizationApiKeyRepository.CreateAsync(new OrganizationApiKey + { + OrganizationId = organization.Id, + ApiKey = CoreHelpers.SecureRandomString(30), + Type = OrganizationApiKeyType.Default, + RevisionDate = DateTime.UtcNow, + }); + await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); + + return organization; + } + + private async Task CreateAndInviteOwnerAsync(Organization organization, string ownerEmail) + { + var ownerOrganizationUser = new OrganizationUser + { + OrganizationId = organization.Id, + UserId = null, + Email = ownerEmail, + Key = null, + Type = OrganizationUserType.Owner, + Status = OrganizationUserStatusType.Invited, + }; + + await _organizationUserRepository.CreateAsync(ownerOrganizationUser); + + await _sendOrganizationInvitesCommand.SendInvitesAsync(new SendInvitesRequest( + users: [ownerOrganizationUser], + organization: organization, + initOrganization: true)); + + return ownerOrganizationUser; + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs new file mode 100644 index 0000000000..7ccb3f7807 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirement.cs @@ -0,0 +1,72 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +/// +/// Represents the Organization Data Ownership policy state. +/// +public enum OrganizationDataOwnershipState +{ + /// + /// Organization Data Ownership is enforced- members are required to save items to an organization. + /// + Enabled = 1, + + /// + /// Organization Data Ownership is not enforced- users can save items to their personal vault. + /// + Disabled = 2 +} + +/// +/// Policy requirements for the Organization data ownership policy +/// +public class OrganizationDataOwnershipPolicyRequirement : IPolicyRequirement +{ + private readonly IEnumerable _organizationIdsWithPolicyEnabled; + + /// + /// The organization data ownership state for the user. + /// + /// + /// The collection of Organization IDs that have the Organization Data Ownership policy enabled. + /// + public OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState organizationDataOwnershipState, + IEnumerable organizationIdsWithPolicyEnabled) + { + _organizationIdsWithPolicyEnabled = organizationIdsWithPolicyEnabled ?? []; + State = organizationDataOwnershipState; + } + + /// + /// The Organization data ownership policy state for the user. + /// + public OrganizationDataOwnershipState State { get; } + + /// + /// Returns true if the Organization Data Ownership policy is enforced in that organization. + /// + public bool RequiresDefaultCollection(Guid organizationId) + { + return _organizationIdsWithPolicyEnabled.Contains(organizationId); + } +} + +public class OrganizationDataOwnershipPolicyRequirementFactory : BasePolicyRequirementFactory +{ + public override PolicyType PolicyType => PolicyType.OrganizationDataOwnership; + + public override OrganizationDataOwnershipPolicyRequirement Create(IEnumerable policyDetails) + { + var organizationDataOwnershipState = policyDetails.Any() + ? OrganizationDataOwnershipState.Enabled + : OrganizationDataOwnershipState.Disabled; + var organizationIdsWithPolicyEnabled = policyDetails.Select(p => p.OrganizationId).ToHashSet(); + + return new OrganizationDataOwnershipPolicyRequirement( + organizationDataOwnershipState, + organizationIdsWithPolicyEnabled); + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirement.cs deleted file mode 100644 index 6f3f017bb9..0000000000 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirement.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; - -namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; - -/// -/// Policy requirements for the Disable Personal Ownership policy. -/// -public class PersonalOwnershipPolicyRequirement : IPolicyRequirement -{ - /// - /// Indicates whether Personal Ownership is disabled for the user. If true, members are required to save items to an organization. - /// - public bool DisablePersonalOwnership { get; init; } -} - -public class PersonalOwnershipPolicyRequirementFactory : BasePolicyRequirementFactory -{ - public override PolicyType PolicyType => PolicyType.PersonalOwnership; - - public override PersonalOwnershipPolicyRequirement Create(IEnumerable policyDetails) - { - var result = new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = policyDetails.Any() }; - return result; - } -} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index f98135b70d..87fdcbe543 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -34,7 +34,7 @@ public static class PolicyServiceCollectionExtensions services.AddScoped, DisableSendPolicyRequirementFactory>(); services.AddScoped, SendOptionsPolicyRequirementFactory>(); services.AddScoped, ResetPasswordPolicyRequirementFactory>(); - services.AddScoped, PersonalOwnershipPolicyRequirementFactory>(); + services.AddScoped, OrganizationDataOwnershipPolicyRequirementFactory>(); services.AddScoped, RequireSsoPolicyRequirementFactory>(); services.AddScoped, RequireTwoFactorPolicyRequirementFactory>(); } diff --git a/src/Core/AdminConsole/Services/IAzureServiceBusService.cs b/src/Core/AdminConsole/Services/IAzureServiceBusService.cs index d254e763d5..75864255c2 100644 --- a/src/Core/AdminConsole/Services/IAzureServiceBusService.cs +++ b/src/Core/AdminConsole/Services/IAzureServiceBusService.cs @@ -1,5 +1,5 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; namespace Bit.Core.Services; diff --git a/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs b/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs index 560da576b7..b80b518223 100644 --- a/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs +++ b/src/Core/AdminConsole/Services/IEventIntegrationPublisher.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; namespace Bit.Core.Services; diff --git a/src/Core/AdminConsole/Services/IIntegrationFilterService.cs b/src/Core/AdminConsole/Services/IIntegrationFilterService.cs new file mode 100644 index 0000000000..5bc035d468 --- /dev/null +++ b/src/Core/AdminConsole/Services/IIntegrationFilterService.cs @@ -0,0 +1,11 @@ +#nullable enable + +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Models.Data; + +namespace Bit.Core.Services; + +public interface IIntegrationFilterService +{ + bool EvaluateFilterGroup(IntegrationFilterGroup group, EventMessage message); +} diff --git a/src/Core/AdminConsole/Services/IIntegrationHandler.cs b/src/Core/AdminConsole/Services/IIntegrationHandler.cs index bf6e6791cf..e02f26a873 100644 --- a/src/Core/AdminConsole/Services/IIntegrationHandler.cs +++ b/src/Core/AdminConsole/Services/IIntegrationHandler.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; namespace Bit.Core.Services; diff --git a/src/Core/AdminConsole/Services/IOrganizationService.cs b/src/Core/AdminConsole/Services/IOrganizationService.cs index 5fe68bd22e..feae561a19 100644 --- a/src/Core/AdminConsole/Services/IOrganizationService.cs +++ b/src/Core/AdminConsole/Services/IOrganizationService.cs @@ -1,5 +1,4 @@ -using System.Security.Claims; -using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Models.Business; using Bit.Core.Auth.Enums; using Bit.Core.Entities; @@ -42,7 +41,6 @@ public interface IOrganizationService Task RevokeUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser); Task>> RevokeUsersAsync(Guid organizationId, IEnumerable organizationUserIds, Guid? revokingUserId); - Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted); Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null); Task<(bool canScale, string failureReason)> CanScaleAsync(Organization organization, int seatsToAdd); diff --git a/src/Core/AdminConsole/Services/IRabbitMqService.cs b/src/Core/AdminConsole/Services/IRabbitMqService.cs index b0b9a72eac..12c40c3b98 100644 --- a/src/Core/AdminConsole/Services/IRabbitMqService.cs +++ b/src/Core/AdminConsole/Services/IRabbitMqService.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using RabbitMQ.Client; using RabbitMQ.Client.Events; diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrationHandler.cs deleted file mode 100644 index aa76fdf8bc..0000000000 --- a/src/Core/AdminConsole/Services/Implementations/EventIntegrationHandler.cs +++ /dev/null @@ -1,85 +0,0 @@ -#nullable enable - -using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.Integrations; -using Bit.Core.AdminConsole.Utilities; -using Bit.Core.Enums; -using Bit.Core.Models.Data; -using Bit.Core.Repositories; - -namespace Bit.Core.Services; - -public class EventIntegrationHandler( - IntegrationType integrationType, - IEventIntegrationPublisher eventIntegrationPublisher, - IOrganizationIntegrationConfigurationRepository configurationRepository, - IUserRepository userRepository, - IOrganizationRepository organizationRepository) - : IEventMessageHandler -{ - public async Task HandleEventAsync(EventMessage eventMessage) - { - if (eventMessage.OrganizationId is not Guid organizationId) - { - return; - } - - var configurations = await configurationRepository.GetConfigurationDetailsAsync( - organizationId, - integrationType, - eventMessage.Type); - - foreach (var configuration in configurations) - { - var template = configuration.Template ?? string.Empty; - var context = await BuildContextAsync(eventMessage, template); - var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(template, context); - var messageId = eventMessage.IdempotencyId ?? Guid.NewGuid(); - - var config = configuration.MergedConfiguration.Deserialize() - ?? throw new InvalidOperationException($"Failed to deserialize to {typeof(T).Name}"); - - var message = new IntegrationMessage - { - IntegrationType = integrationType, - MessageId = messageId.ToString(), - Configuration = config, - RenderedTemplate = renderedTemplate, - RetryCount = 0, - DelayUntilDate = null - }; - - await eventIntegrationPublisher.PublishAsync(message); - } - } - - public async Task HandleManyEventsAsync(IEnumerable eventMessages) - { - foreach (var eventMessage in eventMessages) - { - await HandleEventAsync(eventMessage); - } - } - - private async Task BuildContextAsync(EventMessage eventMessage, string template) - { - var context = new IntegrationTemplateContext(eventMessage); - - if (IntegrationTemplateProcessor.TemplateRequiresUser(template) && eventMessage.UserId.HasValue) - { - context.User = await userRepository.GetByIdAsync(eventMessage.UserId.Value); - } - - if (IntegrationTemplateProcessor.TemplateRequiresActingUser(template) && eventMessage.ActingUserId.HasValue) - { - context.ActingUser = await userRepository.GetByIdAsync(eventMessage.ActingUserId.Value); - } - - if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template) && eventMessage.OrganizationId.HasValue) - { - context.Organization = await organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value); - } - - return context; - } -} diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs similarity index 93% rename from src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs index 8b00204775..ffa148fc08 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusEventListenerService.cs @@ -33,6 +33,13 @@ public class AzureServiceBusEventListenerService : EventLoggingListenerService await _processor.StartProcessingAsync(cancellationToken); } + public override async Task StopAsync(CancellationToken cancellationToken) + { + await _processor.StopProcessingAsync(cancellationToken); + await _processor.DisposeAsync(); + await base.StopAsync(cancellationToken); + } + internal Task ProcessErrorAsync(ProcessErrorEventArgs args) { _logger.LogError( @@ -49,16 +56,4 @@ public class AzureServiceBusEventListenerService : EventLoggingListenerService await ProcessReceivedMessageAsync(Encoding.UTF8.GetString(args.Message.Body), args.Message.MessageId); await args.CompleteMessageAsync(args.Message); } - - public override async Task StopAsync(CancellationToken cancellationToken) - { - await _processor.StopProcessingAsync(cancellationToken); - await base.StopAsync(cancellationToken); - } - - public override void Dispose() - { - _processor.DisposeAsync().GetAwaiter().GetResult(); - base.Dispose(); - } } diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusIntegrationListenerService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusIntegrationListenerService.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/AzureServiceBusIntegrationListenerService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusIntegrationListenerService.cs diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs similarity index 97% rename from src/Core/AdminConsole/Services/Implementations/AzureServiceBusService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs index 7d24095819..4887aa3a7f 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/AzureServiceBusService.cs @@ -1,5 +1,5 @@ using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; using Bit.Core.Settings; diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrationEventWriteService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationEventWriteService.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/EventIntegrationEventWriteService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationEventWriteService.cs diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs new file mode 100644 index 0000000000..3ffd08edd2 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventIntegrationHandler.cs @@ -0,0 +1,110 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.AdminConsole.Utilities; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Services; + +public class EventIntegrationHandler( + IntegrationType integrationType, + IEventIntegrationPublisher eventIntegrationPublisher, + IIntegrationFilterService integrationFilterService, + IOrganizationIntegrationConfigurationRepository configurationRepository, + IUserRepository userRepository, + IOrganizationRepository organizationRepository, + ILogger> logger) + : IEventMessageHandler +{ + public async Task HandleEventAsync(EventMessage eventMessage) + { + if (eventMessage.OrganizationId is not Guid organizationId) + { + return; + } + + var configurations = await configurationRepository.GetConfigurationDetailsAsync( + organizationId, + integrationType, + eventMessage.Type); + + foreach (var configuration in configurations) + { + try + { + if (configuration.Filters is string filterJson) + { + // Evaluate filters - if false, then discard and do not process + var filters = JsonSerializer.Deserialize(filterJson) + ?? throw new InvalidOperationException($"Failed to deserialize Filters to FilterGroup"); + if (!integrationFilterService.EvaluateFilterGroup(filters, eventMessage)) + { + continue; + } + } + + // Valid filter - assemble message and publish to Integration topic/exchange + var template = configuration.Template ?? string.Empty; + var context = await BuildContextAsync(eventMessage, template); + var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(template, context); + var messageId = eventMessage.IdempotencyId ?? Guid.NewGuid(); + var config = configuration.MergedConfiguration.Deserialize() + ?? throw new InvalidOperationException($"Failed to deserialize to {typeof(T).Name} - bad Configuration"); + + var message = new IntegrationMessage + { + IntegrationType = integrationType, + MessageId = messageId.ToString(), + Configuration = config, + RenderedTemplate = renderedTemplate, + RetryCount = 0, + DelayUntilDate = null + }; + + await eventIntegrationPublisher.PublishAsync(message); + } + catch (Exception exception) + { + logger.LogError( + exception, + "Failed to publish Integration Message for {Type}, check Id {RecordId} for error in Configuration or Filters", + typeof(T).Name, + configuration.Id); + } + } + } + + public async Task HandleManyEventsAsync(IEnumerable eventMessages) + { + foreach (var eventMessage in eventMessages) + { + await HandleEventAsync(eventMessage); + } + } + + private async Task BuildContextAsync(EventMessage eventMessage, string template) + { + var context = new IntegrationTemplateContext(eventMessage); + + if (IntegrationTemplateProcessor.TemplateRequiresUser(template) && eventMessage.UserId.HasValue) + { + context.User = await userRepository.GetByIdAsync(eventMessage.UserId.Value); + } + + if (IntegrationTemplateProcessor.TemplateRequiresActingUser(template) && eventMessage.ActingUserId.HasValue) + { + context.ActingUser = await userRepository.GetByIdAsync(eventMessage.ActingUserId.Value); + } + + if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template) && eventMessage.OrganizationId.HasValue) + { + context.Organization = await organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value); + } + + return context; + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRepositoryHandler.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRepositoryHandler.cs diff --git a/src/Core/AdminConsole/Services/Implementations/EventRouteService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRouteService.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/EventRouteService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/EventRouteService.cs diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs new file mode 100644 index 0000000000..b90ea8d16e --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterFactory.cs @@ -0,0 +1,51 @@ +#nullable enable + +using System.Linq.Expressions; +using Bit.Core.Models.Data; + +namespace Bit.Core.Services; + +public delegate bool IntegrationFilter(EventMessage message, object? value); + +public static class IntegrationFilterFactory +{ + public static IntegrationFilter BuildEqualityFilter(string propertyName) + { + var param = Expression.Parameter(typeof(EventMessage), "m"); + var valueParam = Expression.Parameter(typeof(object), "val"); + + var property = Expression.PropertyOrField(param, propertyName); + var typedVal = Expression.Convert(valueParam, typeof(T)); + var body = Expression.Equal(property, typedVal); + + var lambda = Expression.Lambda>(body, param, valueParam); + return new IntegrationFilter(lambda.Compile()); + } + + public static IntegrationFilter BuildInFilter(string propertyName) + { + var param = Expression.Parameter(typeof(EventMessage), "m"); + var valueParam = Expression.Parameter(typeof(object), "val"); + + var property = Expression.PropertyOrField(param, propertyName); + + var method = typeof(Enumerable) + .GetMethods() + .FirstOrDefault(m => + m.Name == "Contains" + && m.GetParameters().Length == 2) + ?.MakeGenericMethod(typeof(T)); + if (method is null) + { + throw new InvalidOperationException("Could not find Contains method."); + } + + var listType = typeof(IEnumerable); + var castedList = Expression.Convert(valueParam, listType); + + var containsCall = Expression.Call(method, castedList, property); + + var lambda = Expression.Lambda>(containsCall, param, valueParam); + return new IntegrationFilter(lambda.Compile()); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs new file mode 100644 index 0000000000..88877c329a --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/IntegrationFilterService.cs @@ -0,0 +1,110 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Models.Data; + +namespace Bit.Core.Services; + +public class IntegrationFilterService : IIntegrationFilterService +{ + private readonly Dictionary _equalsFilters = new(); + private readonly Dictionary _inFilters = new(); + private static readonly string[] _filterableProperties = new[] + { + "UserId", + "InstallationId", + "ProviderId", + "CipherId", + "CollectionId", + "GroupId", + "PolicyId", + "OrganizationUserId", + "ProviderUserId", + "ProviderOrganizationId", + "ActingUserId", + "SecretId", + "ServiceAccountId" + }; + + public IntegrationFilterService() + { + BuildFilters(); + } + + public bool EvaluateFilterGroup(IntegrationFilterGroup group, EventMessage message) + { + var ruleResults = group.Rules?.Select( + rule => EvaluateRule(rule, message) + ) ?? Enumerable.Empty(); + var groupResults = group.Groups?.Select( + innerGroup => EvaluateFilterGroup(innerGroup, message) + ) ?? Enumerable.Empty(); + + var results = ruleResults.Concat(groupResults); + return group.AndOperator ? results.All(r => r) : results.Any(r => r); + } + + private bool EvaluateRule(IntegrationFilterRule rule, EventMessage message) + { + var key = rule.Property; + return rule.Operation switch + { + IntegrationFilterOperation.Equals => _equalsFilters.TryGetValue(key, out var equals) && + equals(message, ToGuid(rule.Value)), + IntegrationFilterOperation.NotEquals => !(_equalsFilters.TryGetValue(key, out var equals) && + equals(message, ToGuid(rule.Value))), + IntegrationFilterOperation.In => _inFilters.TryGetValue(key, out var inList) && + inList(message, ToGuidList(rule.Value)), + IntegrationFilterOperation.NotIn => !(_inFilters.TryGetValue(key, out var inList) && + inList(message, ToGuidList(rule.Value))), + _ => false + }; + } + + private void BuildFilters() + { + foreach (var property in _filterableProperties) + { + _equalsFilters[property] = IntegrationFilterFactory.BuildEqualityFilter(property); + _inFilters[property] = IntegrationFilterFactory.BuildInFilter(property); + } + } + + private static Guid? ToGuid(object? value) + { + if (value is Guid guid) + { + return guid; + } + if (value is string stringValue) + { + return Guid.Parse(stringValue); + } + if (value is JsonElement jsonElement) + { + return jsonElement.GetGuid(); + } + + throw new InvalidCastException("Could not convert value to Guid"); + } + + private static IEnumerable ToGuidList(object? value) + { + if (value is IEnumerable guidList) + { + return guidList; + } + if (value is JsonElement { ValueKind: JsonValueKind.Array } jsonElement) + { + var list = new List(); + foreach (var item in jsonElement.EnumerateArray()) + { + list.Add(ToGuid(item)); + } + return list; + } + + throw new InvalidCastException("Could not convert value to Guid[]"); + } +} diff --git a/src/Core/AdminConsole/Services/Implementations/EventIntegrations/README.md b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/README.md new file mode 100644 index 0000000000..7320f8bab7 --- /dev/null +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/README.md @@ -0,0 +1,421 @@ +# Design goals + +The main goal of event integrations is to easily enable adding new integrations over time without the need +for a lot of custom work to expose events to a new integration. The ability of fan-out offered by AMQP +(either in RabbitMQ or in Azure Service Bus) gives us a way to attach any number of new integrations to the +existing event system without needing to add special handling. By adding a new listener to the existing +pipeline, it gains an independent stream of events without the need for additional broadcast code. + +We want to enable robust handling of failures and retries. By utilizing the two-tier approach +([described below](#two-tier-exchange)), we build in support at the service level for retries. When we add +new integrations, they can focus solely on the integration-specific logic and reporting status, with all the +process of retries and delays managed by the messaging system. + +Another goal is to not only support this functionality in the cloud version, but offer it as well to +self-hosted instances. RabbitMQ provides a lightweight way for self-hosted instances to tie into the event system +using the same robust architecture for integrations without the need for Azure Service Bus. + +Finally, we want to offer organization admins flexibility and control over what events are significant, where +to send events, and the data to be included in the message. The configuration architecture allows Organizations +to customize details of a specific integration; see [Integrations and integration +configurations](#integrations-and-integration-configurations) below for more details on the configuration piece. + +# Architecture + +The entry point for the event integrations is the `IEventWriteService`. By configuring the +`EventIntegrationEventWriteService` as the `EventWriteService`, all events sent to the +service are broadcast on the RabbitMQ or Azure Service Bus message exchange. To abstract away +the specifics of publishing to a specific AMQP provider, an `IEventIntegrationPublisher` +is injected into `EventIntegrationEventWriteService` to handle the publishing of events to the +RabbitMQ or Azure Service Bus service. + +## Two-tier exchange + +When `EventIntegrationEventWriteService` publishes, it posts to the first tier of our two-tier +approach to handling messages. Each tier is represented in the AMQP stack by a separate exchange +(in RabbitMQ terminology) or topic (in Azure Service Bus). + +``` mermaid +flowchart TD + B1[EventService] + B2[EventIntegrationEventWriteService] + B3[Event Exchange / Topic] + B4[EventRepositoryHandler] + B5[WebhookIntegrationHandler] + B6[Events in Database / Azure Tables] + B7[HTTP Server] + B8[SlackIntegrationHandler] + B9[Slack] + B10[EventIntegrationHandler] + B12[Integration Exchange / Topic] + + B1 -->|IEventWriteService| B2 --> B3 + B3-->|EventListenerService| B4 --> B6 + B3-->|EventListenerService| B10 + B3-->|EventListenerService| B10 + B10 --> B12 + B12 -->|IntegrationListenerService| B5 + B12 -->|IntegrationListenerService| B8 + B5 -->|HTTP POST| B7 + B8 -->|HTTP POST| B9 +``` + +### Event tier + +In the first tier, events are broadcast in a fan-out to a series of listeners. The message body +is a JSON representation of an individual `EventMessage` or an array of `EventMessage`. Handlers at +this level are responsible for handling each event or array of events. There are currently two handlers +at this level: + - `EventRepositoryHandler` + - The `EventRepositoryHandler` is responsible for long term storage of events. It receives all events + and stores them via an injected `IEventRepository` into the database. + - This mirrors the behavior of when event integrations are turned off - cloud stores to Azure Tables + and self-hosted is stored to the database. + - `EventIntegrationHandler` + - The `EventIntegrationHandler` is a generic class that is customized to each integration (via the + configuration details of the integration) and is responsible for determining if there's a configuration + for this event / organization / integration, fetching that configuration, and parsing the details of the + event into a template string. + - The `EventIntegrationHandler` uses the injected `IOrganizationIntegrationConfigurationRepository` to pull + the specific set of configuration and template based on the event type, organization, and integration type. + This configuration is what determines if an integration should be sent, what details are necessary for sending + it, and the actual message to send. + - The output of `EventIntegrationHandler` is a new `IntegrationMessage`, with the details of this + the configuration necessary to interact with the integration and the message to send (with all the event + details incorporated), published to the integration level of the message bus. + +### Integration tier + +At the integration level, messages are JSON representations of `IIntegrationMessage` - specifically they +will be concrete types of the generic `IntegrationMessage` where `` is the configuration details of the +specific integration for which they've been sent. These messages represent the details required for +sending a specific event to a specific integration, including handling retries and delays. + +Handlers at the integration level are tied directly to the integration (e.g. `SlackIntegrationHandler`, +`WebhookIntegrationHandler`). These handlers take in `IntegrationMessage` and output +`IntegrationHandlerResult`, which tells the listener the outcome of the integration (e.g. success / fail, +if it can be retried and any minimum delay that should occur). This makes them easy to unit test in isolation +without any of the concerns of AMQP or messaging. + +The listeners at this level are responsible for firing off the handler when a new message comes in and then +taking the correct action based on the result. Successful results simply acknowledge the message and resolve. +Failures will either be sent to the dead letter queue (DLQ) or re-published for retry after the correct amount of delay. + +### Retries + +One of the goals of introducing the integration level is to simplify and enable the process of multiple retries +for a specific event integration. For instance, if a service is temporarily down, we don't want one of our handlers +blocking the rest of the queue while it waits to retry. In addition, we don't want to retry _all_ integrations for a +specific event if only one integration fails nor do we want to re-lookup the configuration details. By splitting +out the `IntegrationMessage` with the configuration, message, and details around retries, we can process each +event / integration individually and retry easily. + +When the `IntegrationHandlerResult.Success` is set to `false` (indicating that the integration attempt failed) the +`Retryable` flag tells the listener whether this failure is temporary or final. If the `Retryable` is `false`, then +the message is immediately sent to the DLQ. If it is `true`, the listener uses the `ApplyRetry(DateTime)` method +in `IntegrationMessage` which handles both incrementing the `RetryCount` and updating the `DelayUntilDate` using +the provided DateTime, but also adding exponential backoff (based on `RetryCount`) and jitter. The listener compares +the `RetryCount` in the `IntegrationMessage` to see if it's over the `MaxRetries` defined in Global Settings. If it +is over the `MaxRetries`, the message is sent to the DLQ. Otherwise, it is scheduled for retry. + +``` mermaid +flowchart TD +A[Success == false] --> B{Retryable?} + B -- No --> C[Send to Dead Letter Queue DLQ] + B -- Yes --> D[Check RetryCount vs MaxRetries] + D -->|RetryCount >= MaxRetries| E[Send to Dead Letter Queue DLQ] + D -->|RetryCount < MaxRetries| F[Schedule for Retry] +``` + +Azure Service Bus supports scheduling messages as part of its core functionality. Retries are scheduled to a specific +time and then ASB holds the message and publishes it at the correct time. + +#### RabbitMQ retry options + +For RabbitMQ (which will be used by self-host only), we have two different options. The `useDelayPlugin` flag in +`GlobalSettings.RabbitMqSettings` determines which one is used. If it is set to `true`, we use the delay plugin. It +defaults to `false` which indicates we should use retry queues with a timing check. + +1. Delay plugin + - [Delay plugin GitHub repo](https://github.com/rabbitmq/rabbitmq-delayed-message-exchange) + - This plugin enables a delayed message exchange in RabbitMQ that supports delaying a message for an amount + of time specified in a special header. + - This allows us to forego using any retry queues and rely instead on the delay exchange. When a message is + marked with the header it gets published to the exchange and the exchange handles all the functionality of + holding it until the appropriate time (similar to ASB's built-in support). + - The plugin must be setup and enabled before turning this option on (which is why it defaults to off). + +2. Retry queues + timing check + - If the delay plugin setting is off, we push the message to a retry queue which has a fixed amount of time before + it gets re-published back to the main queue. + - When a message comes off the queue, we check to see if the `DelayUntilDate` has already passed. + - If it has passed, we then handle the integration normally and retry the request. + - If it is still in the future, we put the message back on the retry queue for an additional wait. + - While this does use extra processing, it gives us better support for honoring the delays even if the delay plugin + isn't enabled. Since this solution is only intended for self-host, it should be a pretty minimal impact with short + delays and a small number of retries. + +## Listener / Handler pattern + +To make it easy to support multiple AMQP services (RabbitMQ and Azure Service Bus), the act +of listening to the stream of messages is decoupled from the act of responding to a message. + +### Listeners + +- Listeners handle the details of the communication platform (i.e. RabbitMQ and Azure Service Bus). +- There is one listener for each platform (RabbitMQ / ASB) for each of the two levels - i.e. one event listener + and one integration listener. +- Perform all the aspects of setup / teardown, subscription, message acknowledgement, etc. for the messaging platform, + but do not directly process any events themselves. Instead, they delegate to the handler with which they + are configured. +- Multiple instances can be configured to run independently, each with its own handler and + subscription / queue. + +### Handlers + +- One handler per queue / subscription (e.g. per integration at the integration level). +- Completely isolated from and know nothing of the messaging platform in use. This allows them to be + freely reused across different communication platforms. +- Perform all aspects of handling an event. +- Allows them to be highly testable as they are isolated and decoupled from the more complicated + aspects of messaging. + +This combination allows for a configuration inside of `ServiceCollectionExtensions.cs` that pairs +instances of the listener service for the currently running messaging platform with any number of +handlers. It also allows for quick development of new handlers as they are focused only on the +task of handling a specific event. + +## Publishers and Services + +Listeners (and `EventIntegrationHandler`) interact with the messaging system via the `IEventPublisher` interface, +which is backed by a RabbitMQ and ASB specific service. By placing most of the messaging platform details in the +service layer, we are able to handle common things like configuring the connection, binding or creating a specific +queue, etc. in one place. The `IRabbitMqService` and `IAzureServiceBusService` implement the `IEventPublisher` +interface and therefore can also handle directly all the message publishing functionality. + +## Integrations and integration configurations + +Organizations can configure integration configurations to send events to different endpoints -- each +handler maps to a specific integration and checks for the configuration when it receives an event. +Currently, there are integrations / handlers for Slack and webhooks (as mentioned above). + +### `OrganizationIntegration` + +- The top-level object that enables a specific integration for the organization. +- Includes any properties that apply to the entire integration across all events. + - For Slack, it consists of the token: `{ "token": "xoxb-token-from-slack" }` + - For webhooks, it is `null`. However, even though there is no configuration, an organization must + have a webhook `OrganizationIntegration` to enable configuration via `OrganizationIntegrationConfiguration`. + +### `OrganizationIntegrationConfiguration` + +- This contains the configurations specific to each `EventType` for the integration. +- `Configuration` contains the event-specific configuration. + - For Slack, this would contain what channel to send the message to: `{ "channelId": "C123456" }` + - For Webhook, this is the URL the request should be sent to: `{ "url": "https://api.example.com" }` +- `Template` contains a template string that is expected to be filled in with the contents of the actual event. + - The tokens in the string are wrapped in `#` characters. For instance, the UserId would be `#UserId#`. + - The `IntegrationTemplateProcessor` does the actual work of replacing these tokens with introspected values from + the provided `EventMessage`. + - The template does not enforce any structure — it could be a freeform text message to send via Slack, or a + JSON body to send via webhook; it is simply stored and used as a string for the most flexibility. + +### `OrganizationIntegrationConfigurationDetails` + +- This is the combination of both the `OrganizationIntegration` and `OrganizationIntegrationConfiguration` into + a single object. The combined contents tell the integration's handler all the details needed to send to an + external service. +- An array of `OrganizationIntegrationConfigurationDetails` is what the `EventIntegrationHandler` fetches from + the database to determine what to publish at the integration level. + +## Filtering + +In addition to the ability to configure integrations mentioned above, organization admins can +also add `Filters` stored in the `OrganizationIntegrationConfiguration`. Filters are completely +optional and as simple or complex as organization admins want to make them. These are stored in +the database as JSON and serialized into an `IntegrationFilterGroup`. This is then passed to +the `IntegrationFilterService`, which evaluates it to a `bool`. If it's `true`, the integration +proceeds as above. If it's `false`, we ignore this event and do not route it to the integration +level. + +### `IntegrationFilterGroup` + +Logical AND / OR grouping of a number of rules and other subgroups. + +| Property | Description | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AndOperator` | Indicates whether **all** (`true`) or **any** (`false`) of the `Rules` and `Groups` must be true. This applies to _both_ the inner group and the list of rules; for instance, if this group contained Rule1 and Rule2 as well as Group1 and Group2:

`true`: `Rule1 && Rule2 && Group1 && Group2`
`false`: `Rule1 \|\| Rule2 \|\| Group1 \|\| Group2` | +| `Rules` | A list of `IntegrationFilterRule`. Can be null or empty, in which case it will return `true`. | +| `Groups` | A list of nested `IntegrationFilterGroup`. Can be null or empty, in which case it will return `true`. | + +### `IntegrationFilterRule` + +The core of the filtering framework to determine if the data in this specific EventMessage +matches the data for which the filter is searching. + +| Property | Description | +|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Property` | The property on `EventMessage` to evaluate (e.g., `CollectionId`). | +| `Operation` | The comparison to perform between the property and `Value`.

**Supported operations:**
• `Equals`: `Guid` equals `Value`
• `NotEquals`: logical inverse of `Equals`
• `In`: `Guid` is in `Value` list
• `NotIn`: logical inverse of `In` | +| `Value` | The comparison value. Type depends on `Operation`:
• `Equals`, `NotEquals`: `Guid`
• `In`, `NotIn`: list of `Guid` | + +```mermaid +graph TD + A[IntegrationFilterGroup] + A -->|Has 0..many| B1[IntegrationFilterRule] + A --> D1[And Operator] + A -->|Has 0..many| C1[Nested IntegrationFilterGroup] + + B1 --> B2[Property: string] + B1 --> B3[Operation: Equals/In/DateBefore/DateAfter] + B1 --> B4[Value: object?] + + C1 -->|Has many| B1_2[IntegrationFilterRule] + C1 -->|Can contain| C2[IntegrationFilterGroup...] +``` + +# Building a new integration + +These are all the pieces required in the process of building out a new integration. For +clarity in naming, these assume a new integration called "Example". + +## IntegrationType + +Add a new type to `IntegrationType` for the new integration. + +## Configuration Models + +The configuration models are the classes that will determine what is stored in the database for +`OrganizationIntegration` and `OrganizationIntegrationConfiguration`. The `Configuration` columns are the +serialized version of the corresponding objects and represent the coonfiguration details for this integration +and event type. + +1. `ExampleIntegration` + - Configuration details for the whole integration (e.g. a token in Slack). + - Applies to every event type configuration defined for this integration. + - Maps to the JSON structure stored in `Configuration` in ``OrganizationIntegration`. +2. `ExampleIntegrationConfiguration` + - Configuration details that could change from event to event (e.g. channelId in Slack). + - Maps to the JSON structure stored in `Configuration` in `OrganizationIntegrationConfiguration`. +3. `ExampleIntegrationConfigurationDetails` + - Combined configuration of both Integration _and_ IntegrationConfiguration. + - This will be the deserialized version of the `MergedConfiguration` in + `OrganizationIntegrationConfigurationDetails`. + +## Request Models + +1. Add a new case to the switch method in `OrganizationIntegrationRequestModel.Validate`. +2. Add a new case to the switch method in `OrganizationIntegrationConfigurationRequestModel.IsValidForType`. + +## Integration Handler + +e.g. `ExampleIntegrationHandler` +- This is where the actual code will go to perform the integration (i.e. send an HTTP request, etc.). +- Handlers receive an `IntegrationMessage` where `` is the `ExampleIntegrationConfigurationDetails` + defined above. This has the Configuration as well as the rendered template message to be sent. +- Handlers return an `IntegrationHandlerResult` with details about if the request - success / failure, + if it can be retried, when it should be delayed until, etc. +- The scope of the handler is simply to do the integration and report the result. + Everything else (such as how many times to retry, when to retry, what to do with failures) + is done in the Listener. + +## GlobalSettings + +### RabbitMQ +Add the queue names for the integration. These are typically set with a default value so +that they will be created when first accessed in code by RabbitMQ. + +1. `ExampleEventQueueName` +2. `ExampleIntegrationQueueName` +3. `ExampleIntegrationRetryQueueName` + +### Azure Service Bus +Add the subscription names to use for ASB for this integration. Similar to RabbitMQ a +default value is provided so that we don't require configuring it in secrets but allow +it to be overridden. **However**, unlike RabbitMQ these subscriptions must exist prior +to the code accessing them. They will not be created on the fly. See [Deploying a new +integration](#deploying-a-new-integration) below + +1. `ExmpleEventSubscriptionName` +2. `ExmpleIntegrationSubscriptionName` + +#### Service Bus Emulator, local config +In order to create ASB resources locally, we need to also update the `servicebusemulator_config.json` file +to include any new subscriptions. +- Under the existing event topic (`event-logging`) add a subscription for the event level for this + new integration (`events-example-subscription`). +- Under the existing integration topic (`event-integrations`) add a new subscription for the integration + level messages (`integration-example-subscription`). + - Copy the correlation filter from the other integration level subscriptions. It should filter based on + the `IntegrationType.ToRoutingKey`, or in this example `example`. + +These names added here are what must match the values provided in the secrets or the defaults provided +in Global Settings. This must be in place (and the local ASB emulator restarted) before you can use any +code locally that accesses ASB resources. + +## ServiceCollectionExtensions +In our `ServiceCollectionExtensions`, we pull all the above pieces together to start listeners on each message +tier with handlers to process the integration. There are a number of helper methods in here to make this simple +to add a new integration - one call per platform. + +Also note that if an integration needs a custom singleton / service defined, the add listeners method is a +good place to set that up. For instance, `SlackIntegrationHandler` needs a `SlackService`, so the singleton +declaration is right above the add integration method for slack. Same thing for webhooks when it comes to +defining a custom HttpClient by name. + +1. In `AddRabbitMqListeners` add the integration: +``` csharp + services.AddRabbitMqIntegration( + globalSettings.EventLogging.RabbitMq.ExampleEventsQueueName, + globalSettings.EventLogging.RabbitMq.ExampleIntegrationQueueName, + globalSettings.EventLogging.RabbitMq.ExampleIntegrationRetryQueueName, + globalSettings.EventLogging.RabbitMq.MaxRetries, + IntegrationType.Example); +``` + +2. In `AddAzureServiceBusListeners` add the integration: +``` csharp +services.AddAzureServiceBusIntegration( + eventSubscriptionName: globalSettings.EventLogging.AzureServiceBus.ExampleEventSubscriptionName, + integrationSubscriptionName: globalSettings.EventLogging.AzureServiceBus.ExampleIntegrationSubscriptionName, + integrationType: IntegrationType.Example, + globalSettings: globalSettings); +``` + +# Deploying a new integration + +## RabbitMQ + +RabbitMQ dynamically creates queues and exchanges when they are first accessed in code. +Therefore, there is no need to manually create queues when deploying a new integration. +They can be created and configured ahead of time, but it's not required. Note that once +they are created, if any configurations need to be changed, the queue or exchange must be +deleted and recreated. + +## Azure Service Bus + +Unlike RabbitMQ, ASB resources **must** be allocated before the code accesses them and +will not be created on the fly. This means that any subscriptions needed for a new +integration must be created in ASB before that code is deployed. + +The two subscriptions created above in Global Settings and `servicebusemulator_config.json` +need to be created in the Azure portal or CLI for the environment before deploying the +code. + +1. `ExmpleEventSubscriptionName` + - This subscription is a fan-out subscription from the main event topic. + - As such, it will start receiving all the events as soon as it is declared. + - This can create a backlog before the integration-specific handler is declared and deployed. + - One strategy to avoid this is to create the subscription with a false filter (e.g. `1 = 0`). + - This will create the subscription, but the filter will ensure that no messages + actually land in the subscription. + - Code can be deployed that references the subscription, because the subscription + legitimately exists (it is simply empty). + - When the code is in place, and we're ready to start receiving messages on the new + integration, we simply remove the filter to return the subscription to receiving + all messages via fan-out. +2. `ExmpleIntegrationSubscriptionName` + - This subscription must be created before the new integration code can be deployed. + - However, it is not fan-out, but rather a filter based on the `IntegrationType.ToRoutingKey`. + - Therefore, it won't start receiving messages until organizations have active configurations. + This means there's no risk of building up a backlog by declaring it ahead of time. diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqEventListenerService.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqEventListenerService.cs diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqIntegrationListenerService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs similarity index 94% rename from src/Core/AdminConsole/Services/Implementations/RabbitMqIntegrationListenerService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs index 5b18d8817c..db6a7f9510 100644 --- a/src/Core/AdminConsole/Services/Implementations/RabbitMqIntegrationListenerService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqIntegrationListenerService.cs @@ -2,7 +2,7 @@ using System.Text; using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using RabbitMQ.Client; @@ -20,6 +20,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService private readonly Lazy> _lazyChannel; private readonly IRabbitMqService _rabbitMqService; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; public RabbitMqIntegrationListenerService(IIntegrationHandler handler, string routingKey, @@ -27,7 +28,8 @@ public class RabbitMqIntegrationListenerService : BackgroundService string retryQueueName, int maxRetries, IRabbitMqService rabbitMqService, - ILogger logger) + ILogger logger, + TimeProvider timeProvider) { _handler = handler; _routingKey = routingKey; @@ -35,6 +37,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService _queueName = queueName; _rabbitMqService = rabbitMqService; _logger = logger; + _timeProvider = timeProvider; _maxRetries = maxRetries; _lazyChannel = new Lazy>(() => _rabbitMqService.CreateChannelAsync()); } @@ -74,7 +77,7 @@ public class RabbitMqIntegrationListenerService : BackgroundService var integrationMessage = JsonSerializer.Deserialize(json); if (integrationMessage is not null && integrationMessage.DelayUntilDate.HasValue && - integrationMessage.DelayUntilDate.Value > DateTime.UtcNow) + integrationMessage.DelayUntilDate.Value > _timeProvider.GetUtcNow().UtcDateTime) { await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea); await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken); diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs similarity index 99% rename from src/Core/AdminConsole/Services/Implementations/RabbitMqService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs index 617d1b41fb..20ae31a113 100644 --- a/src/Core/AdminConsole/Services/Implementations/RabbitMqService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/RabbitMqService.cs @@ -1,7 +1,7 @@ #nullable enable using System.Text; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; using Bit.Core.Settings; using RabbitMQ.Client; diff --git a/src/Core/AdminConsole/Services/Implementations/SlackIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs similarity index 78% rename from src/Core/AdminConsole/Services/Implementations/SlackIntegrationHandler.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs index fe0f6eabe1..6f55c0cf9c 100644 --- a/src/Core/AdminConsole/Services/Implementations/SlackIntegrationHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackIntegrationHandler.cs @@ -1,6 +1,6 @@ #nullable enable -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; namespace Bit.Core.Services; @@ -11,9 +11,9 @@ public class SlackIntegrationHandler( public override async Task HandleAsync(IntegrationMessage message) { await slackService.SendSlackMessageByChannelIdAsync( - message.Configuration.token, + message.Configuration.Token, message.RenderedTemplate, - message.Configuration.channelId + message.Configuration.ChannelId ); return new IntegrationHandlerResult(success: true, message: message); diff --git a/src/Core/AdminConsole/Services/Implementations/SlackService.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackService.cs similarity index 100% rename from src/Core/AdminConsole/Services/Implementations/SlackService.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/SlackService.cs diff --git a/src/Core/AdminConsole/Services/Implementations/WebhookIntegrationHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs similarity index 73% rename from src/Core/AdminConsole/Services/Implementations/WebhookIntegrationHandler.cs rename to src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs index df364b2a48..b66df59a69 100644 --- a/src/Core/AdminConsole/Services/Implementations/WebhookIntegrationHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventIntegrations/WebhookIntegrationHandler.cs @@ -2,14 +2,17 @@ using System.Globalization; using System.Net; +using System.Net.Http.Headers; using System.Text; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; #nullable enable namespace Bit.Core.Services; -public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) +public class WebhookIntegrationHandler( + IHttpClientFactory httpClientFactory, + TimeProvider timeProvider) : IntegrationHandlerBase { private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName); @@ -18,8 +21,16 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) public override async Task HandleAsync(IntegrationMessage message) { - var content = new StringContent(message.RenderedTemplate, Encoding.UTF8, "application/json"); - var response = await _httpClient.PostAsync(message.Configuration.url, content); + var request = new HttpRequestMessage(HttpMethod.Post, message.Configuration.Url); + request.Content = new StringContent(message.RenderedTemplate, Encoding.UTF8, "application/json"); + if (!string.IsNullOrEmpty(message.Configuration.Scheme)) + { + request.Headers.Authorization = new AuthenticationHeaderValue( + scheme: message.Configuration.Scheme, + parameter: message.Configuration.Token + ); + } + var response = await _httpClient.SendAsync(request); var result = new IntegrationHandlerResult(success: response.IsSuccessStatusCode, message); switch (response.StatusCode) @@ -39,7 +50,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory) if (int.TryParse(value, out var seconds)) { // Retry-after was specified in seconds. Adjust DelayUntilDate by the requested number of seconds. - result.DelayUntilDate = DateTime.UtcNow.AddSeconds(seconds); + result.DelayUntilDate = timeProvider.GetUtcNow().AddSeconds(seconds).UtcDateTime; } else if (DateTimeOffset.TryParseExact(value, "r", // "r" is the round-trip format: RFC1123 diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 4d709bb7cf..d648eef2c9 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -1,5 +1,4 @@ -using System.Security.Claims; -using System.Text.Json; +using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums.Provider; @@ -12,6 +11,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; +using Bit.Core.AdminConsole.Utilities.DebuggingInstruments; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Constants; @@ -98,7 +98,7 @@ public class OrganizationService : IOrganizationService IPricingClient pricingClient, IPolicyRequirementQuery policyRequirementQuery, ISendOrganizationInvitesCommand sendOrganizationInvitesCommand - ) + ) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -199,6 +199,7 @@ public class OrganizationService : IOrganizationService { await AdjustSeatsAsync(organization, seatAdjustment); } + if (maxAutoscaleSeats != organization.MaxAutoscaleSeats) { await UpdateAutoscalingAsync(organization, maxAutoscaleSeats); @@ -207,7 +208,6 @@ public class OrganizationService : IOrganizationService private async Task UpdateAutoscalingAsync(Organization organization, int? maxAutoscaleSeats) { - if (maxAutoscaleSeats.HasValue && organization.Seats.HasValue && maxAutoscaleSeats.Value < organization.Seats.Value) @@ -229,7 +229,8 @@ public class OrganizationService : IOrganizationService if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue && maxAutoscaleSeats > plan.PasswordManager.MaxSeats) { - throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ", + throw new BadRequestException(string.Concat( + $"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ", $"but you have specified a max autoscale count of {maxAutoscaleSeats}.", "Reduce your max autoscale seat count.")); } @@ -250,7 +251,8 @@ public class OrganizationService : IOrganizationService return await AdjustSeatsAsync(organization, seatAdjustment); } - private async Task AdjustSeatsAsync(Organization organization, int seatAdjustment, IEnumerable ownerEmails = null) + private async Task AdjustSeatsAsync(Organization organization, int seatAdjustment, + IEnumerable ownerEmails = null) { if (organization.Seats == null) { @@ -286,10 +288,11 @@ public class OrganizationService : IOrganizationService } var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats; - if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value) + if (plan.PasswordManager.MaxAdditionalSeats.HasValue && + additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value) { throw new BadRequestException($"Organization plan allows a maximum of " + - $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); + $"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats."); } if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) @@ -300,8 +303,9 @@ public class OrganizationService : IOrganizationService { if (organization.UseAdminSponsoredFamilies || seatCounts.Sponsored > 0) { - throw new BadRequestException($"Your organization has {seatCounts.Users} members and {seatCounts.Sponsored} sponsored families. " + - $"To decrease the seat count below {seatCounts.Total}, you must remove members or sponsorships."); + throw new BadRequestException( + $"Your organization has {seatCounts.Users} members and {seatCounts.Sponsored} sponsored families. " + + $"To decrease the seat count below {seatCounts.Total}, you must remove members or sponsorships."); } else { @@ -320,7 +324,8 @@ public class OrganizationService : IOrganizationService organization.Seats = (short?)newSeatTotal; await ReplaceAndUpdateCacheAsync(organization); - if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && organization.Seats == organization.MaxAutoscaleSeats) + if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && + organization.Seats == organization.MaxAutoscaleSeats) { try { @@ -329,7 +334,9 @@ public class OrganizationService : IOrganizationService ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); } - await _mailService.SendOrganizationMaxSeatLimitReachedEmailAsync(organization, organization.MaxAutoscaleSeats.Value, ownerEmails); + + await _mailService.SendOrganizationMaxSeatLimitReachedEmailAsync(organization, + organization.MaxAutoscaleSeats.Value, ownerEmails); } catch (Exception e) { @@ -363,7 +370,7 @@ public class OrganizationService : IOrganizationService } var bankAccount = customer.Sources - .FirstOrDefault(s => s is BankAccount && ((BankAccount)s).Status != "verified") as BankAccount; + .FirstOrDefault(s => s is BankAccount && ((BankAccount)s).Status != "verified") as BankAccount; if (bankAccount == null) { throw new GatewayException("Cannot find an unverified bank account."); @@ -390,7 +397,7 @@ public class OrganizationService : IOrganizationService if (anySingleOrgPolicies) { throw new BadRequestException("You may not create an organization. You belong to an organization " + - "which has a policy that prohibits you from being a member of any other organization."); + "which has a policy that prohibits you from being a member of any other organization."); } } @@ -404,7 +411,7 @@ public class OrganizationService : IOrganizationService if (license.LicenseType != LicenseType.Organization) { throw new BadRequestException("Premium licenses cannot be applied to an organization. " + - "Upload this license from your personal account settings page."); + "Upload this license from your personal account settings page."); } var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license); @@ -423,50 +430,11 @@ public class OrganizationService : IOrganizationService await ValidateSignUpPoliciesAsync(owner.Id); - var organization = new Organization - { - Name = license.Name, - BillingEmail = license.BillingEmail, - BusinessName = license.BusinessName, - PlanType = license.PlanType, - Seats = license.Seats, - MaxCollections = license.MaxCollections, - MaxStorageGb = _globalSettings.SelfHosted ? 10240 : license.MaxStorageGb, // 10 TB - UsePolicies = license.UsePolicies, - UseSso = license.UseSso, - UseKeyConnector = license.UseKeyConnector, - UseScim = license.UseScim, - UseGroups = license.UseGroups, - UseDirectory = license.UseDirectory, - UseEvents = license.UseEvents, - UseTotp = license.UseTotp, - Use2fa = license.Use2fa, - UseApi = license.UseApi, - UseResetPassword = license.UseResetPassword, - Plan = license.Plan, - SelfHost = license.SelfHost, - UsersGetPremium = license.UsersGetPremium, - UseCustomPermissions = license.UseCustomPermissions, - Gateway = null, - GatewayCustomerId = null, - GatewaySubscriptionId = null, - ReferenceData = owner.ReferenceData, - Enabled = license.Enabled, - ExpirationDate = license.Expires, - LicenseKey = license.LicenseKey, - PublicKey = publicKey, - PrivateKey = privateKey, - CreationDate = DateTime.UtcNow, - RevisionDate = DateTime.UtcNow, - Status = OrganizationStatusType.Created, - UsePasswordManager = license.UsePasswordManager, - UseSecretsManager = license.UseSecretsManager, - SmSeats = license.SmSeats, - SmServiceAccounts = license.SmServiceAccounts, - UseRiskInsights = license.UseRiskInsights, - UseOrganizationDomains = license.UseOrganizationDomains, - UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies, - }; + var organization = claimsPrincipal != null + // If the ClaimsPrincipal exists (there's a token on the license), use it to build the organization. + ? OrganizationFactory.Create(owner, claimsPrincipal, publicKey, privateKey) + // If there's no ClaimsPrincipal (there's no token on the license), use the license to build the organization. + : OrganizationFactory.Create(owner, license, publicKey, privateKey); var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false); @@ -481,8 +449,9 @@ public class OrganizationService : IOrganizationService /// Private helper method to create a new organization. /// This is common code used by both the cloud and self-hosted methods. /// - private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignUpAsync(Organization organization, - Guid ownerId, string ownerKey, string collectionName, bool withPayment) + private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> + SignUpAsync(Organization organization, + Guid ownerId, string ownerKey, string collectionName, bool withPayment) { try { @@ -538,7 +507,15 @@ public class OrganizationService : IOrganizationService if (orgUser != null) { defaultOwnerAccess = - [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }]; + [ + new CollectionAccessSelection + { + Id = orgUser.Id, + HidePasswords = false, + ReadOnly = false, + Manage = true + } + ]; } await _collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess); @@ -574,7 +551,8 @@ public class OrganizationService : IOrganizationService } } - public async Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated) + public async Task UpdateAsync(Organization organization, bool updateBilling = false, + EventType eventType = EventType.Organization_Updated) { if (organization.Id == default(Guid)) { @@ -595,11 +573,12 @@ public class OrganizationService : IOrganizationService if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) { var customerService = new CustomerService(); - await customerService.UpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions - { - Email = organization.BillingEmail, - Description = organization.DisplayBusinessName() - }); + await customerService.UpdateAsync(organization.GatewayCustomerId, + new CustomerUpdateOptions + { + Email = organization.BillingEmail, + Description = organization.DisplayBusinessName() + }); } if (eventType == EventType.Organization_CollectionManagement_Updated) @@ -649,7 +628,8 @@ public class OrganizationService : IOrganizationService await UpdateAsync(organization); } - public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, + public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, + EventSystemUser? systemUser, OrganizationUserInvite invite, string externalId) { // Ideally OrganizationUserInvite should represent a single user so that this doesn't have to be a runtime check @@ -662,7 +642,8 @@ public class OrganizationService : IOrganizationService var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); if (invalidAssociations?.Any() ?? false) { - throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); + throw new BadRequestException( + "The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); } var results = await InviteUsersAsync(organizationId, invitingUserId, systemUser, @@ -673,6 +654,7 @@ public class OrganizationService : IOrganizationService { throw new BadRequestException("This user has already been invited."); } + return result; } @@ -684,7 +666,8 @@ public class OrganizationService : IOrganizationService /// The system user which is sending the invite. Only used when inviting via SCIM; null if using a client app or Public API /// Details about the users being invited /// - public async Task> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, EventSystemUser? systemUser, + public async Task> InviteUsersAsync(Guid organizationId, Guid? invitingUserId, + EventSystemUser? systemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) { var inviteTypes = new HashSet(invites.Where(i => i.invite.Type.HasValue) @@ -696,7 +679,8 @@ public class OrganizationService : IOrganizationService { foreach (var (invite, _) in invites) { - await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null, invite.Permissions); + await ValidateOrganizationUserUpdatePermissions(organizationId, invite.Type.Value, null, + invite.Permissions); await ValidateOrganizationCustomPermissionsEnabledAsync(organizationId, invite.Type.Value); } } @@ -706,7 +690,8 @@ public class OrganizationService : IOrganizationService if (systemUser.HasValue) { // Log SCIM event - await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, systemUser.Value, e.Item3))); + await _eventService.LogOrganizationUserEventsAsync(events.Select(e => + (e.Item1, e.Item2, systemUser.Value, e.Item3))); } else { @@ -717,8 +702,10 @@ public class OrganizationService : IOrganizationService return organizationUsers; } - private async Task<(List organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> SaveUsersSendInvitesAsync(Guid organizationId, - IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) + private async + Task<(List organizationUsers, List<(OrganizationUser, EventType, DateTime?)> events)> + SaveUsersSendInvitesAsync(Guid organizationId, + IEnumerable<(OrganizationUserInvite invite, string externalId)> invites) { var organization = await GetOrgById(organizationId); var initialSeatCount = organization.Seats; @@ -728,7 +715,8 @@ public class OrganizationService : IOrganizationService } var existingEmails = new HashSet(await _organizationUserRepository.SelectKnownEmailsAsync( - organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); + organizationId, invites.SelectMany(i => i.invite.Emails), false), + StringComparer.InvariantCultureIgnoreCase); // Seat autoscaling var initialSmSeatCount = organization.SmSeats; @@ -756,7 +744,8 @@ public class OrganizationService : IOrganizationService .SelectMany(i => i.invite.Emails) .Count(email => !existingEmails.Contains(email)); - var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount); + var additionalSmSeatsRequired = + await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(organization.Id, inviteWithSmAccessCount); if (additionalSmSeatsRequired > 0) { var plan = await _pricingClient.GetPlanOrThrow(organization.PlanType); @@ -765,7 +754,9 @@ public class OrganizationService : IOrganizationService } var invitedAreAllOwners = invites.All(i => i.invite.Type == OrganizationUserType.Owner); - if (!invitedAreAllOwners && !await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }, includeProvider: true)) + if (!invitedAreAllOwners && + !await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, new Guid[] { }, + includeProvider: true)) { throw new BadRequestException("Organization must have at least one confirmed owner."); } @@ -888,7 +879,8 @@ public class OrganizationService : IOrganizationService await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdateRevert); } - if (initialSeatCount.HasValue && currentOrganization.Seats.HasValue && currentOrganization.Seats.Value != initialSeatCount.Value) + if (initialSeatCount.HasValue && currentOrganization.Seats.HasValue && + currentOrganization.Seats.Value != initialSeatCount.Value) { await AdjustSeatsAsync(organization, initialSeatCount.Value - currentOrganization.Seats.Value); } @@ -904,10 +896,13 @@ public class OrganizationService : IOrganizationService return (allOrgUsers, events); } - public async Task>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, + public async Task>> ResendInvitesAsync(Guid organizationId, + Guid? invitingUserId, IEnumerable organizationUsersId) { var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId); + _logger.LogUserInviteStateDiagnostics(orgUsers); + var org = await GetOrgById(organizationId); var result = new List>(); @@ -926,7 +921,8 @@ public class OrganizationService : IOrganizationService return result; } - public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false) + public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, + bool initOrganization = false) { var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId); if (orgUser == null || orgUser.OrganizationId != organizationId || @@ -935,6 +931,8 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("User invalid."); } + _logger.LogUserInviteStateDiagnostics(orgUser); + var org = await GetOrgById(orgUser.OrganizationId); await SendInviteAsync(orgUser, org, initOrganization); } @@ -1013,7 +1011,9 @@ public class OrganizationService : IOrganizationService IEnumerable ownerEmails; if (providerOrg != null) { - ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed)) + ownerEmails = + (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, + ProviderUserStatusType.Confirmed)) .Select(u => u.Email).Distinct(); } else @@ -1021,6 +1021,7 @@ public class OrganizationService : IOrganizationService ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); } + var initialSeatCount = organization.Seats.Value; await AdjustSeatsAsync(organization, seatsToAdd, ownerEmails); @@ -1035,8 +1036,8 @@ public class OrganizationService : IOrganizationService } - - public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId) + public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, + Guid? callingUserId) { // Org User must be the same as the calling user and the organization ID associated with the user must match passed org ID var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId); @@ -1064,30 +1065,35 @@ public class OrganizationService : IOrganizationService // Block the user from withdrawal if auto enrollment is enabled if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) { - var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync(userId); + var resetPasswordPolicyRequirement = + await _policyRequirementQuery.GetAsync(userId); if (resetPasswordKey == null && resetPasswordPolicyRequirement.AutoEnrollEnabled(organizationId)) { - throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); + throw new BadRequestException( + "Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); } - } else { if (resetPasswordKey == null && resetPasswordPolicy.Data != null) { - var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, + JsonHelpers.IgnoreCase); if (data?.AutoEnrollEnabled ?? false) { - throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); + throw new BadRequestException( + "Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); } } } orgUser.ResetPasswordKey = resetPasswordKey; await _organizationUserRepository.ReplaceAsync(orgUser); - await _eventService.LogOrganizationUserEventAsync(orgUser, resetPasswordKey != null ? - EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); + await _eventService.LogOrganizationUserEventAsync(orgUser, + resetPasswordKey != null + ? EventType.OrganizationUser_ResetPassword_Enroll + : EventType.OrganizationUser_ResetPassword_Withdraw); } public async Task ImportAsync(Guid organizationId, @@ -1124,15 +1130,16 @@ public class OrganizationService : IOrganizationService var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId); var removeUsersSet = new HashSet(removeUserExternalIds) .Except(newUsersSet) - .Where(u => existingUsersDict.TryGetValue(u, out var existingUser) && existingUser.Type != OrganizationUserType.Owner) + .Where(u => existingUsersDict.TryGetValue(u, out var existingUser) && + existingUser.Type != OrganizationUserType.Owner) .Select(u => existingUsersDict[u]); await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id)); events.AddRange(removeUsersSet.Select(u => ( - u, - EventType.OrganizationUser_Removed, - (DateTime?)DateTime.UtcNow - )) + u, + EventType.OrganizationUser_Removed, + (DateTime?)DateTime.UtcNow + )) ); } @@ -1145,10 +1152,10 @@ public class OrganizationService : IOrganizationService existingExternalUsersIdDict.ContainsKey(u.ExternalId)); await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id)); events.AddRange(usersToDelete.Select(u => ( - u, - EventType.OrganizationUser_Removed, - (DateTime?)DateTime.UtcNow - )) + u, + EventType.OrganizationUser_Removed, + (DateTime?)DateTime.UtcNow + )) ); foreach (var deletedUser in usersToDelete) { @@ -1176,6 +1183,7 @@ public class OrganizationService : IOrganizationService existingExternalUsersIdDict.Add(orgUser.ExternalId, orgUser.Id); } } + await _organizationUserRepository.UpsertManyAsync(usersToUpsert); // Add new users @@ -1186,7 +1194,8 @@ public class OrganizationService : IOrganizationService var enoughSeatsAvailable = true; if (organization.Seats.HasValue) { - var seatCounts = await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); + var seatCounts = + await _organizationRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); seatsAvailable = organization.Seats.Value - seatCounts.Total; enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count; } @@ -1219,7 +1228,8 @@ public class OrganizationService : IOrganizationService } } - var invitedUsers = await InviteUsersAsync(organizationId, invitingUserId: null, systemUser: eventSystemUser, userInvites); + var invitedUsers = await InviteUsersAsync(organizationId, invitingUserId: null, systemUser: eventSystemUser, + userInvites); foreach (var invitedUser in invitedUsers) { existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id); @@ -1256,7 +1266,8 @@ public class OrganizationService : IOrganizationService } await _eventService.LogGroupEventsAsync( - savedGroups.Select(g => (g, EventType.Group_Created, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow))); + savedGroups.Select(g => (g, EventType.Group_Created, (EventSystemUser?)eventSystemUser, + (DateTime?)DateTime.UtcNow))); var updateGroups = existingExternalGroups .Where(g => groupsDict.ContainsKey(g.ExternalId)) @@ -1283,11 +1294,11 @@ public class OrganizationService : IOrganizationService await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, existingExternalUsersIdDict, existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null); - } await _eventService.LogGroupEventsAsync( - updateGroups.Select(g => (g, EventType.Group_Updated, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow))); + updateGroups.Select(g => (g, EventType.Group_Updated, (EventSystemUser?)eventSystemUser, + (DateTime?)DateTime.UtcNow))); } } @@ -1299,10 +1310,12 @@ public class OrganizationService : IOrganizationService await _ssoUserRepository.DeleteAsync(userId, organizationId); if (organizationId.HasValue) { - var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId); + var organizationUser = + await _organizationUserRepository.GetByOrganizationAsync(organizationId.Value, userId); if (organizationUser != null) { - await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UnlinkedSso); + await _eventService.LogOrganizationUserEventAsync(organizationUser, + EventType.OrganizationUser_UnlinkedSso); } } } @@ -1424,7 +1437,7 @@ public class OrganizationService : IOrganizationService } if ((plan.ProductTier == ProductTierType.TeamsStarter && - upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) || + upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) || (plan.ProductTier != ProductTierType.TeamsStarter && upgrade.AdditionalSmSeats.GetValueOrDefault() > upgrade.AdditionalSeats)) { @@ -1447,7 +1460,8 @@ public class OrganizationService : IOrganizationService } } - public async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, OrganizationUserType? oldType, Permissions permissions) + public async Task ValidateOrganizationUserUpdatePermissions(Guid organizationId, OrganizationUserType newType, + OrganizationUserType? oldType, Permissions permissions) { if (await _currentContext.OrganizationOwner(organizationId)) { @@ -1474,13 +1488,15 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Custom users can not manage Admins or Owners."); } - if (newType == OrganizationUserType.Custom && !await ValidateCustomPermissionsGrant(organizationId, permissions)) + if (newType == OrganizationUserType.Custom && + !await ValidateCustomPermissionsGrant(organizationId, permissions)) { throw new BadRequestException("Custom users can only grant the same custom permissions that they have."); } } - public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, OrganizationUserType newType) + public async Task ValidateOrganizationCustomPermissionsEnabledAsync(Guid organizationId, + OrganizationUserType newType) { if (newType != OrganizationUserType.Custom) { @@ -1495,7 +1511,8 @@ public class OrganizationService : IOrganizationService if (!organization.UseCustomPermissions) { - throw new BadRequestException("To enable custom permissions the organization must be on an Enterprise plan."); + throw new BadRequestException( + "To enable custom permissions the organization must be on an Enterprise plan."); } } @@ -1601,7 +1618,8 @@ public class OrganizationService : IOrganizationService EventSystemUser systemUser) { await RepositoryRevokeUserAsync(organizationUser); - await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, systemUser); + await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Revoked, + systemUser); if (organizationUser.UserId.HasValue) { @@ -1616,7 +1634,8 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Already revoked."); } - if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, new[] { organizationUser.Id }, includeProvider: true)) + if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationUser.OrganizationId, + new[] { organizationUser.Id }, includeProvider: true)) { throw new BadRequestException("Organization must have at least one confirmed owner."); } @@ -1664,7 +1683,8 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("You cannot revoke yourself."); } - if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue && !deletingUserIsOwner) + if (organizationUser.Type == OrganizationUserType.Owner && revokingUserId.HasValue && + !deletingUserIsOwner) { throw new BadRequestException("Only owners can revoke other owners."); } @@ -1705,27 +1725,4 @@ public class OrganizationService : IOrganizationService return status; } - - public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted) - { - organization.Id = CoreHelpers.GenerateComb(); - organization.Enabled = false; - organization.Status = OrganizationStatusType.Pending; - - await SignUpAsync(organization, default, null, null, true); - - var ownerOrganizationUser = new OrganizationUser - { - OrganizationId = organization.Id, - UserId = null, - Email = ownerEmail, - Key = null, - Type = OrganizationUserType.Owner, - Status = OrganizationUserStatusType.Invited, - }; - await _organizationUserRepository.CreateAsync(ownerOrganizationUser); - - await SendInviteAsync(ownerOrganizationUser, organization, true); - await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited); - } } diff --git a/src/Core/AdminConsole/Services/OrganizationFactory.cs b/src/Core/AdminConsole/Services/OrganizationFactory.cs new file mode 100644 index 0000000000..3261d89253 --- /dev/null +++ b/src/Core/AdminConsole/Services/OrganizationFactory.cs @@ -0,0 +1,114 @@ +using System.Security.Claims; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Licenses; +using Bit.Core.Billing.Licenses.Extensions; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Business; + +namespace Bit.Core.AdminConsole.Services; + +public static class OrganizationFactory +{ + public static Organization Create( + User owner, + ClaimsPrincipal claimsPrincipal, + string publicKey, + string privateKey) => new() + { + Name = claimsPrincipal.GetValue(OrganizationLicenseConstants.Name), + BillingEmail = claimsPrincipal.GetValue(OrganizationLicenseConstants.BillingEmail), + BusinessName = claimsPrincipal.GetValue(OrganizationLicenseConstants.BusinessName), + PlanType = claimsPrincipal.GetValue(OrganizationLicenseConstants.PlanType), + Seats = claimsPrincipal.GetValue(OrganizationLicenseConstants.Seats), + MaxCollections = claimsPrincipal.GetValue(OrganizationLicenseConstants.MaxCollections), + MaxStorageGb = 10240, + UsePolicies = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePolicies), + UseSso = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSso), + UseKeyConnector = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseKeyConnector), + UseScim = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseScim), + UseGroups = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseGroups), + UseDirectory = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseDirectory), + UseEvents = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseEvents), + UseTotp = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseTotp), + Use2fa = claimsPrincipal.GetValue(OrganizationLicenseConstants.Use2fa), + UseApi = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseApi), + UseResetPassword = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseResetPassword), + Plan = claimsPrincipal.GetValue(OrganizationLicenseConstants.Plan), + SelfHost = claimsPrincipal.GetValue(OrganizationLicenseConstants.SelfHost), + UsersGetPremium = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsersGetPremium), + UseCustomPermissions = + claimsPrincipal.GetValue(OrganizationLicenseConstants.UseCustomPermissions), + Gateway = null, + GatewayCustomerId = null, + GatewaySubscriptionId = null, + ReferenceData = owner.ReferenceData, + Enabled = claimsPrincipal.GetValue(OrganizationLicenseConstants.Enabled), + ExpirationDate = claimsPrincipal.GetValue(OrganizationLicenseConstants.Expires), + LicenseKey = claimsPrincipal.GetValue(OrganizationLicenseConstants.LicenseKey), + PublicKey = publicKey, + PrivateKey = privateKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + Status = OrganizationStatusType.Created, + UsePasswordManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UsePasswordManager), + UseSecretsManager = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseSecretsManager), + SmSeats = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmSeats), + SmServiceAccounts = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmServiceAccounts), + UseRiskInsights = claimsPrincipal.GetValue(OrganizationLicenseConstants.UseRiskInsights), + UseOrganizationDomains = + claimsPrincipal.GetValue(OrganizationLicenseConstants.UseOrganizationDomains), + UseAdminSponsoredFamilies = + claimsPrincipal.GetValue(OrganizationLicenseConstants.UseAdminSponsoredFamilies), + }; + + public static Organization Create( + User owner, + OrganizationLicense license, + string publicKey, + string privateKey) => new() + { + Name = license.Name, + BillingEmail = license.BillingEmail, + BusinessName = license.BusinessName, + PlanType = license.PlanType, + Seats = license.Seats, + MaxCollections = license.MaxCollections, + MaxStorageGb = 10240, + UsePolicies = license.UsePolicies, + UseSso = license.UseSso, + UseKeyConnector = license.UseKeyConnector, + UseScim = license.UseScim, + UseGroups = license.UseGroups, + UseDirectory = license.UseDirectory, + UseEvents = license.UseEvents, + UseTotp = license.UseTotp, + Use2fa = license.Use2fa, + UseApi = license.UseApi, + UseResetPassword = license.UseResetPassword, + Plan = license.Plan, + SelfHost = license.SelfHost, + UsersGetPremium = license.UsersGetPremium, + UseCustomPermissions = license.UseCustomPermissions, + Gateway = null, + GatewayCustomerId = null, + GatewaySubscriptionId = null, + ReferenceData = owner.ReferenceData, + Enabled = license.Enabled, + ExpirationDate = license.Expires, + LicenseKey = license.LicenseKey, + PublicKey = publicKey, + PrivateKey = privateKey, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + Status = OrganizationStatusType.Created, + UsePasswordManager = license.UsePasswordManager, + UseSecretsManager = license.UseSecretsManager, + SmSeats = license.SmSeats, + SmServiceAccounts = license.SmServiceAccounts, + UseRiskInsights = license.UseRiskInsights, + UseOrganizationDomains = license.UseOrganizationDomains, + UseAdminSponsoredFamilies = license.UseAdminSponsoredFamilies, + }; +} diff --git a/src/Core/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLogger.cs b/src/Core/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLogger.cs new file mode 100644 index 0000000000..6a0e581522 --- /dev/null +++ b/src/Core/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLogger.cs @@ -0,0 +1,67 @@ +using System.Text.Json; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Microsoft.Extensions.Logging; +using Quartz.Util; + +namespace Bit.Core.AdminConsole.Utilities.DebuggingInstruments; + +/// +/// Temporary code: Log warning when OrganizationUser is in an invalid state, +/// so we can identify which flow is causing the issue through Datadog. +/// +public static class UserInviteDebuggingLogger +{ + public static void LogUserInviteStateDiagnostics(this ILogger logger, OrganizationUser orgUser) + { + LogUserInviteStateDiagnostics(logger, [orgUser]); + } + + public static void LogUserInviteStateDiagnostics(this ILogger logger, IEnumerable allOrgUsers) + { + try + { + var invalidInviteState = allOrgUsers.Any(user => user.Status == OrganizationUserStatusType.Invited && user.Email.IsNullOrWhiteSpace()); + + if (invalidInviteState) + { + var logData = MapObjectDataToLog(allOrgUsers); + logger.LogWarning("Warning invalid invited state. {logData}", logData); + } + + var invalidConfirmedOrAcceptedState = allOrgUsers.Any(user => (user.Status == OrganizationUserStatusType.Confirmed || user.Status == OrganizationUserStatusType.Accepted) && !user.Email.IsNullOrWhiteSpace()); + + if (invalidConfirmedOrAcceptedState) + { + var logData = MapObjectDataToLog(allOrgUsers); + logger.LogWarning("Warning invalid confirmed or accepted state. {logData}", logData); + } + } + catch (Exception exception) + { + + // Ensure that this debugging instrument does not interfere with the current flow. + logger.LogWarning(exception, "Unexpected exception from UserInviteDebuggingLogger"); + } + } + + private static string MapObjectDataToLog(IEnumerable allOrgUsers) + { + var log = allOrgUsers.Select(allOrgUser => new + { + allOrgUser.OrganizationId, + allOrgUser.Status, + hasEmail = !allOrgUser.Email.IsNullOrWhiteSpace(), + userId = allOrgUser.UserId, + allOrgUserId = allOrgUser.Id + }); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + return JsonSerializer.Serialize(log, options); + } +} diff --git a/src/Core/Auth/Models/Data/PendingAuthRequestDetails.cs b/src/Core/Auth/Models/Data/PendingAuthRequestDetails.cs new file mode 100644 index 0000000000..0755e941b7 --- /dev/null +++ b/src/Core/Auth/Models/Data/PendingAuthRequestDetails.cs @@ -0,0 +1,83 @@ + +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Enums; + +namespace Bit.Core.Auth.Models.Data; + +public class PendingAuthRequestDetails : AuthRequest +{ + public Guid? RequestDeviceId { get; set; } + + /** + * Constructor for EF response. + */ + public PendingAuthRequestDetails( + AuthRequest authRequest, + Guid? deviceId) + { + ArgumentNullException.ThrowIfNull(authRequest); + + Id = authRequest.Id; + UserId = authRequest.UserId; + OrganizationId = authRequest.OrganizationId; + Type = authRequest.Type; + RequestDeviceIdentifier = authRequest.RequestDeviceIdentifier; + RequestDeviceType = authRequest.RequestDeviceType; + RequestIpAddress = authRequest.RequestIpAddress; + RequestCountryName = authRequest.RequestCountryName; + ResponseDeviceId = authRequest.ResponseDeviceId; + AccessCode = authRequest.AccessCode; + PublicKey = authRequest.PublicKey; + Key = authRequest.Key; + MasterPasswordHash = authRequest.MasterPasswordHash; + Approved = authRequest.Approved; + CreationDate = authRequest.CreationDate; + ResponseDate = authRequest.ResponseDate; + AuthenticationDate = authRequest.AuthenticationDate; + RequestDeviceId = deviceId; + } + + /** + * Constructor for dapper response. + */ + public PendingAuthRequestDetails( + Guid id, + Guid userId, + Guid organizationId, + short type, + string requestDeviceIdentifier, + short requestDeviceType, + string requestIpAddress, + string requestCountryName, + Guid? responseDeviceId, + string accessCode, + string publicKey, + string key, + string masterPasswordHash, + bool? approved, + DateTime creationDate, + DateTime? responseDate, + DateTime? authenticationDate, + Guid deviceId) + { + Id = id; + UserId = userId; + OrganizationId = organizationId; + Type = (AuthRequestType)type; + RequestDeviceIdentifier = requestDeviceIdentifier; + RequestDeviceType = (DeviceType)requestDeviceType; + RequestIpAddress = requestIpAddress; + RequestCountryName = requestCountryName; + ResponseDeviceId = responseDeviceId; + AccessCode = accessCode; + PublicKey = publicKey; + Key = key; + MasterPasswordHash = masterPasswordHash; + Approved = approved; + CreationDate = creationDate; + ResponseDate = responseDate; + AuthenticationDate = authenticationDate; + RequestDeviceId = deviceId; + } +} diff --git a/src/Core/Auth/Repositories/IAuthRequestRepository.cs b/src/Core/Auth/Repositories/IAuthRequestRepository.cs index 3b01a452f9..7a66ad6e34 100644 --- a/src/Core/Auth/Repositories/IAuthRequestRepository.cs +++ b/src/Core/Auth/Repositories/IAuthRequestRepository.cs @@ -9,6 +9,13 @@ public interface IAuthRequestRepository : IRepository { Task DeleteExpiredAsync(TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration); Task> GetManyByUserIdAsync(Guid userId); + /// + /// Gets all active pending auth requests for a user. Each auth request in the collection will be associated with a different + /// device. It will be the most current request for the device. + /// + /// UserId of the owner of the AuthRequests + /// a collection Auth request details or empty + Task> GetManyPendingAuthRequestByUserId(Guid userId); Task> GetManyPendingByOrganizationIdAsync(Guid organizationId); Task> GetManyAdminApprovalRequestsByManyIdsAsync(Guid organizationId, IEnumerable ids); Task UpdateManyAsync(IEnumerable authRequests); diff --git a/src/Core/Auth/Services/IAuthRequestService.cs b/src/Core/Auth/Services/IAuthRequestService.cs index 4e057f0ccf..d81f6e7c8c 100644 --- a/src/Core/Auth/Services/IAuthRequestService.cs +++ b/src/Core/Auth/Services/IAuthRequestService.cs @@ -1,5 +1,9 @@ using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Exceptions; using Bit.Core.Auth.Models.Api.Request.AuthRequest; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Settings; #nullable enable @@ -7,8 +11,41 @@ namespace Bit.Core.Auth.Services; public interface IAuthRequestService { - Task GetAuthRequestAsync(Guid id, Guid userId); - Task GetValidatedAuthRequestAsync(Guid id, string code); + /// + /// Fetches an authRequest by Id. Returns AuthRequest if AuthRequest.UserId mateches + /// userId. Returns null if the user doesn't match or if the AuthRequest is not found. + /// + /// Authrequest Id being fetched + /// user who owns AuthRequest + /// An AuthRequest or null + Task GetAuthRequestAsync(Guid authRequestId, Guid userId); + /// + /// Fetches the authrequest from the database with the id provided. Then checks + /// the accessCode against the AuthRequest.AccessCode from the database. accessCodes + /// must match the found authRequest, and the AuthRequest must not be expired. Expiration + /// is configured in + /// + /// AuthRequest being acted on + /// Access code of the authrequest, must match saved database value + /// A valid AuthRequest or null + Task GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode); + /// + /// Validates and Creates an in the database, as well as pushes it through notifications services + /// + /// + /// This method can only be called inside of an HTTP call because of it's reliance on + /// Task CreateAuthRequestAsync(AuthRequestCreateRequestModel model); + /// + /// Updates the AuthRequest per the AuthRequestUpdateRequestModel context. This approves + /// or rejects the login request. + /// + /// AuthRequest being acted on. + /// User acting on AuthRequest + /// Update context for the AuthRequest + /// retuns an AuthRequest or throws an exception + /// Thows if the AuthRequest has already been Approved/Rejected + /// Throws if the AuthRequest as expired or the userId doesn't match + /// Throws if the device isn't associated with the UserId Task UpdateAuthRequestAsync(Guid authRequestId, Guid userId, AuthRequestUpdateRequestModel model); } diff --git a/src/Core/Auth/Services/Implementations/AuthRequestService.cs b/src/Core/Auth/Services/Implementations/AuthRequestService.cs index 0fd1846d00..11682b524f 100644 --- a/src/Core/Auth/Services/Implementations/AuthRequestService.cs +++ b/src/Core/Auth/Services/Implementations/AuthRequestService.cs @@ -58,9 +58,9 @@ public class AuthRequestService : IAuthRequestService _logger = logger; } - public async Task GetAuthRequestAsync(Guid id, Guid userId) + public async Task GetAuthRequestAsync(Guid authRequestId, Guid userId) { - var authRequest = await _authRequestRepository.GetByIdAsync(id); + var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId); if (authRequest == null || authRequest.UserId != userId) { return null; @@ -69,10 +69,10 @@ public class AuthRequestService : IAuthRequestService return authRequest; } - public async Task GetValidatedAuthRequestAsync(Guid id, string code) + public async Task GetValidatedAuthRequestAsync(Guid authRequestId, string accessCode) { - var authRequest = await _authRequestRepository.GetByIdAsync(id); - if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, code)) + var authRequest = await _authRequestRepository.GetByIdAsync(authRequestId); + if (authRequest == null || !CoreHelpers.FixedTimeEquals(authRequest.AccessCode, accessCode)) { return null; } @@ -85,12 +85,6 @@ public class AuthRequestService : IAuthRequestService return authRequest; } - /// - /// Validates and Creates an in the database, as well as pushes it through notifications services - /// - /// - /// This method can only be called inside of an HTTP call because of it's reliance on - /// public async Task CreateAuthRequestAsync(AuthRequestCreateRequestModel model) { if (!_currentContext.DeviceType.HasValue) diff --git a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs index 7731e04af2..53bd8bdba2 100644 --- a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs +++ b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs @@ -38,7 +38,6 @@ public static class UserServiceCollectionExtensions public static void AddUserKeyCommands(this IServiceCollection services, IGlobalSettings globalSettings) { - services.AddScoped(); services.AddScoped(); } diff --git a/src/Core/Billing/Models/Sales/OrganizationSale.cs b/src/Core/Billing/Models/Sales/OrganizationSale.cs index 78ad26871b..c8ccb0f9a1 100644 --- a/src/Core/Billing/Models/Sales/OrganizationSale.cs +++ b/src/Core/Billing/Models/Sales/OrganizationSale.cs @@ -34,6 +34,7 @@ public class OrganizationSale var subscriptionSetup = GetSubscriptionSetup(signup); subscriptionSetup.SkipTrial = signup.SkipTrial; + subscriptionSetup.InitiationPath = signup.InitiationPath; return new OrganizationSale { diff --git a/src/Core/Billing/Models/Sales/SubscriptionSetup.cs b/src/Core/Billing/Models/Sales/SubscriptionSetup.cs index 871a2920b1..5e891f75b6 100644 --- a/src/Core/Billing/Models/Sales/SubscriptionSetup.cs +++ b/src/Core/Billing/Models/Sales/SubscriptionSetup.cs @@ -10,6 +10,7 @@ public class SubscriptionSetup public required PasswordManager PasswordManagerOptions { get; set; } public SecretsManager? SecretsManagerOptions { get; set; } public bool SkipTrial = false; + public string? InitiationPath { get; set; } public class PasswordManager { diff --git a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs index 32521f00c8..725e274fa2 100644 --- a/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs +++ b/src/Core/Billing/Services/Implementations/OrganizationBillingService.cs @@ -420,12 +420,29 @@ public class OrganizationBillingService( Items = subscriptionItemOptionsList, Metadata = new Dictionary { - ["organizationId"] = organizationId.ToString() + ["organizationId"] = organizationId.ToString(), + ["trialInitiationPath"] = !string.IsNullOrEmpty(subscriptionSetup.InitiationPath) && + subscriptionSetup.InitiationPath.Contains("trial from marketing website") + ? "marketing-initiated" + : "product-initiated" }, OffSession = true, TrialPeriodDays = subscriptionSetup.SkipTrial ? 0 : plan.TrialPeriodDays }; + // Only set trial_settings.end_behavior.missing_payment_method to "cancel" if there is no payment method + if (string.IsNullOrEmpty(customer.InvoiceSettings?.DefaultPaymentMethodId) && + !customer.Metadata.ContainsKey(BraintreeCustomerIdKey)) + { + subscriptionCreateOptions.TrialSettings = new SubscriptionTrialSettingsOptions + { + EndBehavior = new SubscriptionTrialSettingsEndBehaviorOptions + { + MissingPaymentMethod = "cancel" + } + }; + } + var setNonUSBusinessUseToReverseCharge = featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 49d360ec50..b2e28dab47 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -107,11 +107,10 @@ public static class FeatureFlagKeys public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint"; public const string LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission"; public const string PolicyRequirements = "pm-14439-policy-requirements"; - public const string SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility"; public const string ScimInviteUserOptimization = "pm-16811-optimize-invite-user-flow-to-fail-fast"; public const string EventBasedOrganizationIntegrations = "event-based-organization-integrations"; - public const string OptimizeNestedTraverseTypescript = "pm-21695-optimize-nested-traverse-typescript"; public const string SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions"; + public const string CreateDefaultLocation = "pm-19467-create-default-location"; /* Auth Team */ public const string PM9112DeviceApprovalPersistence = "pm-9112-device-approval-persistence"; @@ -138,6 +137,7 @@ public static class FeatureFlagKeys public const string EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill"; public const string MacOsNativeCredentialSync = "macos-native-credential-sync"; public const string InlineMenuTotp = "inline-menu-totp"; + public const string WindowsDesktopAutotype = "windows-desktop-autotype"; /* Billing Team */ public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; @@ -181,6 +181,7 @@ public static class FeatureFlagKeys public const string EnablePMFlightRecorder = "enable-pm-flight-recorder"; public const string MobileErrorReporting = "mobile-error-reporting"; public const string AndroidChromeAutofill = "android-chrome-autofill"; + public const string UserManagedPrivilegedApps = "pm-18970-user-managed-privileged-apps"; public const string EnablePMPreloginSettings = "enable-pm-prelogin-settings"; public const string AppIntents = "app-intents"; @@ -192,7 +193,6 @@ public static class FeatureFlagKeys public const string IpcChannelFramework = "ipc-channel-framework"; /* Tools Team */ - public const string ItemShare = "item-share"; public const string DesktopSendUIRefresh = "desktop-send-ui-refresh"; /* Vault Team */ @@ -205,6 +205,8 @@ public static class FeatureFlagKeys public const string EndUserNotifications = "pm-10609-end-user-notifications"; public const string PhishingDetection = "phishing-detection"; public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy"; + public const string PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view"; + public const string PM19315EndUserActivationMvp = "pm-19315-end-user-activation-mvp"; public static List GetAllKeys() { diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 88ecaf8cef..a6be8f484e 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -21,8 +21,8 @@ - - + + @@ -59,7 +59,7 @@ - + diff --git a/src/Core/Dirt/Entities/OrganizationApplication.cs b/src/Core/Dirt/Entities/OrganizationApplication.cs new file mode 100644 index 0000000000..259dbd60dd --- /dev/null +++ b/src/Core/Dirt/Entities/OrganizationApplication.cs @@ -0,0 +1,20 @@ +#nullable enable + +using Bit.Core.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.Dirt.Entities; + +public class OrganizationApplication : ITableObject, IRevisable +{ + public Guid Id { get; set; } + public Guid OrganizationId { get; set; } + public string Applications { get; set; } = string.Empty; + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime RevisionDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } +} diff --git a/src/Core/Dirt/Entities/OrganizationReport.cs b/src/Core/Dirt/Entities/OrganizationReport.cs new file mode 100644 index 0000000000..0f327c5c8f --- /dev/null +++ b/src/Core/Dirt/Entities/OrganizationReport.cs @@ -0,0 +1,20 @@ +#nullable enable + +using Bit.Core.Entities; +using Bit.Core.Utilities; + +namespace Bit.Core.Dirt.Entities; + +public class OrganizationReport : ITableObject +{ + public Guid Id { get; set; } + public Guid OrganizationId { get; set; } + public DateTime Date { get; set; } + public string ReportData { get; set; } = string.Empty; + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + + public void SetNewId() + { + Id = CoreHelpers.GenerateComb(); + } +} diff --git a/src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs b/src/Core/Dirt/Entities/PasswordHealthReportApplication.cs similarity index 92% rename from src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs rename to src/Core/Dirt/Entities/PasswordHealthReportApplication.cs index db605d6b74..5a72a0aab0 100644 --- a/src/Core/Dirt/Reports/Entities/PasswordHealthReportApplication.cs +++ b/src/Core/Dirt/Entities/PasswordHealthReportApplication.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities; using Bit.Core.Utilities; -namespace Bit.Core.Dirt.Reports.Entities; +namespace Bit.Core.Dirt.Entities; public class PasswordHealthReportApplication : ITableObject, IRevisable { diff --git a/src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs b/src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs similarity index 96% rename from src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs rename to src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs index 759337d5cf..c1949ffb24 100644 --- a/src/Core/Dirt/Reports/Models/Data/MemberAccessCipherDetails.cs +++ b/src/Core/Dirt/Models/Data/MemberAccessCipherDetails.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Dirt.Reports.Models.Data; +namespace Bit.Core.Dirt.Models.Data; public class MemberAccessDetails { diff --git a/src/Core/Dirt/Models/Data/MemberAccessReportDetail.cs b/src/Core/Dirt/Models/Data/MemberAccessReportDetail.cs new file mode 100644 index 0000000000..a99b6e2088 --- /dev/null +++ b/src/Core/Dirt/Models/Data/MemberAccessReportDetail.cs @@ -0,0 +1,19 @@ +namespace Bit.Core.Dirt.Reports.Models.Data; + +public class MemberAccessReportDetail +{ + public Guid? UserGuid { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public bool TwoFactorEnabled { get; set; } + public bool AccountRecoveryEnabled { get; set; } + public bool UsesKeyConnector { get; set; } + public Guid? CollectionId { get; set; } + public Guid? GroupId { get; set; } + public string GroupName { get; set; } + public string CollectionName { get; set; } + public bool? ReadOnly { get; set; } + public bool? HidePasswords { get; set; } + public bool? Manage { get; set; } + public IEnumerable CipherIds { get; set; } +} diff --git a/src/Core/Dirt/Models/Data/OrganizationMemberBaseDetail.cs b/src/Core/Dirt/Models/Data/OrganizationMemberBaseDetail.cs new file mode 100644 index 0000000000..a1f0bd81fd --- /dev/null +++ b/src/Core/Dirt/Models/Data/OrganizationMemberBaseDetail.cs @@ -0,0 +1,19 @@ +namespace Bit.Core.Dirt.Reports.Models.Data; + +public class OrganizationMemberBaseDetail +{ + public Guid? UserGuid { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public string TwoFactorProviders { get; set; } + public bool UsesKeyConnector { get; set; } + public string ResetPasswordKey { get; set; } + public Guid? CollectionId { get; set; } + public Guid? GroupId { get; set; } + public string GroupName { get; set; } + public string CollectionName { get; set; } + public bool? ReadOnly { get; set; } + public bool? HidePasswords { get; set; } + public bool? Manage { get; set; } + public Guid CipherId { get; set; } +} diff --git a/src/Core/Dirt/Models/Data/RiskInsightsReportDetail.cs b/src/Core/Dirt/Models/Data/RiskInsightsReportDetail.cs new file mode 100644 index 0000000000..1ea805edf1 --- /dev/null +++ b/src/Core/Dirt/Models/Data/RiskInsightsReportDetail.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Dirt.Reports.Models.Data; + +public class RiskInsightsReportDetail +{ + public Guid? UserGuid { get; set; } + public string UserName { get; set; } + public string Email { get; set; } + public bool UsesKeyConnector { get; set; } + public IEnumerable CipherIds { get; set; } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/AddOrganizationReportCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/AddOrganizationReportCommand.cs new file mode 100644 index 0000000000..66d25cdf56 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/AddOrganizationReportCommand.cs @@ -0,0 +1,74 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class AddOrganizationReportCommand : IAddOrganizationReportCommand +{ + private readonly IOrganizationRepository _organizationRepo; + private readonly IOrganizationReportRepository _organizationReportRepo; + private ILogger _logger; + + public AddOrganizationReportCommand( + IOrganizationRepository organizationRepository, + IOrganizationReportRepository organizationReportRepository, + ILogger logger) + { + _organizationRepo = organizationRepository; + _organizationReportRepo = organizationReportRepository; + _logger = logger; + } + + public async Task AddOrganizationReportAsync(AddOrganizationReportRequest request) + { + _logger.LogInformation("Adding organization report for organization {organizationId}", request.OrganizationId); + + var (isValid, errorMessage) = await ValidateRequestAsync(request); + if (!isValid) + { + _logger.LogInformation("Failed to add organization {organizationId} report: {errorMessage}", request.OrganizationId, errorMessage); + throw new BadRequestException(errorMessage); + } + + var organizationReport = new OrganizationReport + { + OrganizationId = request.OrganizationId, + ReportData = request.ReportData, + Date = request.Date == default ? DateTime.UtcNow : request.Date, + CreationDate = DateTime.UtcNow, + }; + + organizationReport.SetNewId(); + + var data = await _organizationReportRepo.CreateAsync(organizationReport); + + _logger.LogInformation("Successfully added organization report for organization {organizationId}, {organizationReportId}", + request.OrganizationId, data.Id); + + return data; + } + + private async Task<(bool IsValid, string errorMessage)> ValidateRequestAsync( + AddOrganizationReportRequest request) + { + // verify that the organization exists + var organization = await _organizationRepo.GetByIdAsync(request.OrganizationId); + if (organization == null) + { + return (false, "Invalid Organization"); + } + + // ensure that we have report data + if (string.IsNullOrWhiteSpace(request.ReportData)) + { + return (false, "Report Data is required"); + } + + return (true, string.Empty); + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs index f8232ffa92..159cbb5c77 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/AddPasswordHealthReportApplicationCommand.cs @@ -1,7 +1,7 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Core.Repositories; diff --git a/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs new file mode 100644 index 0000000000..8fe206c1f1 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/DropOrganizationReportCommand.cs @@ -0,0 +1,45 @@ +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class DropOrganizationReportCommand : IDropOrganizationReportCommand +{ + private IOrganizationReportRepository _organizationReportRepo; + private ILogger _logger; + + public DropOrganizationReportCommand( + IOrganizationReportRepository organizationReportRepository, + ILogger logger) + { + _organizationReportRepo = organizationReportRepository; + _logger = logger; + } + + public async Task DropOrganizationReportAsync(DropOrganizationReportRequest request) + { + _logger.LogInformation("Dropping organization report for organization {organizationId}", + request.OrganizationId); + + var data = await _organizationReportRepo.GetByOrganizationIdAsync(request.OrganizationId); + if (data == null || data.Count() == 0) + { + _logger.LogInformation("No organization reports found for organization {organizationId}", request.OrganizationId); + throw new BadRequestException("No data found."); + } + + data + .Where(_ => request.OrganizationReportIds.Contains(_.Id)) + .ToList() + .ForEach(async reportId => + { + _logger.LogInformation("Dropping organization report {organizationReportId} for organization {organizationId}", + reportId, request.OrganizationId); + + await _organizationReportRepo.DeleteAsync(reportId); + }); + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs index 55914dca37..b955c2c958 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/DropPasswordHealthReportApplicationCommand.cs @@ -1,6 +1,6 @@ using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; namespace Bit.Core.Dirt.Reports.ReportFeatures; diff --git a/src/Core/Dirt/Reports/ReportFeatures/GetOrganizationReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/GetOrganizationReportQuery.cs new file mode 100644 index 0000000000..e536fdfddc --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/GetOrganizationReportQuery.cs @@ -0,0 +1,43 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class GetOrganizationReportQuery : IGetOrganizationReportQuery +{ + private IOrganizationReportRepository _organizationReportRepo; + private ILogger _logger; + + public GetOrganizationReportQuery( + IOrganizationReportRepository organizationReportRepo, + ILogger logger) + { + _organizationReportRepo = organizationReportRepo; + _logger = logger; + } + + public async Task> GetOrganizationReportAsync(Guid organizationId) + { + if (organizationId == Guid.Empty) + { + throw new BadRequestException("OrganizationId is required."); + } + + _logger.LogInformation("Fetching organization reports for organization {organizationId}", organizationId); + return await _organizationReportRepo.GetByOrganizationIdAsync(organizationId); + } + + public async Task GetLatestOrganizationReportAsync(Guid organizationId) + { + if (organizationId == Guid.Empty) + { + throw new BadRequestException("OrganizationId is required."); + } + + _logger.LogInformation("Fetching latest organization report for organization {organizationId}", organizationId); + return await _organizationReportRepo.GetLatestByOrganizationIdAsync(organizationId); + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs index d9b5e79a0c..40d86338db 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/GetPasswordHealthReportApplicationQuery.cs @@ -1,6 +1,6 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; namespace Bit.Core.Dirt.Reports.ReportFeatures; diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddOrganizationReportCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddOrganizationReportCommand.cs new file mode 100644 index 0000000000..3677b9794b --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddOrganizationReportCommand.cs @@ -0,0 +1,10 @@ + +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; + +public interface IAddOrganizationReportCommand +{ + Task AddOrganizationReportAsync(AddOrganizationReportRequest request); +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs index 0a4aa29f2f..0fd21751de 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IAddPasswordHealthReportApplicationCommand.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IDropOrganizationReportCommand.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IDropOrganizationReportCommand.cs new file mode 100644 index 0000000000..1ed9059f56 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IDropOrganizationReportCommand.cs @@ -0,0 +1,9 @@ + +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; + +public interface IDropOrganizationReportCommand +{ + Task DropOrganizationReportAsync(DropOrganizationReportRequest request); +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetOrganizationReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetOrganizationReportQuery.cs new file mode 100644 index 0000000000..f596e8f517 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetOrganizationReportQuery.cs @@ -0,0 +1,9 @@ +using Bit.Core.Dirt.Entities; + +namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; + +public interface IGetOrganizationReportQuery +{ + Task> GetOrganizationReportAsync(Guid organizationId); + Task GetLatestOrganizationReportAsync(Guid organizationId); +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs index ae2f759756..f7fe80b098 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/Interfaces/IGetPasswordHealthReportApplicationQuery.cs @@ -1,4 +1,4 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; namespace Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; diff --git a/src/Core/Dirt/Reports/ReportFeatures/MemberAccessCipherDetailsQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/MemberAccessCipherDetailsQuery.cs deleted file mode 100644 index 4a8039e6bc..0000000000 --- a/src/Core/Dirt/Reports/ReportFeatures/MemberAccessCipherDetailsQuery.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System.Collections.Concurrent; -using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; -using Bit.Core.Dirt.Reports.Models.Data; -using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; -using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Entities; -using Bit.Core.Models.Data; -using Bit.Core.Models.Data.Organizations; -using Bit.Core.Models.Data.Organizations.OrganizationUsers; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Bit.Core.Vault.Models.Data; -using Bit.Core.Vault.Queries; -using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; -using Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests; - -namespace Bit.Core.Dirt.Reports.ReportFeatures; - -public class MemberAccessCipherDetailsQuery : IMemberAccessCipherDetailsQuery -{ - private readonly IOrganizationUserUserDetailsQuery _organizationUserUserDetailsQuery; - private readonly IGroupRepository _groupRepository; - private readonly ICollectionRepository _collectionRepository; - private readonly IOrganizationCiphersQuery _organizationCiphersQuery; - private readonly IApplicationCacheService _applicationCacheService; - private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; - - public MemberAccessCipherDetailsQuery( - IOrganizationUserUserDetailsQuery organizationUserUserDetailsQuery, - IGroupRepository groupRepository, - ICollectionRepository collectionRepository, - IOrganizationCiphersQuery organizationCiphersQuery, - IApplicationCacheService applicationCacheService, - ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery - ) - { - _organizationUserUserDetailsQuery = organizationUserUserDetailsQuery; - _groupRepository = groupRepository; - _collectionRepository = collectionRepository; - _organizationCiphersQuery = organizationCiphersQuery; - _applicationCacheService = applicationCacheService; - _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; - } - - public async Task> GetMemberAccessCipherDetails(MemberAccessCipherDetailsRequest request) - { - var orgUsers = await _organizationUserUserDetailsQuery.GetOrganizationUserUserDetails( - new OrganizationUserUserDetailsQueryRequest - { - OrganizationId = request.OrganizationId, - IncludeCollections = true, - IncludeGroups = true - }); - - var orgGroups = await _groupRepository.GetManyByOrganizationIdAsync(request.OrganizationId); - var orgAbility = await _applicationCacheService.GetOrganizationAbilityAsync(request.OrganizationId); - var orgCollectionsWithAccess = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(request.OrganizationId); - var orgItems = await _organizationCiphersQuery.GetAllOrganizationCiphers(request.OrganizationId); - var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers); - - var memberAccessCipherDetails = GenerateAccessDataParallel( - orgGroups, - orgCollectionsWithAccess, - orgItems, - organizationUsersTwoFactorEnabled, - orgAbility); - - return memberAccessCipherDetails; - } - - /// - /// Generates a report for all members of an organization. Containing summary information - /// such as item, collection, and group counts. Including the cipherIds a member is assigned. - /// Child collection includes detailed information on the user and group collections along - /// with their permissions. - /// - /// Organization groups collection - /// Collections for the organization and the groups/users and permissions - /// Cipher items for the organization with the collections associated with them - /// Organization users and two factor status - /// Organization ability for account recovery status - /// List of the MemberAccessCipherDetailsModel; - private IEnumerable GenerateAccessDataParallel( - ICollection orgGroups, - ICollection> orgCollectionsWithAccess, - IEnumerable orgItems, - IEnumerable<(OrganizationUserUserDetails user, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled, - OrganizationAbility orgAbility) - { - var orgUsers = organizationUsersTwoFactorEnabled.Select(x => x.user).ToList(); - var groupNameDictionary = orgGroups.ToDictionary(x => x.Id, x => x.Name); - var collectionItems = orgItems - .SelectMany(x => x.CollectionIds, - (cipher, collectionId) => new { Cipher = cipher, CollectionId = collectionId }) - .GroupBy(y => y.CollectionId, - (key, ciphers) => new { CollectionId = key, Ciphers = ciphers }); - var itemLookup = collectionItems.ToDictionary(x => x.CollectionId.ToString(), x => x.Ciphers.Select(c => c.Cipher.Id.ToString()).ToList()); - - var memberAccessCipherDetails = new ConcurrentBag(); - - Parallel.ForEach(orgUsers, user => - { - var groupAccessDetails = new List(); - var userCollectionAccessDetails = new List(); - - foreach (var tCollect in orgCollectionsWithAccess) - { - if (itemLookup.TryGetValue(tCollect.Item1.Id.ToString(), out var items)) - { - var itemCounts = items.Count; - - if (tCollect.Item2.Groups.Any()) - { - var groupDetails = tCollect.Item2.Groups - .Where(tCollectGroups => user.Groups.Contains(tCollectGroups.Id)) - .Select(x => new MemberAccessDetails - { - CollectionId = tCollect.Item1.Id, - CollectionName = tCollect.Item1.Name, - GroupId = x.Id, - GroupName = groupNameDictionary[x.Id], - ReadOnly = x.ReadOnly, - HidePasswords = x.HidePasswords, - Manage = x.Manage, - ItemCount = itemCounts, - CollectionCipherIds = items - }); - - groupAccessDetails.AddRange(groupDetails); - } - - if (tCollect.Item2.Users.Any()) - { - var userCollectionDetails = tCollect.Item2.Users - .Where(tCollectUser => tCollectUser.Id == user.Id) - .Select(x => new MemberAccessDetails - { - CollectionId = tCollect.Item1.Id, - CollectionName = tCollect.Item1.Name, - ReadOnly = x.ReadOnly, - HidePasswords = x.HidePasswords, - Manage = x.Manage, - ItemCount = itemCounts, - CollectionCipherIds = items - }); - - userCollectionAccessDetails.AddRange(userCollectionDetails); - } - } - } - - var report = new MemberAccessCipherDetails - { - UserName = user.Name, - Email = user.Email, - TwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == user.Id).twoFactorIsEnabled, - AccountRecoveryEnabled = !string.IsNullOrEmpty(user.ResetPasswordKey) && orgAbility.UseResetPassword, - UserGuid = user.Id, - UsesKeyConnector = user.UsesKeyConnector - }; - - var userAccessDetails = new List(); - if (user.Groups.Any()) - { - var userGroups = groupAccessDetails.Where(x => user.Groups.Contains(x.GroupId.GetValueOrDefault())); - userAccessDetails.AddRange(userGroups); - } - - var groupsWithoutCollections = user.Groups.Where(x => !userAccessDetails.Any(y => x == y.GroupId)); - if (groupsWithoutCollections.Any()) - { - var emptyGroups = groupsWithoutCollections.Select(x => new MemberAccessDetails - { - GroupId = x, - GroupName = groupNameDictionary[x], - ItemCount = 0 - }); - userAccessDetails.AddRange(emptyGroups); - } - - if (user.Collections.Any()) - { - var userCollections = userCollectionAccessDetails.Where(x => user.Collections.Any(y => x.CollectionId == y.Id)); - userAccessDetails.AddRange(userCollections); - } - report.AccessDetails = userAccessDetails; - - var userCiphers = report.AccessDetails - .Where(x => x.ItemCount > 0) - .SelectMany(y => y.CollectionCipherIds) - .Distinct(); - report.CipherIds = userCiphers; - report.TotalItemCount = userCiphers.Count(); - - var distinctItems = report.AccessDetails.Where(x => x.CollectionId.HasValue).Select(x => x.CollectionId).Distinct(); - report.CollectionsCount = distinctItems.Count(); - report.GroupsCount = report.AccessDetails.Select(x => x.GroupId).Where(y => y.HasValue).Distinct().Count(); - - memberAccessCipherDetails.Add(report); - }); - - return memberAccessCipherDetails; - } -} diff --git a/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs new file mode 100644 index 0000000000..21dbfc77a4 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/MemberAccessReportQuery.cs @@ -0,0 +1,64 @@ +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class MemberAccessReportQuery( + IOrganizationMemberBaseDetailRepository organizationMemberBaseDetailRepository, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, + IApplicationCacheService applicationCacheService) : IMemberAccessReportQuery +{ + public async Task> GetMemberAccessReportsAsync( + MemberAccessReportRequest request) + { + var baseDetails = + await organizationMemberBaseDetailRepository.GetOrganizationMemberBaseDetailsByOrganizationId( + request.OrganizationId); + + var orgUsers = baseDetails.Select(x => x.UserGuid.GetValueOrDefault()).Distinct(); + var orgUsersTwoFactorEnabled = await twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers); + + var orgAbility = await applicationCacheService.GetOrganizationAbilityAsync(request.OrganizationId); + + var accessDetails = baseDetails + .GroupBy(b => new + { + b.UserGuid, + b.UserName, + b.Email, + b.TwoFactorProviders, + b.ResetPasswordKey, + b.UsesKeyConnector, + b.GroupId, + b.GroupName, + b.CollectionId, + b.CollectionName, + b.ReadOnly, + b.HidePasswords, + b.Manage + }) + .Select(g => new MemberAccessReportDetail + { + UserGuid = g.Key.UserGuid, + UserName = g.Key.UserName, + Email = g.Key.Email, + TwoFactorEnabled = orgUsersTwoFactorEnabled.FirstOrDefault(x => x.userId == g.Key.UserGuid).twoFactorIsEnabled, + AccountRecoveryEnabled = !string.IsNullOrWhiteSpace(g.Key.ResetPasswordKey) && orgAbility.UseResetPassword, + UsesKeyConnector = g.Key.UsesKeyConnector, + GroupId = g.Key.GroupId, + GroupName = g.Key.GroupName, + CollectionId = g.Key.CollectionId, + CollectionName = g.Key.CollectionName, + ReadOnly = g.Key.ReadOnly, + HidePasswords = g.Key.HidePasswords, + Manage = g.Key.Manage, + CipherIds = g.Select(c => c.CipherId) + }); + + return accessDetails; + } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessCipherDetailsQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessReportQuery.cs similarity index 52% rename from src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessCipherDetailsQuery.cs rename to src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessReportQuery.cs index 98ed780db3..44bb4f33c5 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessCipherDetailsQuery.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IMemberAccessReportQuery.cs @@ -3,7 +3,7 @@ using Bit.Core.Dirt.Reports.ReportFeatures.Requests; namespace Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; -public interface IMemberAccessCipherDetailsQuery +public interface IMemberAccessReportQuery { - Task> GetMemberAccessCipherDetails(MemberAccessCipherDetailsRequest request); + Task> GetMemberAccessReportsAsync(MemberAccessReportRequest request); } diff --git a/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IRiskInsightsReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IRiskInsightsReportQuery.cs new file mode 100644 index 0000000000..c6ba69dfff --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/OrganizationReportMembers/Interfaces/IRiskInsightsReportQuery.cs @@ -0,0 +1,9 @@ +using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +namespace Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; + +public interface IRiskInsightsReportQuery +{ + Task> GetRiskInsightsReportDetails(RiskInsightsReportRequest request); +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs index d847c8051e..a20c7a3e8f 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/ReportingServiceCollectionExtensions.cs @@ -8,9 +8,13 @@ public static class ReportingServiceCollectionExtensions { public static void AddReportingServices(this IServiceCollection services) { - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Core/Dirt/Reports/ReportFeatures/Requests/AddOrganizationReportRequest.cs b/src/Core/Dirt/Reports/ReportFeatures/Requests/AddOrganizationReportRequest.cs new file mode 100644 index 0000000000..ca892cddde --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Requests/AddOrganizationReportRequest.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +public class AddOrganizationReportRequest +{ + public Guid OrganizationId { get; set; } + public string ReportData { get; set; } + public DateTime Date { get; set; } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Requests/DropOrganizationReportRequest.cs b/src/Core/Dirt/Reports/ReportFeatures/Requests/DropOrganizationReportRequest.cs new file mode 100644 index 0000000000..cc889fe351 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Requests/DropOrganizationReportRequest.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +public class DropOrganizationReportRequest +{ + public Guid OrganizationId { get; set; } + public IEnumerable OrganizationReportIds { get; set; } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessCipherDetailsRequest.cs b/src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessReportRequest.cs similarity index 70% rename from src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessCipherDetailsRequest.cs rename to src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessReportRequest.cs index b40dfc6dec..5fe28810a6 100644 --- a/src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessCipherDetailsRequest.cs +++ b/src/Core/Dirt/Reports/ReportFeatures/Requests/MemberAccessReportRequest.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests; -public class MemberAccessCipherDetailsRequest +public class MemberAccessReportRequest { public Guid OrganizationId { get; set; } } diff --git a/src/Core/Dirt/Reports/ReportFeatures/Requests/RiskInsightsReportRequest.cs b/src/Core/Dirt/Reports/ReportFeatures/Requests/RiskInsightsReportRequest.cs new file mode 100644 index 0000000000..1b843ea002 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/Requests/RiskInsightsReportRequest.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests; + +public class RiskInsightsReportRequest +{ + public Guid OrganizationId { get; set; } +} diff --git a/src/Core/Dirt/Reports/ReportFeatures/RiskInsightsReportQuery.cs b/src/Core/Dirt/Reports/ReportFeatures/RiskInsightsReportQuery.cs new file mode 100644 index 0000000000..e686698c51 --- /dev/null +++ b/src/Core/Dirt/Reports/ReportFeatures/RiskInsightsReportQuery.cs @@ -0,0 +1,39 @@ +using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.Interfaces; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Reports.Repositories; + +namespace Bit.Core.Dirt.Reports.ReportFeatures; + +public class RiskInsightsReportQuery : IRiskInsightsReportQuery +{ + private readonly IOrganizationMemberBaseDetailRepository _organizationMemberBaseDetailRepository; + + public RiskInsightsReportQuery(IOrganizationMemberBaseDetailRepository repository) + { + _organizationMemberBaseDetailRepository = repository; + } + + public async Task> GetRiskInsightsReportDetails( + RiskInsightsReportRequest request) + { + var baseDetails = + await _organizationMemberBaseDetailRepository.GetOrganizationMemberBaseDetailsByOrganizationId( + request.OrganizationId); + + var insightsDetails = baseDetails + .GroupBy(b => new { b.UserGuid, b.UserName, b.Email, b.UsesKeyConnector }) + .Select(g => new RiskInsightsReportDetail + { + UserGuid = g.Key.UserGuid, + UserName = g.Key.UserName, + Email = g.Key.Email, + UsesKeyConnector = g.Key.UsesKeyConnector, + CipherIds = g + .Select(x => x.CipherId.ToString()) + .Distinct() + }); + + return insightsDetails; + } +} diff --git a/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs b/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs new file mode 100644 index 0000000000..f89e84e415 --- /dev/null +++ b/src/Core/Dirt/Repositories/IOrganizationApplicationRepository.cs @@ -0,0 +1,9 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Repositories; + +namespace Bit.Core.Dirt.Repositories; + +public interface IOrganizationApplicationRepository : IRepository +{ + Task> GetByOrganizationIdAsync(Guid organizationId); +} diff --git a/src/Core/Dirt/Repositories/IOrganizationMemberBaseDetailRepository.cs b/src/Core/Dirt/Repositories/IOrganizationMemberBaseDetailRepository.cs new file mode 100644 index 0000000000..e2a161aa9c --- /dev/null +++ b/src/Core/Dirt/Repositories/IOrganizationMemberBaseDetailRepository.cs @@ -0,0 +1,8 @@ +using Bit.Core.Dirt.Reports.Models.Data; + +namespace Bit.Core.Dirt.Reports.Repositories; + +public interface IOrganizationMemberBaseDetailRepository +{ + Task> GetOrganizationMemberBaseDetailsByOrganizationId(Guid organizationId); +} diff --git a/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs b/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs new file mode 100644 index 0000000000..e7979ca4b7 --- /dev/null +++ b/src/Core/Dirt/Repositories/IOrganizationReportRepository.cs @@ -0,0 +1,12 @@ +using Bit.Core.Dirt.Entities; +using Bit.Core.Repositories; + +namespace Bit.Core.Dirt.Repositories; + +public interface IOrganizationReportRepository : IRepository +{ + Task> GetByOrganizationIdAsync(Guid organizationId); + + Task GetLatestByOrganizationIdAsync(Guid organizationId); +} + diff --git a/src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs b/src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs similarity index 74% rename from src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs rename to src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs index 5b57932868..a67f696e44 100644 --- a/src/Core/Dirt/Reports/Repositories/IPasswordHealthReportApplicationRepository.cs +++ b/src/Core/Dirt/Repositories/IPasswordHealthReportApplicationRepository.cs @@ -1,7 +1,7 @@ -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Repositories; -namespace Bit.Core.Dirt.Reports.Repositories; +namespace Bit.Core.Dirt.Repositories; public interface IPasswordHealthReportApplicationRepository : IRepository { diff --git a/src/Core/KeyManagement/Models/Data/RotateUserKeyData.cs b/src/Core/KeyManagement/Models/Data/RotateUserKeyData.cs deleted file mode 100644 index 9813f760f3..0000000000 --- a/src/Core/KeyManagement/Models/Data/RotateUserKeyData.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Bit.Core.Auth.Entities; -using Bit.Core.Auth.Models.Data; -using Bit.Core.Entities; -using Bit.Core.Tools.Entities; -using Bit.Core.Vault.Entities; - -namespace Bit.Core.KeyManagement.Models.Data; - -public class RotateUserKeyData -{ - public string MasterPasswordHash { get; set; } - public string Key { get; set; } - public string PrivateKey { get; set; } - public IEnumerable Ciphers { get; set; } - public IEnumerable Folders { get; set; } - public IReadOnlyList Sends { get; set; } - public IEnumerable EmergencyAccesses { get; set; } - public IReadOnlyList OrganizationUsers { get; set; } - public IEnumerable WebAuthnKeys { get; set; } -} diff --git a/src/Core/KeyManagement/UserKey/IRotateUserAccountKeysCommand.cs b/src/Core/KeyManagement/UserKey/IRotateUserAccountKeysCommand.cs index ec40e7031d..1550f3c186 100644 --- a/src/Core/KeyManagement/UserKey/IRotateUserAccountKeysCommand.cs +++ b/src/Core/KeyManagement/UserKey/IRotateUserAccountKeysCommand.cs @@ -1,6 +1,7 @@ using Bit.Core.Entities; using Bit.Core.KeyManagement.Models.Data; using Microsoft.AspNetCore.Identity; +using Microsoft.Data.SqlClient; namespace Bit.Core.KeyManagement.UserKey; @@ -18,3 +19,12 @@ public interface IRotateUserAccountKeysCommand /// User KDF settings and email must match the model provided settings. Task RotateUserAccountKeysAsync(User user, RotateUserAccountKeysData model); } + +/// +/// A type used to implement updates to the database for key rotations. Each domain that requires an update of encrypted +/// data during a key rotation should use this to implement its own database call. The user repository loops through +/// these during a key rotation. +/// Note: connection and transaction are only used for Dapper. They won't be available in EF +/// +public delegate Task UpdateEncryptedDataForKeyRotation(SqlConnection connection = null, + SqlTransaction transaction = null); diff --git a/src/Core/KeyManagement/UserKey/IRotateUserKeyCommand.cs b/src/Core/KeyManagement/UserKey/IRotateUserKeyCommand.cs deleted file mode 100644 index 90dc90541f..0000000000 --- a/src/Core/KeyManagement/UserKey/IRotateUserKeyCommand.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Bit.Core.Entities; -using Bit.Core.KeyManagement.Models.Data; -using Microsoft.AspNetCore.Identity; -using Microsoft.Data.SqlClient; - -namespace Bit.Core.KeyManagement.UserKey; - -/// -/// Responsible for rotation of a user key and updating database with re-encrypted data -/// -public interface IRotateUserKeyCommand -{ - /// - /// Sets a new user key and updates all encrypted data. - /// - /// All necessary information for rotation. Warning: Any encrypted data not included will be lost. - /// An IdentityResult for verification of the master password hash - /// User must be provided. - Task RotateUserKeyAsync(User user, RotateUserKeyData model); -} - -/// -/// A type used to implement updates to the database for key rotations. Each domain that requires an update of encrypted -/// data during a key rotation should use this to implement its own database call. The user repository loops through -/// these during a key rotation. -/// Note: connection and transaction are only used for Dapper. They won't be available in EF -/// -public delegate Task UpdateEncryptedDataForKeyRotation(SqlConnection connection = null, - SqlTransaction transaction = null); diff --git a/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs b/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs deleted file mode 100644 index 8cece5f762..0000000000 --- a/src/Core/KeyManagement/UserKey/Implementations/RotateUserKeyCommand.cs +++ /dev/null @@ -1,121 +0,0 @@ -using Bit.Core.Auth.Repositories; -using Bit.Core.Entities; -using Bit.Core.KeyManagement.Models.Data; -using Bit.Core.Platform.Push; -using Bit.Core.Repositories; -using Bit.Core.Services; -using Bit.Core.Tools.Repositories; -using Bit.Core.Vault.Repositories; -using Microsoft.AspNetCore.Identity; - -namespace Bit.Core.KeyManagement.UserKey.Implementations; - -/// -public class RotateUserKeyCommand : IRotateUserKeyCommand -{ - private readonly IUserService _userService; - private readonly IUserRepository _userRepository; - private readonly ICipherRepository _cipherRepository; - private readonly IFolderRepository _folderRepository; - private readonly ISendRepository _sendRepository; - private readonly IEmergencyAccessRepository _emergencyAccessRepository; - private readonly IOrganizationUserRepository _organizationUserRepository; - private readonly IPushNotificationService _pushService; - private readonly IdentityErrorDescriber _identityErrorDescriber; - private readonly IWebAuthnCredentialRepository _credentialRepository; - - /// - /// Instantiates a new - /// - /// Master password hash validation - /// Updates user keys and re-encrypted data if needed - /// Provides a method to update re-encrypted cipher data - /// Provides a method to update re-encrypted folder data - /// Provides a method to update re-encrypted send data - /// Provides a method to update re-encrypted emergency access data - /// Logs out user from other devices after successful rotation - /// Provides a password mismatch error if master password hash validation fails - public RotateUserKeyCommand(IUserService userService, IUserRepository userRepository, - ICipherRepository cipherRepository, IFolderRepository folderRepository, ISendRepository sendRepository, - IEmergencyAccessRepository emergencyAccessRepository, IOrganizationUserRepository organizationUserRepository, - IPushNotificationService pushService, IdentityErrorDescriber errors, IWebAuthnCredentialRepository credentialRepository) - { - _userService = userService; - _userRepository = userRepository; - _cipherRepository = cipherRepository; - _folderRepository = folderRepository; - _sendRepository = sendRepository; - _emergencyAccessRepository = emergencyAccessRepository; - _organizationUserRepository = organizationUserRepository; - _pushService = pushService; - _identityErrorDescriber = errors; - _credentialRepository = credentialRepository; - } - - /// - public async Task RotateUserKeyAsync(User user, RotateUserKeyData model) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) - { - return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); - } - - var now = DateTime.UtcNow; - user.RevisionDate = user.AccountRevisionDate = now; - user.LastKeyRotationDate = now; - user.SecurityStamp = Guid.NewGuid().ToString(); - user.Key = model.Key; - user.PrivateKey = model.PrivateKey; - if (model.Ciphers.Any() || model.Folders.Any() || model.Sends.Any() || model.EmergencyAccesses.Any() || - model.OrganizationUsers.Any() || model.WebAuthnKeys.Any()) - { - List saveEncryptedDataActions = new(); - - if (model.Ciphers.Any()) - { - saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers)); - } - - if (model.Folders.Any()) - { - saveEncryptedDataActions.Add(_folderRepository.UpdateForKeyRotation(user.Id, model.Folders)); - } - - if (model.Sends.Any()) - { - saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, model.Sends)); - } - - if (model.EmergencyAccesses.Any()) - { - saveEncryptedDataActions.Add( - _emergencyAccessRepository.UpdateForKeyRotation(user.Id, model.EmergencyAccesses)); - } - - if (model.OrganizationUsers.Any()) - { - saveEncryptedDataActions.Add( - _organizationUserRepository.UpdateForKeyRotation(user.Id, model.OrganizationUsers)); - } - - if (model.WebAuthnKeys.Any()) - { - saveEncryptedDataActions.Add(_credentialRepository.UpdateKeysForRotationAsync(user.Id, model.WebAuthnKeys)); - } - - await _userRepository.UpdateUserKeyAndEncryptedDataAsync(user, saveEncryptedDataActions); - } - else - { - await _userRepository.ReplaceAsync(user); - } - - await _pushService.PushLogOutAsync(user.Id, excludeCurrentContextFromPush: true); - return IdentityResult.Success; - } -} diff --git a/src/Core/Models/Api/Request/PushSendRequestModel.cs b/src/Core/Models/Api/Request/PushSendRequestModel.cs index 0ef7e999e3..19f89d931f 100644 --- a/src/Core/Models/Api/Request/PushSendRequestModel.cs +++ b/src/Core/Models/Api/Request/PushSendRequestModel.cs @@ -4,22 +4,22 @@ using Bit.Core.Enums; namespace Bit.Core.Models.Api; -public class PushSendRequestModel : IValidatableObject +public class PushSendRequestModel : IValidatableObject { - public string? UserId { get; set; } - public string? OrganizationId { get; set; } - public string? DeviceId { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public Guid? DeviceId { get; set; } public string? Identifier { get; set; } public required PushType Type { get; set; } - public required object Payload { get; set; } + public required T Payload { get; set; } public ClientType? ClientType { get; set; } - public string? InstallationId { get; set; } + public Guid? InstallationId { get; set; } public IEnumerable Validate(ValidationContext validationContext) { - if (string.IsNullOrWhiteSpace(UserId) && - string.IsNullOrWhiteSpace(OrganizationId) && - string.IsNullOrWhiteSpace(InstallationId)) + if (!UserId.HasValue && + !OrganizationId.HasValue && + !InstallationId.HasValue) { yield return new ValidationResult( $"{nameof(UserId)} or {nameof(OrganizationId)} or {nameof(InstallationId)} is required."); diff --git a/src/Core/NotificationCenter/Repositories/INotificationRepository.cs b/src/Core/NotificationCenter/Repositories/INotificationRepository.cs index 21604ed169..0d0ea80491 100644 --- a/src/Core/NotificationCenter/Repositories/INotificationRepository.cs +++ b/src/Core/NotificationCenter/Repositories/INotificationRepository.cs @@ -32,4 +32,13 @@ public interface INotificationRepository : IRepository /// Task> GetByUserIdAndStatusAsync(Guid userId, ClientType clientType, NotificationStatusFilter? statusFilter, PageOptions pageOptions); + + /// + /// Marks notifications as deleted by a taskId. + /// + /// The unique identifier of the task. + /// + /// A collection of UserIds for the notifications that are now marked as deleted. + /// + Task> MarkNotificationsAsDeletedByTask(Guid taskId); } diff --git a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs index 368c0f731b..81ec82a25d 100644 --- a/src/Core/NotificationHub/NotificationHubPushNotificationService.cs +++ b/src/Core/NotificationHub/NotificationHubPushNotificationService.cs @@ -1,21 +1,17 @@ #nullable enable using System.Text.Json; using System.Text.RegularExpressions; -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.Models.Data; -using Bit.Core.NotificationCenter.Entities; using Bit.Core.Platform.Push; +using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; -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; @@ -26,52 +22,32 @@ namespace Bit.Core.NotificationHub; /// Used by Cloud-Hosted environments. /// Received by Firebase for Android or APNS for iOS. /// -public class NotificationHubPushNotificationService : IPushNotificationService +public class NotificationHubPushNotificationService : IPushEngine, IPushRelayer { private readonly IInstallationDeviceRepository _installationDeviceRepository; private readonly IHttpContextAccessor _httpContextAccessor; private readonly bool _enableTracing = false; private readonly INotificationHubPool _notificationHubPool; private readonly ILogger _logger; - private readonly IGlobalSettings _globalSettings; - private readonly TimeProvider _timeProvider; public NotificationHubPushNotificationService( IInstallationDeviceRepository installationDeviceRepository, INotificationHubPool notificationHubPool, IHttpContextAccessor httpContextAccessor, ILogger logger, - IGlobalSettings globalSettings, - TimeProvider timeProvider) + IGlobalSettings globalSettings) { _installationDeviceRepository = installationDeviceRepository; _httpContextAccessor = httpContextAccessor; _notificationHubPool = notificationHubPool; _logger = logger; - _globalSettings = globalSettings; - _timeProvider = timeProvider; if (globalSettings.Installation.Id == Guid.Empty) { logger.LogWarning("Installation ID is not set. Push notifications for installations will not work."); } } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -93,311 +69,17 @@ public class NotificationHubPushNotificationService : IPushNotificationService CollectionIds = collectionIds, }; - await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true); - } - } - - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendPayloadToUserAsync(folder.UserId, type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification + await PushAsync(new PushNotification { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendPayloadToUserAsync(message.UserId, type, message, true); + Type = type, + Target = NotificationTarget.User, + TargetId = cipher.UserId.Value, + Payload = message, + ExcludeCurrentContext = true, + }); } } - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - public async Task PushNotificationAsync(Notification notification) - { - Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty - ? _globalSettings.Installation.Id - : null; - - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = installationId, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - if (notification.Global) - { - if (installationId.HasValue) - { - await SendPayloadToInstallationAsync(installationId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else - { - _logger.LogWarning( - "Invalid global notification id {NotificationId} push notification. No installation id provided.", - notification.Id); - } - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); - } - } - - public async Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) - { - Guid? installationId = notification.Global && _globalSettings.Installation.Id != Guid.Empty - ? _globalSettings.Installation.Id - : null; - - var message = new NotificationPushNotification - { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = installationId, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - if (notification.Global) - { - if (installationId.HasValue) - { - await SendPayloadToInstallationAsync(installationId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else - { - _logger.LogWarning( - "Invalid global notification status id {NotificationId} push notification. No installation id provided.", - notification.Id); - } - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus, - message, true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); - } - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendPayloadToUserAsync(authRequest.UserId, type, message, true); - } - - private async Task SendPayloadToInstallationAsync(Guid installationId, PushType type, object payload, - bool excludeCurrentContext, ClientType? clientType = null) - { - await SendPayloadToInstallationAsync(installationId.ToString(), type, payload, - GetContextIdentifier(excludeCurrentContext), clientType: clientType); - } - - private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext, - ClientType? clientType = null) - { - await SendPayloadToUserAsync(userId.ToString(), type, payload, GetContextIdentifier(excludeCurrentContext), - clientType: clientType); - } - - private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, - bool excludeCurrentContext, ClientType? clientType = null) - { - await SendPayloadToOrganizationAsync(orgId.ToString(), type, payload, - GetContextIdentifier(excludeCurrentContext), clientType: clientType); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - public async Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, - string? identifier, string? deviceId = null, ClientType? clientType = null) - { - var tag = BuildTag($"template:payload && installationId:{installationId}", identifier, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - 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, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - 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, clientType); - await SendPayloadAsync(tag, type, payload); - if (InstallationDeviceEntity.IsInstallationDeviceId(deviceId)) - { - await _installationDeviceRepository.UpsertAsync(new InstallationDeviceEntity(deviceId)); - } - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendPayloadToOrganizationAsync( - organization.Id, - PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, - false - ); - private string? GetContextIdentifier(bool excludeCurrentContext) { if (!excludeCurrentContext) @@ -425,13 +107,73 @@ public class NotificationHubPushNotificationService : IPushNotificationService return $"({tag})"; } - private async Task SendPayloadAsync(string tag, PushType type, object payload) + public async Task PushAsync(PushNotification pushNotification) + where T : class { + var initialTag = pushNotification.Target switch + { + NotificationTarget.User => $"template:payload_userId:{pushNotification.TargetId}", + NotificationTarget.Organization => $"template:payload && organizationId:{pushNotification.TargetId}", + NotificationTarget.Installation => $"template:payload && installationId:{pushNotification.TargetId}", + _ => throw new InvalidOperationException($"Push notification target '{pushNotification.Target}' is not valid."), + }; + + await PushCoreAsync( + initialTag, + GetContextIdentifier(pushNotification.ExcludeCurrentContext), + pushNotification.Type, + pushNotification.ClientType, + pushNotification.Payload + ); + } + + public async Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification) + { + // Relayed notifications need identifiers prefixed with the installation they are from and a underscore + var initialTag = relayedNotification.Target switch + { + NotificationTarget.User => $"template:payload_userId:{fromInstallation}_{relayedNotification.TargetId}", + NotificationTarget.Organization => $"template:payload && organizationId:{fromInstallation}_{relayedNotification.TargetId}", + NotificationTarget.Installation => $"template:payload && installationId:{fromInstallation}", + _ => throw new InvalidOperationException($"Invalid Notification target {relayedNotification.Target}"), + }; + + await PushCoreAsync( + initialTag, + relayedNotification.Identifier, + relayedNotification.Type, + relayedNotification.ClientType, + relayedNotification.Payload + ); + + if (relayedNotification.DeviceId.HasValue) + { + await _installationDeviceRepository.UpsertAsync( + new InstallationDeviceEntity(fromInstallation, relayedNotification.DeviceId.Value) + ); + } + else + { + _logger.LogWarning( + "A related notification of type '{Type}' came through without a device id from installation {Installation}", + relayedNotification.Type, + fromInstallation + ); + } + } + + private async Task PushCoreAsync(string initialTag, string? contextId, PushType pushType, ClientType? clientType, T payload) + { + var finalTag = BuildTag(initialTag, contextId, clientType); + var results = await _notificationHubPool.AllClients.SendTemplateNotificationAsync( new Dictionary { - { "type", ((byte)type).ToString() }, { "payload", JsonSerializer.Serialize(payload) } - }, tag); + { "type", ((byte)pushType).ToString() }, + { "payload", JsonSerializer.Serialize(payload) }, + }, + finalTag + ); if (_enableTracing) { @@ -444,7 +186,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService _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); + outcome.TrackingId, pushType, outcome.Success, outcome.Failure, payload, outcome.Results); } } } diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs index 1d7eb8f2ba..929c236ef2 100644 --- a/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs @@ -52,6 +52,11 @@ public class BulkAddCollectionAccessCommand : IBulkAddCollectionAccessCommand throw new BadRequestException("No collections were provided."); } + if (collections.Any(c => c.Type == CollectionType.DefaultUserCollection)) + { + throw new BadRequestException("You cannot add access to collections with the type as DefaultUserCollection."); + } + var orgId = collections.First().OrganizationId; if (collections.Any(c => c.OrganizationId != orgId)) diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/CreateCollectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/CreateCollectionCommand.cs new file mode 100644 index 0000000000..d83e30ad9c --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationCollections/CreateCollectionCommand.cs @@ -0,0 +1,76 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.OrganizationFeatures.OrganizationCollections; + +public class CreateCollectionCommand : ICreateCollectionCommand +{ + private readonly IEventService _eventService; + private readonly IOrganizationRepository _organizationRepository; + private readonly ICollectionRepository _collectionRepository; + + public CreateCollectionCommand( + IEventService eventService, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository) + { + _eventService = eventService; + _organizationRepository = organizationRepository; + _collectionRepository = collectionRepository; + } + + public async Task CreateAsync(Collection collection, IEnumerable groups = null, + IEnumerable users = null) + { + if (collection.Type == CollectionType.DefaultUserCollection) + { + throw new BadRequestException("You cannot create a collection with the type as DefaultUserCollection."); + } + + var org = await _organizationRepository.GetByIdAsync(collection.OrganizationId); + if (org == null) + { + throw new BadRequestException("Organization not found"); + } + + var groupsList = groups?.ToList(); + var usersList = users?.ToList(); + + // Cannot use Manage with ReadOnly/HidePasswords permissions + var invalidAssociations = groupsList?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); + if (invalidAssociations?.Any() ?? false) + { + throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); + } + + // A collection should always have someone with Can Manage permissions + var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false; + var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false; + if (!groupHasManageAccess && !userHasManageAccess && !org.AllowAdminAccessToAllCollectionItems) + { + throw new BadRequestException( + "At least one member or group must have can manage permission."); + } + + // Check max collections limit + if (org.MaxCollections.HasValue) + { + var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); + if (org.MaxCollections.Value <= collectionCount) + { + throw new BadRequestException("You have reached the maximum number of collections " + + $"({org.MaxCollections.Value}) for this organization."); + } + } + + await _collectionRepository.CreateAsync(collection, org.UseGroups ? groupsList : null, usersList); + await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created); + + return collection; + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommand.cs index 11f29f228f..4f678633a9 100644 --- a/src/Core/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommand.cs @@ -1,4 +1,6 @@ using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; @@ -20,6 +22,11 @@ public class DeleteCollectionCommand : IDeleteCollectionCommand public async Task DeleteAsync(Collection collection) { + if (collection.Type == CollectionType.DefaultUserCollection) + { + throw new BadRequestException("You cannot delete a collection with the type as DefaultUserCollection."); + } + await _collectionRepository.DeleteAsync(collection); await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Deleted, DateTime.UtcNow); } @@ -33,6 +40,11 @@ public class DeleteCollectionCommand : IDeleteCollectionCommand public async Task DeleteManyAsync(IEnumerable collections) { + if (collections.Any(c => c.Type == Enums.CollectionType.DefaultUserCollection)) + { + throw new BadRequestException("You cannot delete collections with the type as DefaultUserCollection."); + } + await _collectionRepository.DeleteManyAsync(collections.Select(c => c.Id)); await _eventService.LogCollectionEventsAsync(collections.Select(c => (c, Enums.EventType.Collection_Deleted, (DateTime?)DateTime.UtcNow))); } diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/ICreateCollectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/ICreateCollectionCommand.cs new file mode 100644 index 0000000000..b73afb4d1e --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/ICreateCollectionCommand.cs @@ -0,0 +1,17 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Data; + +namespace Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; + +public interface ICreateCollectionCommand +{ + /// + /// Creates a new collection. + /// + /// The collection to create. + /// (Optional) The groups that will have access to the collection. + /// (Optional) The users that will have access to the collection. + /// The created collection. + Task CreateAsync(Collection collection, IEnumerable groups = null, + IEnumerable users = null); +} diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/IUpdateCollectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/IUpdateCollectionCommand.cs new file mode 100644 index 0000000000..94d4d1d1f8 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationCollections/Interfaces/IUpdateCollectionCommand.cs @@ -0,0 +1,17 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Data; + +namespace Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; + +public interface IUpdateCollectionCommand +{ + /// + /// Updates a collection. + /// + /// The collection to update. + /// (Optional) The groups that will have access to the collection. + /// (Optional) The users that will have access to the collection. + /// The updated collection. + Task UpdateAsync(Collection collection, IEnumerable groups = null, + IEnumerable users = null); +} diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommand.cs new file mode 100644 index 0000000000..19ad47a0a5 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommand.cs @@ -0,0 +1,65 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.OrganizationFeatures.OrganizationCollections; + +public class UpdateCollectionCommand : IUpdateCollectionCommand +{ + private readonly IEventService _eventService; + private readonly IOrganizationRepository _organizationRepository; + private readonly ICollectionRepository _collectionRepository; + + public UpdateCollectionCommand( + IEventService eventService, + IOrganizationRepository organizationRepository, + ICollectionRepository collectionRepository) + { + _eventService = eventService; + _organizationRepository = organizationRepository; + _collectionRepository = collectionRepository; + } + + public async Task UpdateAsync(Collection collection, IEnumerable groups = null, + IEnumerable users = null) + { + if (collection.Type == CollectionType.DefaultUserCollection) + { + throw new BadRequestException("You cannot edit a collection with the type as DefaultUserCollection."); + } + + var org = await _organizationRepository.GetByIdAsync(collection.OrganizationId); + if (org == null) + { + throw new BadRequestException("Organization not found"); + } + + var groupsList = groups?.ToList(); + var usersList = users?.ToList(); + + // Cannot use Manage with ReadOnly/HidePasswords permissions + var invalidAssociations = groupsList?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); + if (invalidAssociations?.Any() ?? false) + { + throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); + } + + // A collection should always have someone with Can Manage permissions + var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false; + var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false; + if (!groupHasManageAccess && !userHasManageAccess && !org.AllowAdminAccessToAllCollectionItems) + { + throw new BadRequestException( + "At least one member or group must have can manage permission."); + } + + await _collectionRepository.ReplaceAsync(collection, org.UseGroups ? groupsList : null, usersList); + await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Updated); + + return collection; + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 2bc05017d5..ef78e966f6 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -73,6 +73,7 @@ public static class OrganizationServiceCollectionExtensions { services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void AddOrganizationDeleteCommands(this IServiceCollection services) @@ -143,6 +144,8 @@ public static class OrganizationServiceCollectionExtensions public static void AddOrganizationCollectionCommands(this IServiceCollection services) { + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); } diff --git a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs index 05d1dd2d1d..94a20f1971 100644 --- a/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/AzureQueuePushNotificationService.cs @@ -1,14 +1,10 @@ #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.NotificationCenter.Entities; using Bit.Core.Settings; -using Bit.Core.Tools.Entities; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; @@ -17,12 +13,10 @@ using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; -public class AzureQueuePushNotificationService : IPushNotificationService +public class AzureQueuePushNotificationService : IPushEngine { private readonly QueueClient _queueClient; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IGlobalSettings _globalSettings; - private readonly TimeProvider _timeProvider; public AzureQueuePushNotificationService( [FromKeyedServices("notifications")] QueueClient queueClient, @@ -33,30 +27,13 @@ public class AzureQueuePushNotificationService : IPushNotificationService { _queueClient = queueClient; _httpContextAccessor = httpContextAccessor; - _globalSettings = globalSettings; - _timeProvider = timeProvider; if (globalSettings.Installation.Id == Guid.Empty) { logger.LogWarning("Installation ID is not set. Push notifications for installations will not work."); } } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -83,166 +60,6 @@ public class AzureQueuePushNotificationService : IPushNotificationService } } - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendMessageAsync(type, message, excludeCurrentContext); - } - - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - 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, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - await SendMessageAsync(PushType.Notification, 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, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - await SendMessageAsync(PushType.NotificationStatus, message, true); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification - { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - } - private async Task SendMessageAsync(PushType type, T payload, bool excludeCurrentContext) { var contextId = GetContextIdentifier(excludeCurrentContext); @@ -263,42 +80,9 @@ public class AzureQueuePushNotificationService : IPushNotificationService return currentContext?.DeviceIdentifier; } - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - // Noop - Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) + public async Task PushAsync(PushNotification pushNotification) + where T : class { - // Noop - return Task.FromResult(0); + await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext); } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - // Noop - return Task.FromResult(0); - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, false); } diff --git a/src/Core/Platform/Push/Services/IPushEngine.cs b/src/Core/Platform/Push/Services/IPushEngine.cs new file mode 100644 index 0000000000..bde4ddaf4b --- /dev/null +++ b/src/Core/Platform/Push/Services/IPushEngine.cs @@ -0,0 +1,13 @@ +#nullable enable +using Bit.Core.Enums; +using Bit.Core.Vault.Entities; + +namespace Bit.Core.Platform.Push; + +public interface IPushEngine +{ + Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds); + + Task PushAsync(PushNotification pushNotification) + where T : class; +} diff --git a/src/Core/Platform/Push/Services/IPushNotificationService.cs b/src/Core/Platform/Push/Services/IPushNotificationService.cs index 60f3c35089..58b8a4722d 100644 --- a/src/Core/Platform/Push/Services/IPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/IPushNotificationService.cs @@ -2,41 +2,410 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Enums; +using Bit.Core.Models; using Bit.Core.NotificationCenter.Entities; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; +using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push; public interface IPushNotificationService { - Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds); - Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds); - Task PushSyncCipherDeleteAsync(Cipher cipher); - Task PushSyncFolderCreateAsync(Folder folder); - Task PushSyncFolderUpdateAsync(Folder folder); - Task PushSyncFolderDeleteAsync(Folder folder); - Task PushSyncCiphersAsync(Guid userId); - Task PushSyncVaultAsync(Guid userId); - Task PushSyncOrganizationsAsync(Guid userId); - Task PushSyncOrgKeysAsync(Guid userId); - Task PushSyncSettingsAsync(Guid userId); - Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false); - 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 PushSyncOrganizationStatusAsync(Organization organization); - Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization); + Guid InstallationId { get; } + TimeProvider TimeProvider { get; } + ILogger Logger { get; } - Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null); - 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); - Task PushPendingSecurityTasksAsync(Guid userId); + #region Legacy method, to be removed soon. + Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) + => PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); + + Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) + => PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); + + Task PushSyncCipherDeleteAsync(Cipher cipher) + => PushCipherAsync(cipher, PushType.SyncLoginDelete, null); + + Task PushSyncFolderCreateAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderCreate, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncFolderUpdateAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderUpdate, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncFolderDeleteAsync(Folder folder) + => PushAsync(new PushNotification + { + Type = PushType.SyncFolderDelete, + Target = NotificationTarget.User, + TargetId = folder.UserId, + Payload = new SyncFolderPushNotification + { + Id = folder.Id, + UserId = folder.UserId, + RevisionDate = folder.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncCiphersAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncCiphers, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncVaultAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncVault, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrganizationsAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizations, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrgKeysAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrgKeys, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncSettingsAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.SyncSettings, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + + Task PushLogOutAsync(Guid userId, bool excludeCurrentContextFromPush = false) + => PushAsync(new PushNotification + { + Type = PushType.LogOut, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = excludeCurrentContextFromPush, + }); + + Task PushSyncSendCreateAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendCreate, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + Task PushSyncSendUpdateAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendUpdate, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + Task PushSyncSendDeleteAsync(Send send) + { + if (send.UserId.HasValue) + { + return PushAsync(new PushNotification + { + Type = PushType.SyncSendDelete, + Target = NotificationTarget.User, + TargetId = send.UserId.Value, + Payload = new SyncSendPushNotification + { + Id = send.Id, + UserId = send.UserId.Value, + RevisionDate = send.RevisionDate, + }, + ExcludeCurrentContext = true, + }); + } + + return Task.CompletedTask; + } + + 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, + InstallationId = notification.Global ? InstallationId : null, + TaskId = notification.TaskId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + }; + + NotificationTarget target; + Guid targetId; + + if (notification.Global) + { + // TODO: Think about this a bit more + target = NotificationTarget.Installation; + targetId = InstallationId; + } + else if (notification.UserId.HasValue) + { + target = NotificationTarget.User; + targetId = notification.UserId.Value; + } + else if (notification.OrganizationId.HasValue) + { + target = NotificationTarget.Organization; + targetId = notification.OrganizationId.Value; + } + else + { + Logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); + return Task.CompletedTask; + } + + return PushAsync(new PushNotification + { + Type = PushType.Notification, + Target = target, + TargetId = targetId, + Payload = message, + ExcludeCurrentContext = true, + ClientType = notification.ClientType, + }); + } + + 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, + InstallationId = notification.Global ? InstallationId : null, + TaskId = notification.TaskId, + Title = notification.Title, + Body = notification.Body, + CreationDate = notification.CreationDate, + RevisionDate = notification.RevisionDate, + ReadDate = notificationStatus.ReadDate, + DeletedDate = notificationStatus.DeletedDate, + }; + + NotificationTarget target; + Guid targetId; + + if (notification.Global) + { + // TODO: Think about this a bit more + target = NotificationTarget.Installation; + targetId = InstallationId; + } + else if (notification.UserId.HasValue) + { + target = NotificationTarget.User; + targetId = notification.UserId.Value; + } + else if (notification.OrganizationId.HasValue) + { + target = NotificationTarget.Organization; + targetId = notification.OrganizationId.Value; + } + else + { + Logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); + return Task.CompletedTask; + } + + return PushAsync(new PushNotification + { + Type = PushType.NotificationStatus, + Target = target, + TargetId = targetId, + Payload = message, + ExcludeCurrentContext = true, + ClientType = notification.ClientType, + }); + } + + Task PushAuthRequestAsync(AuthRequest authRequest) + => PushAsync(new PushNotification + { + Type = PushType.AuthRequest, + Target = NotificationTarget.User, + TargetId = authRequest.UserId, + Payload = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId, + }, + ExcludeCurrentContext = true, + }); + + Task PushAuthRequestResponseAsync(AuthRequest authRequest) + => PushAsync(new PushNotification + { + Type = PushType.AuthRequestResponse, + Target = NotificationTarget.User, + TargetId = authRequest.UserId, + Payload = new AuthRequestPushNotification + { + Id = authRequest.Id, + UserId = authRequest.UserId, + }, + ExcludeCurrentContext = true, + }); + + Task PushSyncOrganizationStatusAsync(Organization organization) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizationStatusChanged, + Target = NotificationTarget.Organization, + TargetId = organization.Id, + Payload = new OrganizationStatusPushNotification + { + OrganizationId = organization.Id, + Enabled = organization.Enabled, + }, + ExcludeCurrentContext = false, + }); + + Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) + => PushAsync(new PushNotification + { + Type = PushType.SyncOrganizationCollectionSettingChanged, + Target = NotificationTarget.Organization, + TargetId = organization.Id, + Payload = new OrganizationCollectionManagementPushNotification + { + OrganizationId = organization.Id, + LimitCollectionCreation = organization.LimitCollectionCreation, + LimitCollectionDeletion = organization.LimitCollectionDeletion, + LimitItemDeletion = organization.LimitItemDeletion, + }, + ExcludeCurrentContext = false, + }); + + Task PushPendingSecurityTasksAsync(Guid userId) + => PushAsync(new PushNotification + { + Type = PushType.PendingSecurityTasks, + Target = NotificationTarget.User, + TargetId = userId, + Payload = new UserPushNotification + { + UserId = userId, + Date = TimeProvider.GetUtcNow().UtcDateTime, + }, + ExcludeCurrentContext = false, + }); + #endregion + + Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds); + + Task PushAsync(PushNotification pushNotification) + where T : class; } diff --git a/src/Core/Platform/Push/Services/IPushRelayer.cs b/src/Core/Platform/Push/Services/IPushRelayer.cs new file mode 100644 index 0000000000..fde0a521f3 --- /dev/null +++ b/src/Core/Platform/Push/Services/IPushRelayer.cs @@ -0,0 +1,44 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.Enums; + +namespace Bit.Core.Platform.Push.Internal; + +/// +/// An object encapsulating the information that is available in a notification +/// given to us from a self-hosted installation. +/// +public class RelayedNotification +{ + /// + public required PushType Type { get; init; } + /// + public required NotificationTarget Target { get; init; } + /// + public required Guid TargetId { get; init; } + /// + public required JsonElement Payload { get; init; } + /// + public required ClientType? ClientType { get; init; } + public required Guid? DeviceId { get; init; } + public required string? Identifier { get; init; } +} + +/// +/// A service for taking a notification that was relayed to us from a self-hosted installation and +/// will be injested into our infrastructure so that we can get the notification to devices that require +/// cloud interaction. +/// +/// +/// This interface should be treated as internal and not consumed by other teams. +/// +public interface IPushRelayer +{ + /// + /// Relays a notification that was received from an authenticated installation into our cloud push notification infrastructure. + /// + /// The authenticated installation this notification came from. + /// The information received from the self-hosted installation. + Task RelayAsync(Guid fromInstallation, RelayedNotification relayedNotification); +} diff --git a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs index 490b690a3b..404b153fa3 100644 --- a/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs +++ b/src/Core/Platform/Push/Services/MultiServicePushNotificationService.cs @@ -1,202 +1,77 @@ #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; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; public class MultiServicePushNotificationService : IPushNotificationService { - private readonly IEnumerable _services; - private readonly ILogger _logger; + private readonly IEnumerable _services; + + public Guid InstallationId { get; } + + public TimeProvider TimeProvider { get; } + + public ILogger Logger { get; } public MultiServicePushNotificationService( - [FromKeyedServices("implementation")] IEnumerable services, + IEnumerable services, ILogger logger, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + TimeProvider timeProvider) { _services = services; - _logger = logger; - _logger.LogInformation("Hub services: {Services}", _services.Count()); + Logger = logger; + Logger.LogInformation("Hub services: {Services}", _services.Count()); globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub => { - _logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); + Logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); }); + InstallationId = globalSettings.Installation.Id; + TimeProvider = timeProvider; } - public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - PushToServices((s) => s.PushSyncCipherCreateAsync(cipher, collectionIds)); - return Task.FromResult(0); - } - - public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - PushToServices((s) => s.PushSyncCipherUpdateAsync(cipher, collectionIds)); - return Task.FromResult(0); - } - - public Task PushSyncCipherDeleteAsync(Cipher cipher) - { - PushToServices((s) => s.PushSyncCipherDeleteAsync(cipher)); - return Task.FromResult(0); - } - - public Task PushSyncFolderCreateAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderCreateAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncFolderUpdateAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderUpdateAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncFolderDeleteAsync(Folder folder) - { - PushToServices((s) => s.PushSyncFolderDeleteAsync(folder)); - return Task.FromResult(0); - } - - public Task PushSyncCiphersAsync(Guid userId) - { - PushToServices((s) => s.PushSyncCiphersAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncVaultAsync(Guid userId) - { - PushToServices((s) => s.PushSyncVaultAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationsAsync(Guid userId) - { - PushToServices((s) => s.PushSyncOrganizationsAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncOrgKeysAsync(Guid userId) - { - PushToServices((s) => s.PushSyncOrgKeysAsync(userId)); - return Task.FromResult(0); - } - - public Task PushSyncSettingsAsync(Guid userId) - { - PushToServices((s) => s.PushSyncSettingsAsync(userId)); - return Task.FromResult(0); - } - - public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - PushToServices((s) => s.PushLogOutAsync(userId, excludeCurrentContext)); - return Task.FromResult(0); - } - - public Task PushSyncSendCreateAsync(Send send) - { - PushToServices((s) => s.PushSyncSendCreateAsync(send)); - return Task.FromResult(0); - } - - public Task PushSyncSendUpdateAsync(Send send) - { - PushToServices((s) => s.PushSyncSendUpdateAsync(send)); - return Task.FromResult(0); - } - - public Task PushAuthRequestAsync(AuthRequest authRequest) - { - PushToServices((s) => s.PushAuthRequestAsync(authRequest)); - return Task.FromResult(0); - } - - public Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - PushToServices((s) => s.PushAuthRequestResponseAsync(authRequest)); - return Task.FromResult(0); - } - - public Task PushSyncSendDeleteAsync(Send send) - { - PushToServices((s) => s.PushSyncSendDeleteAsync(send)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationStatusAsync(Organization organization) - { - PushToServices((s) => s.PushSyncOrganizationStatusAsync(organization)); - return Task.FromResult(0); - } - - public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) - { - PushToServices(s => s.PushSyncOrganizationCollectionManagementSettingsAsync(organization)); - 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 SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - PushToServices((s) => - s.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType)); - 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); - } - - public Task PushPendingSecurityTasksAsync(Guid userId) - { - PushToServices((s) => s.PushPendingSecurityTasksAsync(userId)); - return Task.CompletedTask; - } - - private void PushToServices(Func pushFunc) + private Task PushToServices(Func pushFunc) { if (!_services.Any()) { - _logger.LogWarning("No services found to push notification"); - return; + Logger.LogWarning("No services found to push notification"); + return Task.CompletedTask; } + +#if DEBUG + var tasks = new List(); +#endif + foreach (var service in _services) { - _logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); + Logger.LogDebug("Pushing notification to service {ServiceName}", service.GetType().Name); +#if DEBUG + var task = +#endif pushFunc(service); +#if DEBUG + tasks.Add(task); +#endif } + +#if DEBUG + return Task.WhenAll(tasks); +#else + return Task.CompletedTask; +#endif + } + + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) + { + return PushToServices((s) => s.PushCipherAsync(cipher, pushType, collectionIds)); + } + public Task PushAsync(PushNotification pushNotification) where T : class + { + return PushToServices((s) => s.PushAsync(pushNotification)); } } diff --git a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs index 6e7278cf94..e6f71de006 100644 --- a/src/Core/Platform/Push/Services/NoopPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NoopPushNotificationService.cs @@ -1,129 +1,12 @@ #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; namespace Bit.Core.Platform.Push.Internal; -public class NoopPushNotificationService : IPushNotificationService +internal class NoopPushNotificationService : IPushEngine { - public Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - return Task.FromResult(0); - } + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) => Task.CompletedTask; - public Task PushSyncCipherDeleteAsync(Cipher cipher) - { - return Task.FromResult(0); - } - - public Task PushSyncCiphersAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderCreateAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderDeleteAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncFolderUpdateAsync(Folder folder) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationsAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncOrgKeysAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncSettingsAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushSyncVaultAsync(Guid userId) - { - return Task.FromResult(0); - } - - public Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - return Task.FromResult(0); - } - - public Task PushSyncSendCreateAsync(Send send) - { - return Task.FromResult(0); - } - - public Task PushSyncSendDeleteAsync(Send send) - { - return Task.FromResult(0); - } - - public Task PushSyncSendUpdateAsync(Send send) - { - return Task.FromResult(0); - } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationStatusAsync(Organization organization) - { - return Task.FromResult(0); - } - - public Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => Task.CompletedTask; - - public Task PushAuthRequestAsync(AuthRequest authRequest) - { - return Task.FromResult(0); - } - - public Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - return Task.FromResult(0); - } - - public Task PushNotificationAsync(Notification notification) => Task.CompletedTask; - - public Task PushNotificationStatusAsync(Notification notification, NotificationStatus notificationStatus) => - Task.CompletedTask; - - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - return Task.FromResult(0); - } - - public Task PushPendingSecurityTasksAsync(Guid userId) - { - return Task.FromResult(0); - } + public Task PushAsync(PushNotification pushNotification) where T : class => Task.CompletedTask; } diff --git a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs index bdeefc0363..5e0d584ba8 100644 --- a/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/NotificationsApiPushNotificationService.cs @@ -1,13 +1,9 @@ #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; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -20,18 +16,15 @@ namespace Bit.Core.Platform.Push; /// Used by Cloud-Hosted environments. /// Received by AzureQueueHostedService message receiver in Notifications project. /// -public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService +public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushEngine { - private readonly IGlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly TimeProvider _timeProvider; public NotificationsApiPushNotificationService( IHttpClientFactory httpFactory, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger, - TimeProvider timeProvider) + ILogger logger) : base( httpFactory, globalSettings.BaseServiceUri.InternalNotifications, @@ -41,27 +34,10 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService globalSettings.InternalIdentityKey, logger) { - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; - _timeProvider = timeProvider; } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -89,174 +65,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService } } - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification - { - UserId = userId, - Date = _timeProvider.GetUtcNow().UtcDateTime, - }; - - await SendMessageAsync(type, message, excludeCurrentContext); - } - - public async Task PushAuthRequestAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } - - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } - - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification - { - Id = authRequest.Id, - UserId = authRequest.UserId - }; - - await SendMessageAsync(type, message, true); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - 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, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate - }; - - await SendMessageAsync(PushType.Notification, 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, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - await SendMessageAsync(PushType.NotificationStatus, message, true); - } - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification - { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendMessageAsync(type, message, false); - } - } - private async Task SendMessageAsync(PushType type, T payload, bool excludeCurrentContext) { var contextId = GetContextIdentifier(excludeCurrentContext); @@ -276,43 +84,8 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService return currentContext?.DeviceIdentifier; } - public Task SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - // Noop - Task.CompletedTask; - - public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) + public async Task PushAsync(PushNotification pushNotification) where T : class { - // Noop - return Task.FromResult(0); + await SendMessageAsync(pushNotification.Type, pushNotification.Payload, pushNotification.ExcludeCurrentContext); } - - public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) - { - // Noop - return Task.FromResult(0); - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendMessageAsync(PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendMessageAsync(PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, false); } diff --git a/src/Core/Platform/Push/Services/PushNotification.cs b/src/Core/Platform/Push/Services/PushNotification.cs new file mode 100644 index 0000000000..e1d3f44cd8 --- /dev/null +++ b/src/Core/Platform/Push/Services/PushNotification.cs @@ -0,0 +1,78 @@ +#nullable enable +using Bit.Core.Enums; + +namespace Bit.Core.Platform.Push; + +/// +/// Contains constants for all the available targets for a given notification. +/// +public enum NotificationTarget +{ + /// + /// The target for the notification is a single user. + /// + User, + /// + /// The target for the notification are all the users in an organization. + /// + Organization, + /// + /// The target for the notification are all the organizations, + /// and all the users in that organization for a installation. + /// + Installation, +} + +/// +/// An object containing all the information required for getting a notification +/// to an end users device and the information you want available to that device. +/// +/// The type of the payload. This type is expected to be able to be roundtripped as JSON. +public record PushNotification + where T : class +{ + /// + /// The to be associated with the notification. This is used to route + /// the notification to the correct handler on the client side. Be sure to use the correct payload + /// type for the associated . + /// + public required PushType Type { get; init; } + + /// + /// The target entity type for the notification. + /// + /// + /// When the target type is the + /// property is expected to be a users ID. When it is + /// it should be an organizations id. When it is a + /// it should be an installation id. + /// + public required NotificationTarget Target { get; init; } + + /// + /// The indentifier for the given . + /// + public required Guid TargetId { get; init; } + + /// + /// The payload to be sent with the notification. This object will be JSON serialized. + /// + public required T Payload { get; init; } + + /// + /// When the notification will not include the current context identifier on it, this + /// means that the notification may get handled on the device that this notification could have originated from. + /// + public required bool ExcludeCurrentContext { get; init; } + + /// + /// The type of clients the notification should be sent to, if then + /// is inferred. + /// + public ClientType? ClientType { get; init; } + + internal Guid? GetTargetWhen(NotificationTarget notificationTarget) + { + return Target == notificationTarget ? TargetId : null; + } +} diff --git a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs index 0ede81e719..9f2289b864 100644 --- a/src/Core/Platform/Push/Services/RelayPushNotificationService.cs +++ b/src/Core/Platform/Push/Services/RelayPushNotificationService.cs @@ -1,18 +1,15 @@ #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; -using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Bit.Core.Platform.Push.Internal; @@ -22,20 +19,18 @@ namespace Bit.Core.Platform.Push.Internal; /// Used by Self-Hosted environments. /// Received by PushController endpoint in Api project. /// -public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService +public class RelayPushNotificationService : BaseIdentityClientService, IPushEngine { private readonly IDeviceRepository _deviceRepository; - private readonly IGlobalSettings _globalSettings; private readonly IHttpContextAccessor _httpContextAccessor; - private readonly TimeProvider _timeProvider; + public RelayPushNotificationService( IHttpClientFactory httpFactory, IDeviceRepository deviceRepository, GlobalSettings globalSettings, IHttpContextAccessor httpContextAccessor, - ILogger logger, - TimeProvider timeProvider) + ILogger logger) : base( httpFactory, globalSettings.PushRelayBaseUri, @@ -46,27 +41,10 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti logger) { _deviceRepository = deviceRepository; - _globalSettings = globalSettings; _httpContextAccessor = httpContextAccessor; - _timeProvider = timeProvider; } - public async Task PushSyncCipherCreateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherCreate, collectionIds); - } - - public async Task PushSyncCipherUpdateAsync(Cipher cipher, IEnumerable collectionIds) - { - await PushCipherAsync(cipher, PushType.SyncCipherUpdate, collectionIds); - } - - public async Task PushSyncCipherDeleteAsync(Cipher cipher) - { - await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); - } - - private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) + public async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable? collectionIds) { if (cipher.OrganizationId.HasValue) { @@ -87,306 +65,45 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti RevisionDate = cipher.RevisionDate, }; - await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true); - } - } - - public async Task PushSyncFolderCreateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderCreate); - } - - public async Task PushSyncFolderUpdateAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderUpdate); - } - - public async Task PushSyncFolderDeleteAsync(Folder folder) - { - await PushFolderAsync(folder, PushType.SyncFolderDelete); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - await SendPayloadToUserAsync(folder.UserId, type, message, true); - } - - public async Task PushSyncCiphersAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncCiphers); - } - - public async Task PushSyncVaultAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncVault); - } - - public async Task PushSyncOrganizationsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrganizations); - } - - public async Task PushSyncOrgKeysAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncOrgKeys); - } - - public async Task PushSyncSettingsAsync(Guid userId) - { - await PushUserAsync(userId, PushType.SyncSettings); - } - - public async Task PushLogOutAsync(Guid userId, bool excludeCurrentContext = false) - { - await PushUserAsync(userId, PushType.LogOut, excludeCurrentContext); - } - - private async Task PushUserAsync(Guid userId, PushType type, bool excludeCurrentContext = false) - { - var message = new UserPushNotification { UserId = userId, Date = _timeProvider.GetUtcNow().UtcDateTime }; - - await SendPayloadToUserAsync(userId, type, message, excludeCurrentContext); - } - - public async Task PushSyncSendCreateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendCreate); - } - - public async Task PushSyncSendUpdateAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendUpdate); - } - - public async Task PushSyncSendDeleteAsync(Send send) - { - await PushSendAsync(send, PushType.SyncSendDelete); - } - - private async Task PushSendAsync(Send send, PushType type) - { - if (send.UserId.HasValue) - { - var message = new SyncSendPushNotification + await PushAsync(new PushNotification { - Id = send.Id, - UserId = send.UserId.Value, - RevisionDate = send.RevisionDate - }; - - await SendPayloadToUserAsync(message.UserId, type, message, true); + Type = type, + Target = NotificationTarget.User, + TargetId = cipher.UserId.Value, + Payload = message, + ExcludeCurrentContext = true, + }); } } - public async Task PushAuthRequestAsync(AuthRequest authRequest) + public async Task PushAsync(PushNotification pushNotification) + where T : class { - await PushAuthRequestAsync(authRequest, PushType.AuthRequest); - } + var deviceIdentifier = _httpContextAccessor.HttpContext + ?.RequestServices.GetService() + ?.DeviceIdentifier; - public async Task PushAuthRequestResponseAsync(AuthRequest authRequest) - { - await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); - } + Guid? deviceId = null; - private async Task PushAuthRequestAsync(AuthRequest authRequest, PushType type) - { - var message = new AuthRequestPushNotification { Id = authRequest.Id, UserId = authRequest.UserId }; - - await SendPayloadToUserAsync(authRequest.UserId, type, message, true); - } - - public async Task PushNotificationAsync(Notification notification) - { - var message = new NotificationPushNotification + if (!string.IsNullOrEmpty(deviceIdentifier)) { - Id = notification.Id, - Priority = notification.Priority, - Global = notification.Global, - ClientType = notification.ClientType, - UserId = notification.UserId, - OrganizationId = notification.OrganizationId, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate + var device = await _deviceRepository.GetByIdentifierAsync(deviceIdentifier); + deviceId = device?.Id; + } + + var payload = new PushSendRequestModel + { + Type = pushNotification.Type, + UserId = pushNotification.GetTargetWhen(NotificationTarget.User), + OrganizationId = pushNotification.GetTargetWhen(NotificationTarget.Organization), + InstallationId = pushNotification.GetTargetWhen(NotificationTarget.Installation), + Payload = pushNotification.Payload, + Identifier = pushNotification.ExcludeCurrentContext ? deviceIdentifier : null, + // We set the device id regardless of if they want to exclude the current context or not + DeviceId = deviceId, + ClientType = pushNotification.ClientType, }; - if (notification.Global) - { - await SendPayloadToInstallationAsync(PushType.Notification, message, true, notification.ClientType); - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.Notification, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.Notification, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification id {NotificationId} push notification", notification.Id); - } - } - - 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, - InstallationId = notification.Global ? _globalSettings.Installation.Id : null, - TaskId = notification.TaskId, - Title = notification.Title, - Body = notification.Body, - CreationDate = notification.CreationDate, - RevisionDate = notification.RevisionDate, - ReadDate = notificationStatus.ReadDate, - DeletedDate = notificationStatus.DeletedDate - }; - - if (notification.Global) - { - await SendPayloadToInstallationAsync(PushType.NotificationStatus, message, true, notification.ClientType); - } - else if (notification.UserId.HasValue) - { - await SendPayloadToUserAsync(notification.UserId.Value, PushType.NotificationStatus, message, true, - notification.ClientType); - } - else if (notification.OrganizationId.HasValue) - { - await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.NotificationStatus, message, - true, notification.ClientType); - } - else - { - _logger.LogWarning("Invalid notification status id {NotificationId} push notification", notification.Id); - } - } - - public async Task PushSyncOrganizationStatusAsync(Organization organization) - { - var message = new OrganizationStatusPushNotification - { - OrganizationId = organization.Id, - Enabled = organization.Enabled - }; - - await SendPayloadToOrganizationAsync(organization.Id, PushType.SyncOrganizationStatusChanged, message, false); - } - - public async Task PushSyncOrganizationCollectionManagementSettingsAsync(Organization organization) => - await SendPayloadToOrganizationAsync( - organization.Id, - PushType.SyncOrganizationCollectionSettingChanged, - new OrganizationCollectionManagementPushNotification - { - OrganizationId = organization.Id, - LimitCollectionCreation = organization.LimitCollectionCreation, - LimitCollectionDeletion = organization.LimitCollectionDeletion, - LimitItemDeletion = organization.LimitItemDeletion - }, - false - ); - - public async Task PushPendingSecurityTasksAsync(Guid userId) - { - await PushUserAsync(userId, PushType.PendingSecurityTasks); - } - - private async Task SendPayloadToInstallationAsync(PushType type, object payload, bool excludeCurrentContext, - ClientType? clientType = null) - { - var request = new PushSendRequestModel - { - InstallationId = _globalSettings.Installation.Id.ToString(), - Type = type, - Payload = payload, - ClientType = clientType - }; - - await AddCurrentContextAsync(request, excludeCurrentContext); - await SendAsync(HttpMethod.Post, "push/send", request); - } - - 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 SendPayloadToInstallationAsync(string installationId, PushType type, object payload, string? identifier, - string? deviceId = null, ClientType? clientType = null) => - throw new NotImplementedException(); - - 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(); + await SendAsync(HttpMethod.Post, "push/send", payload); } } diff --git a/src/Core/Services/ICollectionService.cs b/src/Core/Services/ICollectionService.cs index c116e5f076..101f3ea23b 100644 --- a/src/Core/Services/ICollectionService.cs +++ b/src/Core/Services/ICollectionService.cs @@ -1,10 +1,8 @@ using Bit.Core.Entities; -using Bit.Core.Models.Data; namespace Bit.Core.Services; public interface ICollectionService { - Task SaveAsync(Collection collection, IEnumerable groups = null, IEnumerable users = null); Task DeleteUserAsync(Collection collection, Guid organizationUserId); } diff --git a/src/Core/Services/IDeviceService.cs b/src/Core/Services/IDeviceService.cs index cd055f8b46..78739e081d 100644 --- a/src/Core/Services/IDeviceService.cs +++ b/src/Core/Services/IDeviceService.cs @@ -6,7 +6,7 @@ namespace Bit.Core.Services; public interface IDeviceService { - Task SaveAsync(WebPushRegistrationData webPush, Device device); + Task SaveAsync(WebPushRegistrationData webPush, Device device, IEnumerable organizationIds); Task SaveAsync(Device device); Task ClearTokenAsync(Device device); Task DeactivateAsync(Device device); diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index 852f2da073..3b828955af 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -2,7 +2,6 @@ using Bit.Core.Entities; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Repositories; namespace Bit.Core.Services; @@ -10,74 +9,26 @@ namespace Bit.Core.Services; public class CollectionService : ICollectionService { private readonly IEventService _eventService; - private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ICollectionRepository _collectionRepository; public CollectionService( IEventService eventService, - IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository) { _eventService = eventService; - _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _collectionRepository = collectionRepository; } - public async Task SaveAsync(Collection collection, IEnumerable? groups = null, - IEnumerable? users = null) - { - var org = await _organizationRepository.GetByIdAsync(collection.OrganizationId); - if (org == null) - { - throw new BadRequestException("Organization not found"); - } - - var groupsList = groups?.ToList(); - var usersList = users?.ToList(); - - // Cannot use Manage with ReadOnly/HidePasswords permissions - var invalidAssociations = groupsList?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); - if (invalidAssociations?.Any() ?? false) - { - throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); - } - - // A collection should always have someone with Can Manage permissions - var groupHasManageAccess = groupsList?.Any(g => g.Manage) ?? false; - var userHasManageAccess = usersList?.Any(u => u.Manage) ?? false; - if (!groupHasManageAccess && !userHasManageAccess && !org.AllowAdminAccessToAllCollectionItems) - { - throw new BadRequestException( - "At least one member or group must have can manage permission."); - } - - if (collection.Id == default(Guid)) - { - if (org.MaxCollections.HasValue) - { - var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(org.Id); - if (org.MaxCollections.Value <= collectionCount) - { - throw new BadRequestException("You have reached the maximum number of collections " + - $"({org.MaxCollections.Value}) for this organization."); - } - } - - await _collectionRepository.CreateAsync(collection, org.UseGroups ? groupsList : null, usersList); - await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created); - } - else - { - await _collectionRepository.ReplaceAsync(collection, org.UseGroups ? groupsList : null, usersList); - await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Updated); - } - } - public async Task DeleteUserAsync(Collection collection, Guid organizationUserId) { + if (collection.Type == Enums.CollectionType.DefaultUserCollection) + { + throw new BadRequestException("You cannot modify member access for collections with the type as DefaultUserCollection."); + } + var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId); if (orgUser == null || orgUser.OrganizationId != collection.OrganizationId) { diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index 165fab0237..931dfccdec 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -29,9 +29,17 @@ public class DeviceService : IDeviceService _globalSettings = globalSettings; } - public async Task SaveAsync(WebPushRegistrationData webPush, Device device) + public async Task SaveAsync(WebPushRegistrationData webPush, Device device, IEnumerable organizationIds) { - await SaveAsync(new PushRegistrationData(webPush.Endpoint, webPush.P256dh, webPush.Auth), device); + await _pushRegistrationService.CreateOrUpdateRegistrationAsync( + new PushRegistrationData(webPush.Endpoint, webPush.P256dh, webPush.Auth), + device.Id.ToString(), + device.UserId.ToString(), + device.Identifier, + device.Type, + organizationIds, + _globalSettings.Installation.Id + ); } public async Task SaveAsync(Device device) diff --git a/src/Core/Services/Implementations/SendGridMailDeliveryService.cs b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs index ea915b56f2..773f87931d 100644 --- a/src/Core/Services/Implementations/SendGridMailDeliveryService.cs +++ b/src/Core/Services/Implementations/SendGridMailDeliveryService.cs @@ -21,7 +21,7 @@ public class SendGridMailDeliveryService : IMailDeliveryService, IDisposable GlobalSettings globalSettings, IWebHostEnvironment hostingEnvironment, ILogger logger) - : this(new SendGridClient(globalSettings.Mail.SendGridApiKey), + : this(new SendGridClient(globalSettings.Mail.SendGridApiKey, globalSettings.Mail.SendGridApiHost), globalSettings, hostingEnvironment, logger) { } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index f08d66c28f..7a794ec3f6 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -431,6 +431,7 @@ public class GlobalSettings : IGlobalSettings public SmtpSettings Smtp { get; set; } = new SmtpSettings(); public string SendGridApiKey { get; set; } public int? SendGridPercentage { get; set; } + public string SendGridApiHost { get; set; } = "https://api.sendgrid.com"; public class SmtpSettings { diff --git a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs index f67a2550d2..829eedc34d 100644 --- a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs +++ b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs @@ -55,11 +55,11 @@ public class ImportCiphersCommand : IImportCiphersCommand Guid importingUserId) { // Make sure the user can save new ciphers to their personal vault - var isPersonalVaultRestricted = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements) - ? (await _policyRequirementQuery.GetAsync(importingUserId)).DisablePersonalOwnership - : await _policyService.AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.PersonalOwnership); + var organizationDataOwnershipEnabled = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements) + ? (await _policyRequirementQuery.GetAsync(importingUserId)).State == OrganizationDataOwnershipState.Enabled + : await _policyService.AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.OrganizationDataOwnership); - if (isPersonalVaultRestricted) + if (organizationDataOwnershipEnabled) { throw new BadRequestException("You cannot import items into your personal vault because you are " + "a member of an organization which forbids it."); diff --git a/src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs b/src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs index 87b4e581ca..804200a05f 100644 --- a/src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs +++ b/src/Core/Tools/SendFeatures/Commands/NonAnonymousSendCommand.cs @@ -8,6 +8,7 @@ using Bit.Core.Tools.Repositories; using Bit.Core.Tools.SendFeatures.Commands.Interfaces; using Bit.Core.Tools.Services; using Bit.Core.Utilities; +using Microsoft.Extensions.Logging; namespace Bit.Core.Tools.SendFeatures.Commands; @@ -18,19 +19,22 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand private readonly IPushNotificationService _pushNotificationService; private readonly ISendValidationService _sendValidationService; private readonly ISendCoreHelperService _sendCoreHelperService; + private readonly ILogger _logger; public NonAnonymousSendCommand(ISendRepository sendRepository, ISendFileStorageService sendFileStorageService, IPushNotificationService pushNotificationService, ISendAuthorizationService sendAuthorizationService, ISendValidationService sendValidationService, - ISendCoreHelperService sendCoreHelperService) + ISendCoreHelperService sendCoreHelperService, + ILogger logger) { _sendRepository = sendRepository; _sendFileStorageService = sendFileStorageService; _pushNotificationService = pushNotificationService; _sendValidationService = sendValidationService; _sendCoreHelperService = sendCoreHelperService; + _logger = logger; } public async Task SaveSendAsync(Send send) @@ -63,6 +67,11 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand throw new BadRequestException("No file data."); } + if (fileLength > SendFileSettingHelper.MAX_FILE_SIZE) + { + throw new BadRequestException($"Max file size is {SendFileSettingHelper.MAX_FILE_SIZE_READABLE}."); + } + var storageBytesRemaining = await _sendValidationService.StorageRemainingForSendAsync(send); if (storageBytesRemaining < fileLength) @@ -77,13 +86,17 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand data.Id = fileId; data.Size = fileLength; data.Validated = false; - send.Data = JsonSerializer.Serialize(data, - JsonHelpers.IgnoreWritingNull); + send.Data = JsonSerializer.Serialize(data, JsonHelpers.IgnoreWritingNull); await SaveSendAsync(send); return await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId); } catch { + _logger.LogWarning( + "Deleted file from {SendId} because an error occurred when creating the upload URL.", + send.Id + ); + // Clean up since this is not transactional await _sendFileStorageService.DeleteFileAsync(send, fileId); throw; @@ -135,23 +148,31 @@ public class NonAnonymousSendCommand : INonAnonymousSendCommand { var fileData = JsonSerializer.Deserialize(send.Data); - var (valid, realSize) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, fileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY); + var minimum = fileData.Size - SendFileSettingHelper.FILE_SIZE_LEEWAY; + var maximum = Math.Min( + fileData.Size + SendFileSettingHelper.FILE_SIZE_LEEWAY, + SendFileSettingHelper.MAX_FILE_SIZE + ); + var (valid, size) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, minimum, maximum); - if (!valid || realSize > SendFileSettingHelper.FILE_SIZE_LEEWAY) + // protect file service from upload hijacking by deleting invalid sends + if (!valid) { - // File reported differs in size from that promised. Must be a rogue client. Delete Send + _logger.LogWarning( + "Deleted {SendId} because its reported size {Size} was outside the expected range ({Minimum} - {Maximum}).", + send.Id, + size, + minimum, + maximum + ); await DeleteSendAsync(send); return false; } - // Update Send data if necessary - if (realSize != fileData.Size) - { - fileData.Size = realSize.Value; - } + // replace expected size with validated size + fileData.Size = size; fileData.Validated = true; - send.Data = JsonSerializer.Serialize(fileData, - JsonHelpers.IgnoreWritingNull); + send.Data = JsonSerializer.Serialize(fileData, JsonHelpers.IgnoreWritingNull); await SaveSendAsync(send); return valid; diff --git a/src/Core/Tools/SendFeatures/Services/AzureSendFileStorageService.cs b/src/Core/Tools/SendFeatures/Services/AzureSendFileStorageService.cs index 09f3be29e7..ee54ffd6b6 100644 --- a/src/Core/Tools/SendFeatures/Services/AzureSendFileStorageService.cs +++ b/src/Core/Tools/SendFeatures/Services/AzureSendFileStorageService.cs @@ -88,7 +88,7 @@ public class AzureSendFileStorageService : ISendFileStorageService return sasUri.ToString(); } - public async Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway) + public async Task<(bool, long)> ValidateFileAsync(Send send, string fileId, long minimum, long maximum) { await InitAsync(); @@ -116,17 +116,14 @@ public class AzureSendFileStorageService : ISendFileStorageService await blobClient.SetHttpHeadersAsync(headers); var length = blobProperties.Value.ContentLength; - if (length < expectedFileSize - leeway || length > expectedFileSize + leeway) - { - return (false, length); - } + var valid = minimum <= length || length <= maximum; - return (true, length); + return (valid, length); } catch (Exception ex) { - _logger.LogError(ex, "Unhandled error in ValidateFileAsync"); - return (false, null); + _logger.LogError(ex, $"A storage operation failed in {nameof(ValidateFileAsync)}"); + return (false, -1); } } diff --git a/src/Core/Tools/SendFeatures/Services/Interfaces/ISendStorageService.cs b/src/Core/Tools/SendFeatures/Services/Interfaces/ISendStorageService.cs index 29bc0c6a6a..8712d07d48 100644 --- a/src/Core/Tools/SendFeatures/Services/Interfaces/ISendStorageService.cs +++ b/src/Core/Tools/SendFeatures/Services/Interfaces/ISendStorageService.cs @@ -56,16 +56,13 @@ public interface ISendFileStorageService /// /// used to help validate file /// File id to identify which file to validate - /// Expected file size of the file - /// - /// Send file size tolerance in bytes. When an uploaded file's `expectedFileSize` - /// is outside of the leeway, the storage operation fails. - /// - /// - /// ❌ Fill this in with an explanation of the error thrown when `leeway` is incorrect - /// - /// Task object for async operations with Tuple of boolean that determines if file was valid and long that - /// the actual file size of the file. + /// The minimum allowed length of the stored file in bytes. + /// The maximuim allowed length of the stored file in bytes + /// + /// A task that completes when validation is finished. The first element of the tuple is + /// when validation succeeded, and false otherwise. The second element + /// of the tuple contains the observed file length in bytes. If an error occurs during validation, + /// this returns `-1`. /// - Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway); + Task<(bool valid, long length)> ValidateFileAsync(Send send, string fileId, long minimum, long maximum); } diff --git a/src/Core/Tools/SendFeatures/Services/LocalSendStorageService.cs b/src/Core/Tools/SendFeatures/Services/LocalSendStorageService.cs index c205028d9e..a6b3fb0faf 100644 --- a/src/Core/Tools/SendFeatures/Services/LocalSendStorageService.cs +++ b/src/Core/Tools/SendFeatures/Services/LocalSendStorageService.cs @@ -85,9 +85,9 @@ public class LocalSendStorageService : ISendFileStorageService public Task GetSendFileUploadUrlAsync(Send send, string fileId) => Task.FromResult($"/sends/{send.Id}/file/{fileId}"); - public Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway) + public Task<(bool, long)> ValidateFileAsync(Send send, string fileId, long minimum, long maximum) { - long? length = null; + long length = -1; var path = FilePath(send, fileId); if (!File.Exists(path)) { @@ -95,11 +95,7 @@ public class LocalSendStorageService : ISendFileStorageService } length = new FileInfo(path).Length; - if (expectedFileSize < length - leeway || expectedFileSize > length + leeway) - { - return Task.FromResult((false, length)); - } - - return Task.FromResult((true, length)); + var valid = minimum < length || length < maximum; + return Task.FromResult((valid, length)); } } diff --git a/src/Core/Tools/Services/NoopImplementations/NoopSendFileStorageService.cs b/src/Core/Tools/Services/NoopImplementations/NoopSendFileStorageService.cs index 4fb841e7a3..16c20e521e 100644 --- a/src/Core/Tools/Services/NoopImplementations/NoopSendFileStorageService.cs +++ b/src/Core/Tools/Services/NoopImplementations/NoopSendFileStorageService.cs @@ -37,8 +37,8 @@ public class NoopSendFileStorageService : ISendFileStorageService return Task.FromResult((string)null); } - public Task<(bool, long?)> ValidateFileAsync(Send send, string fileId, long expectedFileSize, long leeway) + public Task<(bool, long)> ValidateFileAsync(Send send, string fileId, long minimum, long maximum) { - return Task.FromResult((false, default(long?))); + return Task.FromResult((false, -1L)); } } diff --git a/src/Core/Vault/Commands/Interfaces/IMarkNotificationsForTaskAsDeletedCommand.cs b/src/Core/Vault/Commands/Interfaces/IMarkNotificationsForTaskAsDeletedCommand.cs new file mode 100644 index 0000000000..90566b4d83 --- /dev/null +++ b/src/Core/Vault/Commands/Interfaces/IMarkNotificationsForTaskAsDeletedCommand.cs @@ -0,0 +1,11 @@ +namespace Bit.Core.Vault.Commands.Interfaces; + +public interface IMarkNotificationsForTaskAsDeletedCommand +{ + /// + /// Marks notifications associated with a given taskId as deleted. + /// + /// The unique identifier of the task to complete + /// A task representing the async operation + Task MarkAsDeletedAsync(Guid taskId); +} diff --git a/src/Core/Vault/Commands/MarkNotificationsForTaskAsDeletedCommand.cs b/src/Core/Vault/Commands/MarkNotificationsForTaskAsDeletedCommand.cs new file mode 100644 index 0000000000..8d1e6e4538 --- /dev/null +++ b/src/Core/Vault/Commands/MarkNotificationsForTaskAsDeletedCommand.cs @@ -0,0 +1,32 @@ +using Bit.Core.NotificationCenter.Repositories; +using Bit.Core.Platform.Push; +using Bit.Core.Vault.Commands.Interfaces; + +namespace Bit.Core.Vault.Commands; + +public class MarkNotificationsForTaskAsDeletedCommand : IMarkNotificationsForTaskAsDeletedCommand +{ + private readonly INotificationRepository _notificationRepository; + private readonly IPushNotificationService _pushNotificationService; + + public MarkNotificationsForTaskAsDeletedCommand( + INotificationRepository notificationRepository, + IPushNotificationService pushNotificationService) + { + _notificationRepository = notificationRepository; + _pushNotificationService = pushNotificationService; + + } + + public async Task MarkAsDeletedAsync(Guid taskId) + { + var userIds = await _notificationRepository.MarkNotificationsAsDeletedByTask(taskId); + + // For each user associated with the notifications, send a push notification so local tasks can be updated. + var uniqueUserIds = userIds.Distinct(); + foreach (var id in uniqueUserIds) + { + await _pushNotificationService.PushPendingSecurityTasksAsync(id); + } + } +} diff --git a/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs b/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs index 77b8a8625c..8a12910bb8 100644 --- a/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs +++ b/src/Core/Vault/Commands/MarkTaskAsCompletedCommand.cs @@ -14,15 +14,19 @@ public class MarkTaskAsCompletedCommand : IMarkTaskAsCompleteCommand private readonly ISecurityTaskRepository _securityTaskRepository; private readonly IAuthorizationService _authorizationService; private readonly ICurrentContext _currentContext; + private readonly IMarkNotificationsForTaskAsDeletedCommand _markNotificationsForTaskAsDeletedAsync; + public MarkTaskAsCompletedCommand( ISecurityTaskRepository securityTaskRepository, IAuthorizationService authorizationService, - ICurrentContext currentContext) + ICurrentContext currentContext, + IMarkNotificationsForTaskAsDeletedCommand markNotificationsForTaskAsDeletedAsync) { _securityTaskRepository = securityTaskRepository; _authorizationService = authorizationService; _currentContext = currentContext; + _markNotificationsForTaskAsDeletedAsync = markNotificationsForTaskAsDeletedAsync; } /// @@ -46,5 +50,8 @@ public class MarkTaskAsCompletedCommand : IMarkTaskAsCompleteCommand task.RevisionDate = DateTime.UtcNow; await _securityTaskRepository.ReplaceAsync(task); + + // Mark all notifications related to this task as deleted + await _markNotificationsForTaskAsDeletedAsync.MarkAsDeletedAsync(taskId); } } diff --git a/src/Core/Vault/Repositories/ICipherRepository.cs b/src/Core/Vault/Repositories/ICipherRepository.cs index 46742c6aa3..5a04a6651d 100644 --- a/src/Core/Vault/Repositories/ICipherRepository.cs +++ b/src/Core/Vault/Repositories/ICipherRepository.cs @@ -55,7 +55,7 @@ public interface ICipherRepository : IRepository Guid userId); /// - /// Returns the users and the cipher ids for security tawsks that are applicable to them. + /// Returns the users and the cipher ids for security tasks that are applicable to them. /// /// Security tasks are actionable when a user has manage access to the associated cipher. /// diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index 5d17441024..42221adf4b 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -142,11 +142,11 @@ public class CipherService : ICipherService } else { - var isPersonalVaultRestricted = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements) - ? (await _policyRequirementQuery.GetAsync(savingUserId)).DisablePersonalOwnership - : await _policyService.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership); + var organizationDataOwnershipEnabled = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements) + ? (await _policyRequirementQuery.GetAsync(savingUserId)).State == OrganizationDataOwnershipState.Enabled + : await _policyService.AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.OrganizationDataOwnership); - if (isPersonalVaultRestricted) + if (organizationDataOwnershipEnabled) { throw new BadRequestException("Due to an Enterprise Policy, you are restricted from saving items to your personal vault."); } diff --git a/src/Core/Vault/VaultServiceCollectionExtensions.cs b/src/Core/Vault/VaultServiceCollectionExtensions.cs index 1f361cb613..9efa1ea379 100644 --- a/src/Core/Vault/VaultServiceCollectionExtensions.cs +++ b/src/Core/Vault/VaultServiceCollectionExtensions.cs @@ -24,5 +24,6 @@ public static class VaultServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Events/Dockerfile b/src/Events/Dockerfile index 58fbbe3df1..3a6342ef7a 100644 --- a/src/Events/Dockerfile +++ b/src/Events/Dockerfile @@ -50,6 +50,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ gosu \ curl \ + krb5-user \ && rm -rf /var/lib/apt/lists/* # Copy app from the build stage diff --git a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs index 5a6fcbe4aa..feecf4a5d1 100644 --- a/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs @@ -3,6 +3,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; +using Bit.Core.AdminConsole.Utilities.DebuggingInstruments; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.KeyManagement.UserKey; @@ -12,6 +13,7 @@ using Bit.Core.Repositories; using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Logging; #nullable enable @@ -25,8 +27,9 @@ public class OrganizationUserRepository : Repository, IO /// https://github.com/dotnet/SqlClient/issues/54 /// private string _marsConnectionString; + private readonly ILogger _logger; - public OrganizationUserRepository(GlobalSettings globalSettings) + public OrganizationUserRepository(GlobalSettings globalSettings, ILogger logger) : base(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) { var builder = new SqlConnectionStringBuilder(ConnectionString) @@ -34,6 +37,7 @@ public class OrganizationUserRepository : Repository, IO MultipleActiveResultSets = true, }; _marsConnectionString = builder.ToString(); + _logger = logger; } public async Task GetCountByOrganizationIdAsync(Guid organizationId) @@ -305,6 +309,8 @@ public class OrganizationUserRepository : Repository, IO public async Task CreateAsync(OrganizationUser obj, IEnumerable collections) { + _logger.LogUserInviteStateDiagnostics(obj); + obj.SetNewId(); var objWithCollections = JsonSerializer.Deserialize( JsonSerializer.Serialize(obj))!; @@ -323,6 +329,8 @@ public class OrganizationUserRepository : Repository, IO public async Task ReplaceAsync(OrganizationUser obj, IEnumerable collections) { + _logger.LogUserInviteStateDiagnostics(obj); + var objWithCollections = JsonSerializer.Deserialize( JsonSerializer.Serialize(obj))!; objWithCollections.Collections = collections.ToArrayTVP(); @@ -406,6 +414,8 @@ public class OrganizationUserRepository : Repository, IO public async Task?> CreateManyAsync(IEnumerable organizationUsers) { + _logger.LogUserInviteStateDiagnostics(organizationUsers); + organizationUsers = organizationUsers.ToList(); if (!organizationUsers.Any()) { @@ -430,6 +440,8 @@ public class OrganizationUserRepository : Repository, IO public async Task ReplaceManyAsync(IEnumerable organizationUsers) { + _logger.LogUserInviteStateDiagnostics(organizationUsers); + organizationUsers = organizationUsers.ToList(); if (!organizationUsers.Any()) { diff --git a/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs index db6419d389..c9cf796986 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs @@ -14,13 +14,12 @@ namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class AuthRequestRepository : Repository, IAuthRequestRepository { + private readonly GlobalSettings _globalSettings; public AuthRequestRepository(GlobalSettings globalSettings) - : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) - { } - - public AuthRequestRepository(string connectionString, string readOnlyConnectionString) - : base(connectionString, readOnlyConnectionString) - { } + : base(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + _globalSettings = globalSettings; + } public async Task DeleteExpiredAsync( TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration) @@ -52,6 +51,18 @@ public class AuthRequestRepository : Repository, IAuthRequest } } + public async Task> GetManyPendingAuthRequestByUserId(Guid userId) + { + var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes; + using var connection = new SqlConnection(ConnectionString); + var results = await connection.QueryAsync( + $"[{Schema}].[AuthRequest_ReadPendingByUserId]", + new { UserId = userId, ExpirationMinutes = expirationMinutes }, + commandType: CommandType.StoredProcedure); + + return results; + } + public async Task> GetManyPendingByOrganizationIdAsync(Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index a95c2bd4c6..00728ec1a0 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Providers.Repositories; using Bit.Core.Billing.Repositories; using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; using Bit.Core.Platform.Installations; @@ -70,6 +71,9 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs b/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs new file mode 100644 index 0000000000..9f1d4d1172 --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/OrganizationApplicationRepository.cs @@ -0,0 +1,35 @@ +using System.Data; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class OrganizationApplicationRepository : Repository, IOrganizationApplicationRepository +{ + public OrganizationApplicationRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public OrganizationApplicationRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { + } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationApplication_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } +} diff --git a/src/Infrastructure.Dapper/Dirt/OrganizationMemberBaseDetailRepository.cs b/src/Infrastructure.Dapper/Dirt/OrganizationMemberBaseDetailRepository.cs new file mode 100644 index 0000000000..458e72f996 --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/OrganizationMemberBaseDetailRepository.cs @@ -0,0 +1,39 @@ +using System.Data; +using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class OrganizationMemberBaseDetailRepository : BaseRepository, IOrganizationMemberBaseDetailRepository +{ + public OrganizationMemberBaseDetailRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public OrganizationMemberBaseDetailRepository(string connectionString, string readOnlyConnectionString) : base( + connectionString, readOnlyConnectionString) + { + } + + public async Task> GetOrganizationMemberBaseDetailsByOrganizationId( + Guid organizationId) + { + await using var connection = new SqlConnection(ConnectionString); + + + var result = await connection.QueryAsync( + "[dbo].[MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId]", + new + { + OrganizationId = organizationId + + }, commandType: CommandType.StoredProcedure); + + return result; + } +} diff --git a/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs b/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs new file mode 100644 index 0000000000..7a5fe1c8c2 --- /dev/null +++ b/src/Infrastructure.Dapper/Dirt/OrganizationReportRepository.cs @@ -0,0 +1,45 @@ +using System.Data; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Dirt; + +public class OrganizationReportRepository : Repository, IOrganizationReportRepository +{ + public OrganizationReportRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { + } + + public OrganizationReportRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { + } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationReport_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task GetLatestByOrganizationIdAsync(Guid organizationId) + { + return await GetByOrganizationIdAsync(organizationId) + .ContinueWith(task => + { + var reports = task.Result; + return reports.OrderByDescending(r => r.CreationDate).FirstOrDefault(); + }); + } +} diff --git a/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs b/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs index 2445de8a9e..266386433f 100644 --- a/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs +++ b/src/Infrastructure.Dapper/Dirt/PasswordHealthReportApplicationRepository.cs @@ -1,6 +1,6 @@ using System.Data; -using Bit.Core.Dirt.Reports.Entities; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; using Bit.Core.Settings; using Bit.Infrastructure.Dapper.Repositories; using Dapper; diff --git a/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs b/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs index b6843d9801..63b1c21f49 100644 --- a/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs +++ b/src/Infrastructure.Dapper/NotificationCenter/Repositories/NotificationRepository.cs @@ -56,4 +56,21 @@ public class NotificationRepository : Repository, INotificat ContinuationToken = data.Count < pageOptions.PageSize ? null : (pageNumber + 1).ToString() }; } + + public async Task> MarkNotificationsAsDeletedByTask(Guid taskId) + { + await using var connection = new SqlConnection(ConnectionString); + + var results = await connection.QueryAsync( + "[dbo].[Notification_MarkAsDeletedByTask]", + new + { + TaskId = taskId, + }, + commandType: CommandType.StoredProcedure); + + var data = results.ToList(); + + return data; + } } diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index e0a89b1685..9c75295f3f 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -98,7 +98,10 @@ public class CipherRepository : Repository, ICipherRepository return results .GroupBy(c => c.Id) - .Select(g => g.OrderByDescending(og => og.Edit).ThenByDescending(og => og.ViewPassword).First()) + .Select(g => + g.OrderByDescending(og => og.Manage) + .ThenByDescending(og => og.Edit) + .ThenByDescending(og => og.ViewPassword).First()) .ToList(); } } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs index 1a54d6588a..c816b01a01 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrganizationIdIntegrationTypeQuery.cs @@ -31,6 +31,7 @@ public class OrganizationIntegrationConfigurationDetailsReadManyByEventTypeOrgan IntegrationType = oi.Type, EventType = oic.EventType, Configuration = oic.Configuration, + Filters = oic.Filters, IntegrationConfiguration = oi.Configuration, Template = oic.Template }; diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/AuthRequestRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/AuthRequestRepository.cs index 7dee40a9e6..23cdb60dcd 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/AuthRequestRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/AuthRequestRepository.cs @@ -3,6 +3,7 @@ using AutoMapper.QueryableExtensions; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; using Bit.Core.Repositories; +using Bit.Core.Settings; using Bit.Infrastructure.EntityFramework.Auth.Models; using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; @@ -14,9 +15,13 @@ namespace Bit.Infrastructure.EntityFramework.Auth.Repositories; public class AuthRequestRepository : Repository, IAuthRequestRepository { - public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) - : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.AuthRequests) - { } + private readonly IGlobalSettings _globalSettings; + public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper, IGlobalSettings globalSettings) + : base(serviceScopeFactory, mapper, context => context.AuthRequests) + { + _globalSettings = globalSettings; + } + public async Task DeleteExpiredAsync( TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration) { @@ -57,6 +62,32 @@ public class AuthRequestRepository : Repository> GetManyPendingAuthRequestByUserId(Guid userId) + { + var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes; + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var mostRecentAuthRequests = await + (from authRequest in dbContext.AuthRequests + where authRequest.Type == AuthRequestType.AuthenticateAndUnlock + || authRequest.Type == AuthRequestType.Unlock + where authRequest.UserId == userId + where authRequest.CreationDate.AddMinutes(expirationMinutes) >= DateTime.UtcNow + group authRequest by authRequest.RequestDeviceIdentifier into groupedAuthRequests + select + (from r in groupedAuthRequests + join d in dbContext.Devices on new { r.RequestDeviceIdentifier, r.UserId } + equals new { RequestDeviceIdentifier = d.Identifier, d.UserId } into deviceJoin + from dj in deviceJoin.DefaultIfEmpty() // This creates a left join allowing null for devices + orderby r.CreationDate descending + select new PendingAuthRequestDetails(r, dj.Id)).First() + ).ToListAsync(); + + mostRecentAuthRequests.RemoveAll(a => a.Approved != null); + + return mostRecentAuthRequests; + } + public async Task> GetManyAdminApprovalRequestsByManyIdsAsync( Guid organizationId, IEnumerable ids) diff --git a/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs new file mode 100644 index 0000000000..bf0d7d011a --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationApplicationEntityTypeConfiguration.cs @@ -0,0 +1,30 @@ +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Configurations; + +public class OrganizationApplicationEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(s => s.Id) + .ValueGeneratedNever(); + + builder.HasIndex(s => s.Id) + .IsClustered(true); + + builder + .HasIndex(s => s.OrganizationId) + .IsClustered(false); + + builder + .HasOne(s => s.Organization) + .WithMany() + .HasForeignKey(s => s.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + builder.ToTable(nameof(OrganizationApplication)); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs new file mode 100644 index 0000000000..99794d11a2 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Configurations/OrganizationReportEntityTypeConfiguration.cs @@ -0,0 +1,30 @@ +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Configurations; + +public class OrganizationReportEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .Property(s => s.Id) + .ValueGeneratedNever(); + + builder.HasIndex(s => s.Id) + .IsClustered(true); + + builder + .HasIndex(s => s.OrganizationId) + .IsClustered(false); + + builder + .HasOne(s => s.Organization) + .WithMany() + .HasForeignKey(s => s.OrganizationId) + .OnDelete(DeleteBehavior.Cascade); + + builder.ToTable(nameof(OrganizationReport)); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs new file mode 100644 index 0000000000..9aaec0af2c --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationApplication.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; +public class OrganizationApplication : Core.Dirt.Entities.OrganizationApplication +{ + public virtual Organization Organization { get; set; } +} + +public class OrganizationApplicationProfile : Profile +{ + public OrganizationApplicationProfile() + { + CreateMap() + .ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs new file mode 100644 index 0000000000..a7d08e142f --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Models/OrganizationReport.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Bit.Infrastructure.EntityFramework.AdminConsole.Models; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Models; +public class OrganizationReport : Core.Dirt.Entities.OrganizationReport +{ + public virtual Organization Organization { get; set; } +} + +public class OrganizationReportProfile : Profile +{ + public OrganizationReportProfile() + { + CreateMap() + .ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs b/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs index 222bfbeb65..bc471f0844 100644 --- a/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Models/PasswordHealthReportApplication.cs @@ -3,7 +3,7 @@ using Bit.Infrastructure.EntityFramework.AdminConsole.Models; namespace Bit.Infrastructure.EntityFramework.Dirt.Models; -public class PasswordHealthReportApplication : Core.Dirt.Reports.Entities.PasswordHealthReportApplication +public class PasswordHealthReportApplication : Core.Dirt.Entities.PasswordHealthReportApplication { public virtual Organization Organization { get; set; } } @@ -12,7 +12,7 @@ public class PasswordHealthReportApplicationProfile : Profile { public PasswordHealthReportApplicationProfile() { - CreateMap() + CreateMap() .ReverseMap(); } } diff --git a/src/Infrastructure.EntityFramework/Dirt/OrganizationMemberBaseDetailRepository.cs b/src/Infrastructure.EntityFramework/Dirt/OrganizationMemberBaseDetailRepository.cs new file mode 100644 index 0000000000..123379da90 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/OrganizationMemberBaseDetailRepository.cs @@ -0,0 +1,32 @@ +using AutoMapper; +using Bit.Core.Dirt.Reports.Models.Data; +using Bit.Core.Dirt.Reports.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Dirt; + +public class OrganizationMemberBaseDetailRepository : BaseEntityFrameworkRepository, IOrganizationMemberBaseDetailRepository +{ + public OrganizationMemberBaseDetailRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base( + serviceScopeFactory, + mapper) + { + } + + public async Task> GetOrganizationMemberBaseDetailsByOrganizationId( + Guid organizationId) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + + var result = await dbContext.Set() + .FromSqlRaw("EXEC [dbo].[MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId] @OrganizationId", + new SqlParameter("@OrganizationId", organizationId)) + .ToListAsync(); + + return result; + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs new file mode 100644 index 0000000000..f408ed85ba --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationApplicationRepository.cs @@ -0,0 +1,29 @@ +using AutoMapper; +using Bit.Core.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Dirt.Models; +using Bit.Infrastructure.EntityFramework.Repositories; +using LinqToDB; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; + +public class OrganizationApplicationRepository : + Repository, + IOrganizationApplicationRepository +{ + public OrganizationApplicationRepository(IServiceScopeFactory serviceScopeFactory, + IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationApplications) + { } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.OrganizationApplications + .Where(p => p.OrganizationId == organizationId) + .ToListAsync(); + return Mapper.Map>(results); + } + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs new file mode 100644 index 0000000000..416fd91933 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/OrganizationReportRepository.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using LinqToDB; +using Microsoft.Extensions.DependencyInjection; + + +namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; + +public class OrganizationReportRepository : + Repository, + IOrganizationReportRepository +{ + public OrganizationReportRepository(IServiceScopeFactory serviceScopeFactory, + IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationReports) + { } + + public async Task> GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var results = await dbContext.OrganizationReports + .Where(p => p.OrganizationId == organizationId) + .ToListAsync(); + return Mapper.Map>(results); + } + } + + public async Task GetLatestByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var result = await dbContext.OrganizationReports + .Where(p => p.OrganizationId == organizationId) + .OrderByDescending(p => p.Date) + .Take(1) + .FirstOrDefaultAsync(); + + if (result == null) + return default; + + return Mapper.Map(result); + } + } +} diff --git a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs index 604a0d87a1..296a542a8b 100644 --- a/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs +++ b/src/Infrastructure.EntityFramework/Dirt/Repositories/PasswordHealthReportApplicationRepository.cs @@ -1,5 +1,5 @@ using AutoMapper; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Infrastructure.EntityFramework.Dirt.Models; using Bit.Infrastructure.EntityFramework.Repositories; using LinqToDB; @@ -8,14 +8,14 @@ using Microsoft.Extensions.DependencyInjection; namespace Bit.Infrastructure.EntityFramework.Dirt.Repositories; public class PasswordHealthReportApplicationRepository : - Repository, + Repository, IPasswordHealthReportApplicationRepository { public PasswordHealthReportApplicationRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PasswordHealthReportApplications) { } - public async Task> GetByOrganizationIdAsync(Guid organizationId) + public async Task> GetByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -23,7 +23,7 @@ public class PasswordHealthReportApplicationRepository : var results = await dbContext.PasswordHealthReportApplications .Where(p => p.OrganizationId == organizationId) .ToListAsync(); - return Mapper.Map>(results); + return Mapper.Map>(results); } } } diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 321c4c90e5..aab83ef7a8 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Billing.Providers.Repositories; using Bit.Core.Billing.Repositories; using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Enums; using Bit.Core.KeyManagement.Repositories; using Bit.Core.NotificationCenter.Repositories; @@ -14,6 +15,7 @@ using Bit.Core.Vault.Repositories; using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Infrastructure.EntityFramework.Auth.Repositories; using Bit.Infrastructure.EntityFramework.Billing.Repositories; +using Bit.Infrastructure.EntityFramework.Dirt; using Bit.Infrastructure.EntityFramework.Dirt.Repositories; using Bit.Infrastructure.EntityFramework.KeyManagement.Repositories; using Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories; @@ -107,6 +109,9 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationStatusEntityTypeConfiguration.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationStatusEntityTypeConfiguration.cs index 1207d839d8..c535cd3e84 100644 --- a/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationStatusEntityTypeConfiguration.cs +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Configurations/NotificationStatusEntityTypeConfiguration.cs @@ -13,6 +13,12 @@ public class NotificationStatusEntityTypeConfiguration : IEntityTypeConfiguratio .HasKey(ns => new { ns.UserId, ns.NotificationId }) .IsClustered(); + builder + .HasOne(ns => ns.Notification) + .WithMany() + .HasForeignKey(ns => ns.NotificationId) + .OnDelete(DeleteBehavior.Cascade); + builder.ToTable(nameof(NotificationStatus)); } } diff --git a/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs index 5d1071f26c..213a14a81d 100644 --- a/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs +++ b/src/Infrastructure.EntityFramework/NotificationCenter/Repositories/NotificationRepository.cs @@ -74,4 +74,53 @@ public class NotificationRepository : Repository> MarkNotificationsAsDeletedByTask(Guid taskId) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + + var notifications = await dbContext.Notifications + .Where(n => n.TaskId == taskId) + .ToListAsync(); + + var notificationIds = notifications.Select(n => n.Id).ToList(); + + var statuses = await dbContext.Set() + .Where(ns => notificationIds.Contains(ns.NotificationId)) + .ToListAsync(); + + var now = DateTime.UtcNow; + + // Update existing statuses and add missing ones + foreach (var notification in notifications) + { + var status = statuses.FirstOrDefault(s => s.NotificationId == notification.Id); + if (status != null) + { + if (status.DeletedDate == null) + { + status.DeletedDate = now; + } + } + else if (notification.UserId.HasValue) + { + dbContext.Set().Add(new NotificationStatus + { + NotificationId = notification.Id, + UserId = (Guid)notification.UserId, + DeletedDate = now + }); + } + } + + await dbContext.SaveChangesAsync(); + + var userIds = notifications + .Select(n => n.UserId) + .Where(u => u.HasValue) + .ToList(); + + return (IEnumerable)userIds; + } } diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 647b3e3ab1..7446abdd97 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -1,4 +1,5 @@ using Bit.Core; +using Bit.Core.Dirt.Reports.Models.Data; using Bit.Infrastructure.EntityFramework.AdminConsole.Models; using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider; using Bit.Infrastructure.EntityFramework.Auth.Models; @@ -15,6 +16,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using DP = Microsoft.AspNetCore.DataProtection; + #nullable enable namespace Bit.Infrastructure.EntityFramework.Repositories; @@ -80,8 +82,11 @@ public class DatabaseContext : DbContext public DbSet NotificationStatuses { get; set; } public DbSet ClientOrganizationMigrationRecords { get; set; } public DbSet PasswordHealthReportApplications { get; set; } + public DbSet OrganizationMemberBaseDetails { get; set; } public DbSet SecurityTasks { get; set; } public DbSet OrganizationInstallations { get; set; } + public DbSet OrganizationReports { get; set; } + public DbSet OrganizationApplications { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -112,6 +117,7 @@ public class DatabaseContext : DbContext var eOrganizationConnection = builder.Entity(); var eOrganizationDomain = builder.Entity(); var aWebAuthnCredential = builder.Entity(); + var eOrganizationMemberBaseDetail = builder.Entity(); // Shadow property configurations go here @@ -134,6 +140,8 @@ public class DatabaseContext : DbContext eCollectionGroup.HasKey(cg => new { cg.CollectionId, cg.GroupId }); eGroupUser.HasKey(gu => new { gu.GroupId, gu.OrganizationUserId }); + eOrganizationMemberBaseDetail.HasNoKey(); + var dataProtector = this.GetService().CreateProtector( Constants.DatabaseFieldProtectorPurpose); var dataProtectionConverter = new DataProtectionConverter(dataProtector); diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index befb835e26..a560e8e107 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -457,7 +457,7 @@ public class CipherRepository : Repository cipherDetailsView = withOrganizations ? + var cipherDetailsView = withOrganizations ? new UserCipherDetailsQuery(userId).Run(dbContext) : new CipherDetailsQuery(userId).Run(dbContext); if (!withOrganizations) @@ -485,8 +485,15 @@ public class CipherRepository : Repository c.Id) + .Select(g => g.OrderByDescending(c => c.Manage) + .ThenByDescending(c => c.Edit) + .ThenByDescending(c => c.ViewPassword) + .First()) + .ToList(); } } diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 5451400803..1c4473674c 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates; using AspNetCoreRateLimit; using Azure.Storage.Queues; using Bit.Core.AdminConsole.Models.Business.Tokenables; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services.Implementations; @@ -288,7 +288,7 @@ public static class ServiceCollectionExtensions if (CoreHelpers.SettingHasValue(globalSettings.PushRelayBaseUri) && CoreHelpers.SettingHasValue(globalSettings.Installation.Key)) { - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.AddSingleton(); } else @@ -299,20 +299,20 @@ public static class ServiceCollectionExtensions if (CoreHelpers.SettingHasValue(globalSettings.InternalIdentityKey) && CoreHelpers.SettingHasValue(globalSettings.BaseServiceUri.InternalNotifications)) { - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } } else { services.AddSingleton(); services.AddSingleton(); - services.AddKeyedSingleton("implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddSingleton(); if (CoreHelpers.SettingHasValue(globalSettings.Notifications?.ConnectionString)) { services.AddKeyedSingleton("notifications", (_, _) => new QueueClient(globalSettings.Notifications.ConnectionString, "notifications")); - services.AddKeyedSingleton( - "implementation"); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } } @@ -366,7 +366,6 @@ public static class ServiceCollectionExtensions { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -615,9 +614,11 @@ public static class ServiceCollectionExtensions new EventIntegrationHandler( integrationType, provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService())); + provider.GetRequiredService(), + provider.GetRequiredService>>())); services.AddSingleton(provider => new AzureServiceBusEventListenerService( @@ -648,6 +649,7 @@ public static class ServiceCollectionExtensions !CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.EventTopicName)) return services; + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddAzureServiceBusEventRepositoryListener(globalSettings); @@ -698,9 +700,11 @@ public static class ServiceCollectionExtensions new EventIntegrationHandler( integrationType, provider.GetRequiredService(), + provider.GetRequiredService(), provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService())); + provider.GetRequiredService(), + provider.GetRequiredService>>())); services.AddSingleton(provider => new RabbitMqEventListenerService( @@ -718,7 +722,8 @@ public static class ServiceCollectionExtensions retryQueueName: integrationRetryQueueName, maxRetries: maxRetries, rabbitMqService: provider.GetRequiredService(), - logger: provider.GetRequiredService>())); + logger: provider.GetRequiredService>(), + timeProvider: provider.GetRequiredService())); return services; } @@ -730,6 +735,7 @@ public static class ServiceCollectionExtensions return services; } + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddRabbitMqEventRepositoryListener(globalSettings); diff --git a/src/Sql/Dirt/Stored Procedure/MemberAccessDetail_GetMemberAccessDetailByOrganizationId.sql b/src/Sql/Dirt/Stored Procedure/MemberAccessDetail_GetMemberAccessDetailByOrganizationId.sql new file mode 100644 index 0000000000..1aaf667f6a --- /dev/null +++ b/src/Sql/Dirt/Stored Procedure/MemberAccessDetail_GetMemberAccessDetailByOrganizationId.sql @@ -0,0 +1,92 @@ +CREATE PROCEDURE dbo.MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + +IF @OrganizationId IS NULL + THROW 50000, 'OrganizationId cannot be null', 1; + + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + NULL AS GroupId, + NULL AS GroupName, + CU.ReadOnly, + CU.HidePasswords, + CU.Manage, + Cipher.Id AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.CollectionUser CU ON CU.OrganizationUserId = OU.Id + INNER JOIN dbo.Collection C ON C.Id = CU.CollectionId and C.OrganizationId = @OrganizationId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId AND Cipher.OrganizationId = @OrganizationId + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND Cipher.DeletedDate IS NULL +UNION ALL + -- Group-based collection permissions + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + G.Id AS GroupId, + G.Name AS GroupName, + CG.ReadOnly, + CG.HidePasswords, + CG.Manage, + Cipher.Id AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.GroupUser GU ON GU.OrganizationUserId = OU.Id + INNER JOIN dbo.[Group] G ON G.Id = GU.GroupId + INNER JOIN dbo.CollectionGroup CG ON CG.GroupId = G.Id + INNER JOIN dbo.Collection C ON C.Id = CG.CollectionId AND C.OrganizationId = @OrganizationId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId and Cipher.OrganizationId = @OrganizationId + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND Cipher.DeletedDate IS NULL +UNION ALL + -- Users without collection access (invited users) + -- typically invited users who have not yet accepted the invitation + -- and not yet assigned to any collection + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + null as CollectionId, + null AS CollectionName, + NULL AS GroupId, + NULL AS GroupName, + null as [ReadOnly], + null as HidePasswords, + null as Manage, + null AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId AND O.Id = @OrganizationId AND O.Enabled = 1 + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND OU.Id not in ( + select OU1.Id from dbo.OrganizationUser OU1 + inner join dbo.CollectionUser CU1 on CU1.OrganizationUserId = OU1.Id + WHERE OU1.OrganizationId = @organizationId + ) diff --git a/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_MarkAsDeletedByTask.sql b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_MarkAsDeletedByTask.sql new file mode 100644 index 0000000000..a2c16079f7 --- /dev/null +++ b/src/Sql/NotificationCenter/dbo/Stored Procedures/Notification_MarkAsDeletedByTask.sql @@ -0,0 +1,35 @@ +CREATE PROCEDURE [dbo].[Notification_MarkAsDeletedByTask] + @TaskId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON; + + -- Collect UserIds as they are altered + DECLARE @UserIdsForAlteredNotifications TABLE ( + UserId UNIQUEIDENTIFIER + ); + + -- Update existing NotificationStatus as deleted + UPDATE ns + SET ns.DeletedDate = GETUTCDATE() + OUTPUT inserted.UserId INTO @UserIdsForAlteredNotifications + FROM NotificationStatus ns + INNER JOIN Notification n ON ns.NotificationId = n.Id + WHERE n.TaskId = @TaskId + AND ns.DeletedDate IS NULL; + + -- Insert NotificationStatus records for notifications that don't have one yet + INSERT INTO NotificationStatus (NotificationId, UserId, DeletedDate) + OUTPUT inserted.UserId INTO @UserIdsForAlteredNotifications + SELECT n.Id, n.UserId, GETUTCDATE() + FROM Notification n + LEFT JOIN NotificationStatus ns + ON n.Id = ns.NotificationId + WHERE n.TaskId = @TaskId + AND ns.NotificationId IS NULL; + + -- Return the UserIds associated with the altered notifications + SELECT u.UserId + FROM @UserIdsForAlteredNotifications u; +END +GO diff --git a/src/Sql/dbo/Auth/Stored Procedures/AuthRequest_ReadPendingByUserId.sql b/src/Sql/dbo/Auth/Stored Procedures/AuthRequest_ReadPendingByUserId.sql new file mode 100644 index 0000000000..4c3217812a --- /dev/null +++ b/src/Sql/dbo/Auth/Stored Procedures/AuthRequest_ReadPendingByUserId.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[AuthRequest_ReadPendingByUserId] + @UserId UNIQUEIDENTIFIER, + @ExpirationMinutes INT +AS +BEGIN + SET NOCOUNT ON + + SELECT * + FROM [dbo].[AuthRequestPendingDetailsView] + WHERE [UserId] = @UserId + AND [CreationDate] >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) +END diff --git a/src/Sql/dbo/Auth/Views/AuthRequestPendingDetailsView.sql b/src/Sql/dbo/Auth/Views/AuthRequestPendingDetailsView.sql new file mode 100644 index 0000000000..d0433bca09 --- /dev/null +++ b/src/Sql/dbo/Auth/Views/AuthRequestPendingDetailsView.sql @@ -0,0 +1,38 @@ +CREATE VIEW [dbo].[AuthRequestPendingDetailsView] +AS + WITH + PendingRequests + AS + ( + SELECT + [AR].*, + [D].[Id] AS [DeviceId], + ROW_NUMBER() OVER (PARTITION BY [AR].[RequestDeviceIdentifier] ORDER BY [AR].[CreationDate] DESC) AS [rn] + FROM [dbo].[AuthRequest] [AR] + LEFT JOIN [dbo].[Device] [D] + ON [AR].[RequestDeviceIdentifier] = [D].[Identifier] + AND [D].[UserId] = [AR].[UserId] + WHERE [AR].[Type] IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock + ) + SELECT + [PR].[Id], + [PR].[UserId], + [PR].[OrganizationId], + [PR].[Type], + [PR].[RequestDeviceIdentifier], + [PR].[RequestDeviceType], + [PR].[RequestIpAddress], + [PR].[RequestCountryName], + [PR].[ResponseDeviceId], + [PR].[AccessCode], + [PR].[PublicKey], + [PR].[Key], + [PR].[MasterPasswordHash], + [PR].[Approved], + [PR].[CreationDate], + [PR].[ResponseDate], + [PR].[AuthenticationDate], + [PR].[DeviceId] + FROM [PendingRequests] [PR] + WHERE [PR].[rn] = 1 + AND [PR].[Approved] IS NULL -- since we only want pending requests we only want the most recent that is also approved = null diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql new file mode 100644 index 0000000000..b2bb8593ef --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Create.sql @@ -0,0 +1,25 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationApplication] + ( + [Id], + [OrganizationId], + [Applications], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Applications, + @CreationDate, + @RevisionDate + ); diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql new file mode 100644 index 0000000000..5e00333287 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_DeleteById.sql @@ -0,0 +1,7 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationApplication] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql new file mode 100644 index 0000000000..13f9667df6 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadById.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql new file mode 100644 index 0000000000..9872c72e8d --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_ReadByOrganizationId.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [OrganizationId] = @OrganizationId; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql new file mode 100644 index 0000000000..5c24a307bd --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationApplication_Update.sql @@ -0,0 +1,16 @@ +CREATE PROCEDURE [dbo].[OrganizationApplication_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + UPDATE [dbo].[OrganizationApplication] + SET + [OrganizationId] = @OrganizationId, + [Applications] = @Applications, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql new file mode 100644 index 0000000000..d0cea4d73b --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_Create.sql @@ -0,0 +1,23 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Date DATETIME2(7), + @ReportData NVARCHAR(MAX), + @CreationDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationReport]( + [Id], + [OrganizationId], + [Date], + [ReportData], + [CreationDate] + ) + VALUES ( + @Id, + @OrganizationId, + @Date, + @ReportData, + @CreationDate + ); diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql new file mode 100644 index 0000000000..fa274424df --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_DeleteById.sql @@ -0,0 +1,7 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationReport] + WHERE [Id] = @Id diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql new file mode 100644 index 0000000000..046172a4c3 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadById.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [Id] = @Id; diff --git a/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql new file mode 100644 index 0000000000..6bdcf51f70 --- /dev/null +++ b/src/Sql/dbo/Dirt/Stored Procedures/OrganizationReport_ReadByOrganizationId.sql @@ -0,0 +1,9 @@ +CREATE PROCEDURE [dbo].[OrganizationReport_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [OrganizationId] = @OrganizationId; diff --git a/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql b/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql new file mode 100644 index 0000000000..58c8080e23 --- /dev/null +++ b/src/Sql/dbo/Dirt/Tables/OrganizationApplication.sql @@ -0,0 +1,14 @@ +CREATE TABLE [dbo].[OrganizationApplication] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Applications] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationApplication] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationApplication_OrganizationId] + ON [dbo].[OrganizationApplication]([OrganizationId] ASC); +GO diff --git a/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql b/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql new file mode 100644 index 0000000000..563877a340 --- /dev/null +++ b/src/Sql/dbo/Dirt/Tables/OrganizationReport.sql @@ -0,0 +1,18 @@ +CREATE TABLE [dbo].[OrganizationReport] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Date] DATETIME2 (7) NOT NULL, + [ReportData] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationReport] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationReport_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId] + ON [dbo].[OrganizationReport]([OrganizationId] ASC); +GO + +CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId_Date] + ON [dbo].[OrganizationReport]([OrganizationId] ASC, [Date] DESC); +GO diff --git a/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql b/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql new file mode 100644 index 0000000000..af687ceb8e --- /dev/null +++ b/src/Sql/dbo/Dirt/Views/OrganizationApplicationView.sql @@ -0,0 +1,5 @@ +CREATE VIEW [dbo].[OrganizationApplicationView] +AS +SELECT + * +FROM [dbo].[OrganizationApplication]; diff --git a/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql b/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql new file mode 100644 index 0000000000..202dccd35e --- /dev/null +++ b/src/Sql/dbo/Dirt/Views/OrganizationReportView.sql @@ -0,0 +1,2 @@ +CREATE VIEW [dbo].[OrganizationReportView] AS +SELECT * FROM [dbo].[OrganizationReport]; diff --git a/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Create.sql b/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Create.sql index 18160c9ea9..ab40ea5d4f 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Create.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Create.sql @@ -5,7 +5,8 @@ @Configuration VARCHAR(MAX), @Template VARCHAR(MAX), @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7) + @RevisionDate DATETIME2(7), + @Filters VARCHAR(MAX) = NULL AS BEGIN SET NOCOUNT ON @@ -18,7 +19,8 @@ BEGIN [Configuration], [Template], [CreationDate], - [RevisionDate] + [RevisionDate], + [Filters] ) VALUES ( @@ -28,6 +30,7 @@ BEGIN @Configuration, @Template, @CreationDate, - @RevisionDate + @RevisionDate, + @Filters ) END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Update.sql b/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Update.sql index b613774d04..903844ab70 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Update.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationIntegrationConfiguration_Update.sql @@ -5,7 +5,8 @@ @Configuration VARCHAR(MAX), @Template VARCHAR(MAX), @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7) + @RevisionDate DATETIME2(7), + @Filters VARCHAR(MAX) = NULL AS BEGIN SET NOCOUNT ON @@ -18,7 +19,8 @@ BEGIN [Configuration] = @Configuration, [Template] = @Template, [CreationDate] = @CreationDate, - [RevisionDate] = @RevisionDate + [RevisionDate] = @RevisionDate, + [Filters] = @Filters WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql index 2daa12209f..50ad247c1a 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql @@ -137,6 +137,20 @@ BEGIN WHERE [OrganizationId] = @Id + -- Delete Organization Application + DELETE + FROM + [dbo].[OrganizationApplication] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Report + DELETE + FROM + [dbo].[OrganizationReport] + WHERE + [OrganizationId] = @Id + DELETE FROM [dbo].[Organization] diff --git a/src/Sql/dbo/Tables/NotificationStatus.sql b/src/Sql/dbo/Tables/NotificationStatus.sql index 2f68e2b2f7..2ccb0e0a8a 100644 --- a/src/Sql/dbo/Tables/NotificationStatus.sql +++ b/src/Sql/dbo/Tables/NotificationStatus.sql @@ -5,11 +5,10 @@ CREATE TABLE [dbo].[NotificationStatus] [ReadDate] DATETIME2 (7) NULL, [DeletedDate] DATETIME2 (7) NULL, CONSTRAINT [PK_NotificationStatus] PRIMARY KEY CLUSTERED ([NotificationId] ASC, [UserId] ASC), - CONSTRAINT [FK_NotificationStatus_Notification] FOREIGN KEY ([NotificationId]) REFERENCES [dbo].[Notification] ([Id]), + CONSTRAINT [FK_NotificationStatus_Notification] FOREIGN KEY ([NotificationId]) REFERENCES [dbo].[Notification] ([Id]) ON DELETE CASCADE, CONSTRAINT [FK_NotificationStatus_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) ); GO CREATE NONCLUSTERED INDEX [IX_NotificationStatus_UserId] ON [dbo].[NotificationStatus]([UserId] ASC); - diff --git a/src/Sql/dbo/Tables/OrganizationIntegrationConfiguration.sql b/src/Sql/dbo/Tables/OrganizationIntegrationConfiguration.sql index 28283f8015..b46ca81141 100644 --- a/src/Sql/dbo/Tables/OrganizationIntegrationConfiguration.sql +++ b/src/Sql/dbo/Tables/OrganizationIntegrationConfiguration.sql @@ -7,6 +7,7 @@ CREATE TABLE [dbo].[OrganizationIntegrationConfiguration] [Template] VARCHAR (MAX) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, + [Filters] VARCHAR (MAX) NULL, CONSTRAINT [PK_OrganizationIntegrationConfiguration] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_OrganizationIntegrationConfiguration_OrganizationIntegration] FOREIGN KEY ([OrganizationIntegrationId]) REFERENCES [dbo].[OrganizationIntegration] ([Id]) ON DELETE CASCADE ); diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql index 214b74d092..8fc95eb302 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/CipherDetails_Update.sql @@ -6,7 +6,7 @@ @Data NVARCHAR(MAX), @Favorites NVARCHAR(MAX), -- not used @Folders NVARCHAR(MAX), -- not used - @Attachments NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @FolderId UNIQUEIDENTIFIER, @@ -50,6 +50,7 @@ BEGIN ELSE JSON_MODIFY([Favorites], @UserIdPath, NULL) END, + [Attachments] = @Attachments, [Reprompt] = @Reprompt, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate, diff --git a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql index 0aa73ae9b6..8baf1b5f0f 100644 --- a/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql +++ b/src/Sql/dbo/Vault/Stored Procedures/Cipher/Cipher_Update.sql @@ -6,7 +6,7 @@ @Data NVARCHAR(MAX), @Favorites NVARCHAR(MAX), @Folders NVARCHAR(MAX), - @Attachments NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @DeletedDate DATETIME2(7), @@ -25,6 +25,7 @@ BEGIN [Data] = @Data, [Favorites] = @Favorites, [Folders] = @Folders, + [Attachments] = @Attachments, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate, [DeletedDate] = @DeletedDate, diff --git a/src/Sql/dbo/Views/OrganizationIntegrationConfigurationDetailsView.sql b/src/Sql/dbo/Views/OrganizationIntegrationConfigurationDetailsView.sql index 45609da551..e66c036250 100644 --- a/src/Sql/dbo/Views/OrganizationIntegrationConfigurationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationIntegrationConfigurationDetailsView.sql @@ -6,7 +6,8 @@ AS oic.[EventType], oic.[Configuration], oi.[Configuration] AS [IntegrationConfiguration], - oic.[Template] + oic.[Template], + oic.[Filters] FROM [dbo].[OrganizationIntegrationConfiguration] oic INNER JOIN diff --git a/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs b/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs index 08c5973936..173580ad8c 100644 --- a/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs +++ b/test/Api.IntegrationTest/Factories/ApiApplicationFactory.cs @@ -28,6 +28,8 @@ public class ApiApplicationFactory : WebApplicationFactoryBase _identityApplicationFactory.ManagesDatabase = false; } + public IdentityApplicationFactory Identity => _identityApplicationFactory; + protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); diff --git a/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs new file mode 100644 index 0000000000..4d86817a11 --- /dev/null +++ b/test/Api.IntegrationTest/Platform/Controllers/PushControllerTests.cs @@ -0,0 +1,449 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Nodes; +using Azure.Storage.Queues; +using Bit.Api.IntegrationTest.Factories; +using Bit.Core.Enums; +using Bit.Core.Models; +using Bit.Core.Models.Api; +using Bit.Core.Models.Data; +using Bit.Core.NotificationHub; +using Bit.Core.Platform.Installations; +using Bit.Core.Repositories; +using NSubstitute; +using Xunit; +using static Bit.Core.Settings.GlobalSettings; + +namespace Bit.Api.IntegrationTest.Platform.Controllers; + +public class PushControllerTests +{ + private static readonly Guid _userId = Guid.NewGuid(); + private static readonly Guid _organizationId = Guid.NewGuid(); + private static readonly Guid _deviceId = Guid.NewGuid(); + + public static IEnumerable SendData() + { + static object[] Typed(PushSendRequestModel pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall = true) + { + return [pushSendRequestModel, expectedHubTagExpression, expectHubCall]; + } + + static object[] UserTyped(PushType pushType) + { + return Typed(new PushSendRequestModel + { + Type = pushType, + UserId = _userId, + DeviceId = _deviceId, + Payload = new UserPushNotification + { + Date = DateTime.UtcNow, + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + } + + // User cipher + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherUpdate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherUpdate, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherCreate, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherDelete, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + // Organization cipher, an org cipher would not naturally be synced from our + // code but it is technically possible to be submitted to the endpoint. + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncCipherDelete, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new SyncCipherPushNotification + { + Id = Guid.NewGuid(), + OrganizationId = _organizationId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderDelete, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.SyncFolderCreate, + UserId = _userId, + DeviceId = _deviceId, + Payload = new SyncFolderPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return UserTyped(PushType.SyncCiphers); + yield return UserTyped(PushType.SyncVault); + yield return UserTyped(PushType.SyncOrganizations); + yield return UserTyped(PushType.SyncOrgKeys); + yield return UserTyped(PushType.SyncSettings); + yield return UserTyped(PushType.LogOut); + yield return UserTyped(PushType.PendingSecurityTasks); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.AuthRequest, + UserId = _userId, + DeviceId = _deviceId, + Payload = new AuthRequestPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.AuthRequestResponse, + UserId = _userId, + DeviceId = _deviceId, + Payload = new AuthRequestPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.Notification, + UserId = _userId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.Notification, + UserId = _userId, + DeviceId = _deviceId, + ClientType = ClientType.All, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + Global = true, + }, + }, $"(template:payload_userId:%installation%_{_userId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.NotificationStatus, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + + yield return Typed(new PushSendRequestModel + { + Type = PushType.NotificationStatus, + OrganizationId = _organizationId, + DeviceId = _deviceId, + Payload = new NotificationPushNotification + { + Id = Guid.NewGuid(), + UserId = _userId, + }, + }, $"(template:payload && organizationId:%installation%_{_organizationId})"); + } + + [Theory] + [MemberData(nameof(SendData))] + public async Task Send_Works(PushSendRequestModel pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall) + { + var (apiFactory, httpClient, installation, queueClient, notificationHubProxy) = await SetupTest(); + + // Act + var pushSendResponse = await httpClient.PostAsJsonAsync("push/send", pushSendRequestModel); + + // Assert + pushSendResponse.EnsureSuccessStatusCode(); + + // Relayed notifications, the ones coming to this endpoint should + // not make their way into our Azure Queue and instead should only be sent to Azure Notifications + // hub. + await queueClient + .Received(0) + .SendMessageAsync(Arg.Any()); + + // Check that this notification was sent through hubs the expected number of times + await notificationHubProxy + .Received(expectHubCall ? 1 : 0) + .SendTemplateNotificationAsync( + Arg.Any>(), + Arg.Is(expectedHubTagExpression.Replace("%installation%", installation.Id.ToString())) + ); + + // TODO: Expect on the dictionary more? + + // Notifications being relayed from SH should have the device id + // tracked so that we can later send the notification to that device. + await apiFactory.GetService() + .Received(1) + .UpsertAsync(Arg.Is( + ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == pushSendRequestModel.DeviceId.ToString() + )); + } + + [Fact] + public async Task Send_InstallationNotification_NotAuthenticatedInstallation_Fails() + { + var (_, httpClient, _, _, _) = await SetupTest(); + + var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.NotificationStatus, + InstallationId = Guid.NewGuid(), + Payload = new { } + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.Content.ReadFromJsonAsync(); + Assert.Equal(JsonValueKind.Object, body.GetValueKind()); + Assert.True(body.AsObject().TryGetPropertyValue("message", out var message)); + Assert.Equal(JsonValueKind.String, message.GetValueKind()); + Assert.Equal("InstallationId does not match current context.", message.GetValue()); + } + + [Fact] + public async Task Send_InstallationNotification_Works() + { + var (apiFactory, httpClient, installation, _, notificationHubProxy) = await SetupTest(); + + var deviceId = Guid.NewGuid(); + + var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.NotificationStatus, + InstallationId = installation.Id, + Payload = new { }, + DeviceId = deviceId, + ClientType = ClientType.Web, + }); + + response.EnsureSuccessStatusCode(); + + await notificationHubProxy + .Received(1) + .SendTemplateNotificationAsync( + Arg.Any>(), + Arg.Is($"(template:payload && installationId:{installation.Id} && clientType:Web)") + ); + + await apiFactory.GetService() + .Received(1) + .UpsertAsync(Arg.Is( + ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == deviceId.ToString() + )); + } + + [Fact] + public async Task Send_NoOrganizationNoInstallationNoUser_FailsModelValidation() + { + var (_, client, _, _, _) = await SetupTest(); + + var response = await client.PostAsJsonAsync("push/send", new PushSendRequestModel + { + Type = PushType.AuthRequest, + Payload = new { }, + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var body = await response.Content.ReadFromJsonAsync(); + Assert.Equal(JsonValueKind.Object, body.GetValueKind()); + Assert.True(body.AsObject().TryGetPropertyValue("message", out var message)); + Assert.Equal(JsonValueKind.String, message.GetValueKind()); + Assert.Equal("The model state is invalid.", message.GetValue()); + } + + private static async Task<(ApiApplicationFactory Factory, HttpClient AuthedClient, Installation Installation, QueueClient MockedQueue, INotificationHubProxy MockedHub)> SetupTest() + { + // Arrange + var apiFactory = new ApiApplicationFactory(); + + var queueClient = Substitute.For(); + + // Substitute the underlying queue messages will go to. + apiFactory.ConfigureServices(services => + { + var queueClientService = services.FirstOrDefault( + sd => sd.ServiceKey == (object)"notifications" + && sd.ServiceType == typeof(QueueClient) + ) ?? throw new InvalidOperationException("Expected service was not found."); + + services.Remove(queueClientService); + + services.AddKeyedSingleton("notifications", queueClient); + }); + + var notificationHubProxy = Substitute.For(); + + apiFactory.SubstituteService(s => + { + s.AllClients + .Returns(notificationHubProxy); + }); + + apiFactory.SubstituteService(s => { }); + + // Setup as cloud with NotificationHub setup and Azure Queue + apiFactory.UpdateConfiguration("GlobalSettings:Notifications:ConnectionString", "any_value"); + + // Configure hubs + var index = 0; + void AddHub(NotificationHubSettings notificationHubSettings) + { + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:ConnectionString", + notificationHubSettings.ConnectionString + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:HubName", + notificationHubSettings.HubName + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationStartDate", + notificationHubSettings.RegistrationStartDate?.ToString() + ); + apiFactory.UpdateConfiguration( + $"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationEndDate", + notificationHubSettings.RegistrationEndDate?.ToString() + ); + index++; + } + + AddHub(new NotificationHubSettings + { + ConnectionString = "some_value", + RegistrationStartDate = DateTime.UtcNow.AddDays(-2), + }); + + var httpClient = apiFactory.CreateClient(); + + // Add installation into database + var installationRepository = apiFactory.GetService(); + var installation = await installationRepository.CreateAsync(new Installation + { + Key = "my_test_key", + Email = "test@example.com", + Enabled = true, + }); + + var identityClient = apiFactory.Identity.CreateDefaultClient(); + + var connectTokenResponse = await identityClient.PostAsync("connect/token", new FormUrlEncodedContent(new Dictionary + { + { "grant_type", "client_credentials" }, + { "scope", "api.push" }, + { "client_id", $"installation.{installation.Id}" }, + { "client_secret", installation.Key }, + })); + + connectTokenResponse.EnsureSuccessStatusCode(); + + var connectTokenResponseModel = await connectTokenResponse.Content.ReadFromJsonAsync(); + + // Setup authentication + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + connectTokenResponseModel["token_type"].GetValue(), + connectTokenResponseModel["access_token"].GetValue() + ); + + return (apiFactory, httpClient, installation, queueClient, notificationHubProxy); + } +} diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs index f7863401b5..4732ddd748 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationIntegrationsConfigurationControllerTests.cs @@ -3,7 +3,7 @@ using Bit.Api.AdminConsole.Controllers; using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -151,9 +151,10 @@ public class OrganizationIntegrationsConfigurationControllerTests { organizationIntegration.OrganizationId = organizationId; organizationIntegration.Type = IntegrationType.Slack; - var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456"); + var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456"); model.Configuration = JsonSerializer.Serialize(slackConfig); model.Template = "Template String"; + model.Filters = null; var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration); @@ -188,9 +189,48 @@ public class OrganizationIntegrationsConfigurationControllerTests { organizationIntegration.OrganizationId = organizationId; organizationIntegration.Type = IntegrationType.Webhook; - var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost"); + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN"); model.Configuration = JsonSerializer.Serialize(webhookConfig); model.Template = "Template String"; + model.Filters = null; + + var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration); + + sutProvider.Sut.Url = Substitute.For(); + sutProvider.GetDependency() + .OrganizationOwner(organizationId) + .Returns(true); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(organizationIntegration); + sutProvider.GetDependency() + .CreateAsync(Arg.Any()) + .Returns(organizationIntegrationConfiguration); + var requestAction = await sutProvider.Sut.CreateAsync(organizationId, organizationIntegration.Id, model); + + await sutProvider.GetDependency().Received(1) + .CreateAsync(Arg.Any()); + Assert.IsType(requestAction); + Assert.Equal(expected.Id, requestAction.Id); + Assert.Equal(expected.Configuration, requestAction.Configuration); + Assert.Equal(expected.EventType, requestAction.EventType); + Assert.Equal(expected.Template, requestAction.Template); + } + + [Theory, BitAutoData] + public async Task PostAsync_OnlyUrlProvided_Webhook_Succeeds( + SutProvider sutProvider, + Guid organizationId, + OrganizationIntegration organizationIntegration, + OrganizationIntegrationConfiguration organizationIntegrationConfiguration, + OrganizationIntegrationConfigurationRequestModel model) + { + organizationIntegration.OrganizationId = organizationId; + organizationIntegration.Type = IntegrationType.Webhook; + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost"); + model.Configuration = JsonSerializer.Serialize(webhookConfig); + model.Template = "Template String"; + model.Filters = null; var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration); @@ -350,7 +390,7 @@ public class OrganizationIntegrationsConfigurationControllerTests { organizationIntegration.OrganizationId = organizationId; organizationIntegration.Type = IntegrationType.Webhook; - var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost"); + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN"); model.Configuration = JsonSerializer.Serialize(webhookConfig); model.Template = null; @@ -393,9 +433,10 @@ public class OrganizationIntegrationsConfigurationControllerTests organizationIntegration.OrganizationId = organizationId; organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id; organizationIntegration.Type = IntegrationType.Slack; - var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456"); + var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456"); model.Configuration = JsonSerializer.Serialize(slackConfig); model.Template = "Template String"; + model.Filters = null; var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration)); @@ -436,9 +477,53 @@ public class OrganizationIntegrationsConfigurationControllerTests organizationIntegration.OrganizationId = organizationId; organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id; organizationIntegration.Type = IntegrationType.Webhook; - var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost"); + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN"); model.Configuration = JsonSerializer.Serialize(webhookConfig); model.Template = "Template String"; + model.Filters = null; + + var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration)); + + sutProvider.Sut.Url = Substitute.For(); + sutProvider.GetDependency() + .OrganizationOwner(organizationId) + .Returns(true); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(organizationIntegration); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(organizationIntegrationConfiguration); + var requestAction = await sutProvider.Sut.UpdateAsync( + organizationId, + organizationIntegration.Id, + organizationIntegrationConfiguration.Id, + model); + + await sutProvider.GetDependency().Received(1) + .ReplaceAsync(Arg.Any()); + Assert.IsType(requestAction); + Assert.Equal(expected.Id, requestAction.Id); + Assert.Equal(expected.Configuration, requestAction.Configuration); + Assert.Equal(expected.EventType, requestAction.EventType); + Assert.Equal(expected.Template, requestAction.Template); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_OnlyUrlProvided_Webhook_Succeeds( + SutProvider sutProvider, + Guid organizationId, + OrganizationIntegration organizationIntegration, + OrganizationIntegrationConfiguration organizationIntegrationConfiguration, + OrganizationIntegrationConfigurationRequestModel model) + { + organizationIntegration.OrganizationId = organizationId; + organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id; + organizationIntegration.Type = IntegrationType.Webhook; + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost"); + model.Configuration = JsonSerializer.Serialize(webhookConfig); + model.Template = "Template String"; + model.Filters = null; var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration)); @@ -476,9 +561,10 @@ public class OrganizationIntegrationsConfigurationControllerTests { organizationIntegration.OrganizationId = organizationId; organizationIntegration.Type = IntegrationType.Webhook; - var webhookConfig = new WebhookIntegrationConfiguration(url: "https://localhost"); + var webhookConfig = new WebhookIntegrationConfiguration(Url: "https://localhost", Scheme: "Bearer", Token: "AUTH-TOKEN"); model.Configuration = JsonSerializer.Serialize(webhookConfig); model.Template = "Template String"; + model.Filters = null; sutProvider.Sut.Url = Substitute.For(); sutProvider.GetDependency() @@ -582,7 +668,7 @@ public class OrganizationIntegrationsConfigurationControllerTests organizationIntegration.OrganizationId = organizationId; organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id; organizationIntegration.Type = IntegrationType.Slack; - var slackConfig = new SlackIntegrationConfiguration(channelId: "C123456"); + var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456"); model.Configuration = JsonSerializer.Serialize(slackConfig); model.Template = null; diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs index 542a76090c..e5fb3fe541 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs @@ -30,6 +30,7 @@ public class OrganizationUserControllerPutTests OrganizationUser organizationUser, OrganizationAbility organizationAbility, SutProvider sutProvider, Guid savingUserId) { + // Arrange Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); // Authorize all changes for basic happy path test @@ -41,15 +42,18 @@ public class OrganizationUserControllerPutTests // Save these for later - organizationUser object will be mutated var orgUserId = organizationUser.Id; var orgUserEmail = organizationUser.Email; + var existingUserType = organizationUser.Type; + // Act await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + // Assert await sutProvider.GetDependency().Received(1).UpdateUserAsync(Arg.Is(ou => ou.Type == model.Type && ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && ou.AccessSecretsManager == model.AccessSecretsManager && ou.Id == orgUserId && - ou.Email == orgUserEmail), + ou.Email == orgUserEmail), existingUserType, savingUserId, Arg.Is>(cas => cas.All(c => model.Collections.Any(m => m.Id == c.Id))), @@ -77,6 +81,7 @@ public class OrganizationUserControllerPutTests OrganizationUser organizationUser, OrganizationAbility organizationAbility, SutProvider sutProvider, Guid savingUserId) { + // Arrange // Updating self organizationUser.UserId = savingUserId; organizationAbility.AllowAdminAccessToAllCollectionItems = false; @@ -88,15 +93,18 @@ public class OrganizationUserControllerPutTests var orgUserId = organizationUser.Id; var orgUserEmail = organizationUser.Email; + var existingUserType = organizationUser.Type; + // Act await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + // Assert await sutProvider.GetDependency().Received(1).UpdateUserAsync(Arg.Is(ou => - ou.Type == model.Type && - ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && - ou.AccessSecretsManager == model.AccessSecretsManager && - ou.Id == orgUserId && - ou.Email == orgUserEmail), + ou.Type == model.Type && + ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && + ou.AccessSecretsManager == model.AccessSecretsManager && + ou.Id == orgUserId && + ou.Email == orgUserEmail), existingUserType, savingUserId, Arg.Is>(cas => cas.All(c => model.Collections.Any(m => m.Id == c.Id))), @@ -110,6 +118,7 @@ public class OrganizationUserControllerPutTests OrganizationUser organizationUser, OrganizationAbility organizationAbility, SutProvider sutProvider, Guid savingUserId) { + // Arrange // Updating self organizationUser.UserId = savingUserId; organizationAbility.AllowAdminAccessToAllCollectionItems = true; @@ -121,15 +130,18 @@ public class OrganizationUserControllerPutTests var orgUserId = organizationUser.Id; var orgUserEmail = organizationUser.Email; + var existingUserType = organizationUser.Type; + // Act await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + // Assert await sutProvider.GetDependency().Received(1).UpdateUserAsync(Arg.Is(ou => - ou.Type == model.Type && - ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && - ou.AccessSecretsManager == model.AccessSecretsManager && - ou.Id == orgUserId && - ou.Email == orgUserEmail), + ou.Type == model.Type && + ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && + ou.AccessSecretsManager == model.AccessSecretsManager && + ou.Id == orgUserId && + ou.Email == orgUserEmail), existingUserType, savingUserId, Arg.Is>(cas => cas.All(c => model.Collections.Any(m => m.Id == c.Id))), @@ -142,6 +154,7 @@ public class OrganizationUserControllerPutTests OrganizationUser organizationUser, OrganizationAbility organizationAbility, SutProvider sutProvider, Guid savingUserId) { + // Arrange var editedCollectionId = CoreHelpers.GenerateComb(); var readonlyCollectionId1 = CoreHelpers.GenerateComb(); var readonlyCollectionId2 = CoreHelpers.GenerateComb(); @@ -194,16 +207,19 @@ public class OrganizationUserControllerPutTests .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) .Returns(AuthorizationResult.Failed()); + var existingUserType = organizationUser.Type; + // Act await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + // Assert // Expect all collection access (modified and unmodified) to be saved await sutProvider.GetDependency().Received(1).UpdateUserAsync(Arg.Is(ou => - ou.Type == model.Type && - ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && - ou.AccessSecretsManager == model.AccessSecretsManager && - ou.Id == orgUserId && - ou.Email == orgUserEmail), + ou.Type == model.Type && + ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) && + ou.AccessSecretsManager == model.AccessSecretsManager && + ou.Id == orgUserId && + ou.Email == orgUserEmail), existingUserType, savingUserId, Arg.Is>(cas => cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && diff --git a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModelTests.cs b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModelTests.cs index 77ce06f4f8..20831ec7d9 100644 --- a/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModelTests.cs +++ b/test/Api.Test/AdminConsole/Models/Request/Organizations/OrganizationIntegrationConfigurationRequestModelTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; using Xunit; @@ -43,7 +43,7 @@ public class OrganizationIntegrationConfigurationRequestModelTests [InlineData(" ")] public void IsValidForType_EmptyTemplate_ReturnsFalse(string? template) { - var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com")); + var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com", "Bearer", "AUTH-TOKEN")); var model = new OrganizationIntegrationConfigurationRequestModel { Configuration = config, @@ -65,6 +65,21 @@ public class OrganizationIntegrationConfigurationRequestModelTests Assert.False(model.IsValidForType(IntegrationType.Webhook)); } + + [Fact] + public void IsValidForType_InvalidJsonFilters_ReturnsFalse() + { + var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com")); + var model = new OrganizationIntegrationConfigurationRequestModel + { + Configuration = config, + Filters = "{Not valid json", + Template = "template" + }; + + Assert.False(model.IsValidForType(IntegrationType.Webhook)); + } + [Fact] public void IsValidForType_ScimIntegration_ReturnsFalse() { @@ -92,7 +107,34 @@ public class OrganizationIntegrationConfigurationRequestModelTests } [Fact] - public void IsValidForType_ValidWebhookConfiguration_ReturnsTrue() + public void IsValidForType_ValidSlackConfigurationWithFilters_ReturnsTrue() + { + var config = JsonSerializer.Serialize(new SlackIntegrationConfiguration("C12345")); + var filters = JsonSerializer.Serialize(new IntegrationFilterGroup() + { + AndOperator = true, + Rules = [ + new IntegrationFilterRule() + { + Operation = IntegrationFilterOperation.Equals, + Property = "CollectionId", + Value = Guid.NewGuid() + } + ], + Groups = [] + }); + var model = new OrganizationIntegrationConfigurationRequestModel + { + Configuration = config, + Filters = filters, + Template = "template" + }; + + Assert.True(model.IsValidForType(IntegrationType.Slack)); + } + + [Fact] + public void IsValidForType_ValidNoAuthWebhookConfiguration_ReturnsTrue() { var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com")); var model = new OrganizationIntegrationConfigurationRequestModel @@ -104,6 +146,46 @@ public class OrganizationIntegrationConfigurationRequestModelTests Assert.True(model.IsValidForType(IntegrationType.Webhook)); } + [Fact] + public void IsValidForType_ValidWebhookConfiguration_ReturnsTrue() + { + var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com", "Bearer", "AUTH-TOKEN")); + var model = new OrganizationIntegrationConfigurationRequestModel + { + Configuration = config, + Template = "template" + }; + + Assert.True(model.IsValidForType(IntegrationType.Webhook)); + } + + [Fact] + public void IsValidForType_ValidWebhookConfigurationWithFilters_ReturnsTrue() + { + var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration("https://example.com", "Bearer", "AUTH-TOKEN")); + var filters = JsonSerializer.Serialize(new IntegrationFilterGroup() + { + AndOperator = true, + Rules = [ + new IntegrationFilterRule() + { + Operation = IntegrationFilterOperation.Equals, + Property = "CollectionId", + Value = Guid.NewGuid() + } + ], + Groups = [] + }); + var model = new OrganizationIntegrationConfigurationRequestModel + { + Configuration = config, + Filters = filters, + Template = "template" + }; + + Assert.True(model.IsValidForType(IntegrationType.Webhook)); + } + [Fact] public void IsValidForType_UnknownIntegrationType_ReturnsFalse() { diff --git a/test/Api.Test/Api.Test.csproj b/test/Api.Test/Api.Test.csproj index d6b31ce930..fb75246d4f 100644 --- a/test/Api.Test/Api.Test.csproj +++ b/test/Api.Test/Api.Test.csproj @@ -25,9 +25,4 @@ - - - - - diff --git a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs index 581a7e8f04..64261ede82 100644 --- a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs @@ -1,27 +1,16 @@ using System.Security.Claims; -using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Auth.Controllers; -using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Request.Accounts; -using Bit.Api.Auth.Models.Request.WebAuthn; -using Bit.Api.KeyManagement.Validators; -using Bit.Api.Tools.Models.Request; -using Bit.Api.Vault.Models.Request; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; -using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Api.Request.Accounts; -using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Entities; using Bit.Core.Exceptions; -using Bit.Core.KeyManagement.UserKey; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Tools.Entities; -using Bit.Core.Vault.Entities; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Identity; using NSubstitute; @@ -39,23 +28,10 @@ public class AccountsControllerTests : IDisposable private readonly IProviderUserRepository _providerUserRepository; private readonly IPolicyService _policyService; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; - private readonly IRotateUserKeyCommand _rotateUserKeyCommand; private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand; private readonly IFeatureService _featureService; - private readonly IRotationValidator, IEnumerable> _cipherValidator; - private readonly IRotationValidator, IEnumerable> _folderValidator; - private readonly IRotationValidator, IReadOnlyList> _sendValidator; - private readonly IRotationValidator, IEnumerable> - _emergencyAccessValidator; - private readonly IRotationValidator, - IReadOnlyList> - _resetPasswordValidator; - private readonly IRotationValidator, IEnumerable> - _webauthnKeyRotationValidator; - - public AccountsControllerTests() { _userService = Substitute.For(); @@ -64,21 +40,9 @@ public class AccountsControllerTests : IDisposable _providerUserRepository = Substitute.For(); _policyService = Substitute.For(); _setInitialMasterPasswordCommand = Substitute.For(); - _rotateUserKeyCommand = Substitute.For(); _twoFactorIsEnabledQuery = Substitute.For(); _tdeOffboardingPasswordCommand = Substitute.For(); _featureService = Substitute.For(); - _cipherValidator = - Substitute.For, IEnumerable>>(); - _folderValidator = - Substitute.For, IEnumerable>>(); - _sendValidator = Substitute.For, IReadOnlyList>>(); - _emergencyAccessValidator = Substitute.For, - IEnumerable>>(); - _webauthnKeyRotationValidator = Substitute.For, IEnumerable>>(); - _resetPasswordValidator = Substitute - .For, - IReadOnlyList>>(); _sut = new AccountsController( _organizationService, @@ -88,15 +52,8 @@ public class AccountsControllerTests : IDisposable _policyService, _setInitialMasterPasswordCommand, _tdeOffboardingPasswordCommand, - _rotateUserKeyCommand, _twoFactorIsEnabledQuery, - _featureService, - _cipherValidator, - _folderValidator, - _sendValidator, - _emergencyAccessValidator, - _resetPasswordValidator, - _webauthnKeyRotationValidator + _featureService ); } diff --git a/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs b/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs new file mode 100644 index 0000000000..828911f6bd --- /dev/null +++ b/test/Api.Test/Auth/Controllers/AuthRequestsControllerTests.cs @@ -0,0 +1,258 @@ +using System.Security.Claims; +using Bit.Api.Auth.Controllers; +using Bit.Api.Auth.Models.Response; +using Bit.Api.Models.Response; +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Api.Request.AuthRequest; +using Bit.Core.Auth.Models.Data; +using Bit.Core.Auth.Services; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Auth.Controllers; + +[ControllerCustomize(typeof(AuthRequestsController))] +[SutProviderCustomize] +public class AuthRequestsControllerTests +{ + const string _testGlobalSettingsBaseUri = "https://vault.test.dev"; + + [Theory, BitAutoData] + public async Task Get_ReturnsExpectedResult( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetManyByUserIdAsync(user.Id) + .Returns([authRequest]); + + // Act + var result = await sutProvider.Sut.Get(); + + // Assert + Assert.NotNull(result); + var expectedCount = 1; + Assert.Equal(result.Data.Count(), expectedCount); + Assert.IsType>(result); + } + + [Theory, BitAutoData] + public async Task GetById_ThrowsNotFoundException( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetAuthRequestAsync(authRequest.Id, user.Id) + .Returns((AuthRequest)null); + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.Get(authRequest.Id)); + } + + [Theory, BitAutoData] + public async Task GetById_ReturnsAuthRequest( + SutProvider sutProvider, + User user, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetAuthRequestAsync(authRequest.Id, user.Id) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.Get(authRequest.Id); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task GetPending_ReturnsExpectedResult( + SutProvider sutProvider, + User user, + PendingAuthRequestDetails authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .GetManyPendingAuthRequestByUserId(user.Id) + .Returns([authRequest]); + + // Act + var result = await sutProvider.Sut.GetPendingAuthRequestsAsync(); + + // Assert + Assert.NotNull(result); + var expectedCount = 1; + Assert.Equal(result.Data.Count(), expectedCount); + Assert.IsType>(result); + } + + [Theory, BitAutoData] + public async Task GetResponseById_ThrowsNotFoundException( + SutProvider sutProvider, + AuthRequest authRequest) + { + // Arrange + sutProvider.GetDependency() + .GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode) + .Returns((AuthRequest)null); + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode)); + } + + [Theory, BitAutoData] + public async Task GetResponseById_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetValidatedAuthRequestAsync(authRequest.Id, authRequest.AccessCode) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.GetResponse(authRequest.Id, authRequest.AccessCode); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task Post_AdminApprovalRequest_ThrowsBadRequestException( + SutProvider sutProvider, + AuthRequestCreateRequestModel authRequest) + { + // Arrange + authRequest.Type = AuthRequestType.AdminApproval; + + // Act + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.Post(authRequest)); + + var expectedMessage = "You must be authenticated to create a request of that type."; + Assert.Equal(exception.Message, expectedMessage); + } + + [Theory, BitAutoData] + public async Task Post_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequestCreateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + requestModel.Type = AuthRequestType.AuthenticateAndUnlock; + sutProvider.GetDependency() + .CreateAuthRequestAsync(requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.Post(requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task PostAdminRequest_ReturnsAuthRequest( + SutProvider sutProvider, + AuthRequestCreateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + requestModel.Type = AuthRequestType.AuthenticateAndUnlock; + sutProvider.GetDependency() + .CreateAuthRequestAsync(requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut.PostAdminRequest(requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task Put_ReturnsAuthRequest( + SutProvider sutProvider, + User user, + AuthRequestUpdateRequestModel requestModel, + AuthRequest authRequest) + { + // Arrange + SetBaseServiceUri(sutProvider); + + sutProvider.GetDependency() + .GetProperUserId(Arg.Any()) + .Returns(user.Id); + + sutProvider.GetDependency() + .UpdateAuthRequestAsync(authRequest.Id, user.Id, requestModel) + .Returns(authRequest); + + // Act + var result = await sutProvider.Sut + .Put(authRequest.Id, requestModel); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + private void SetBaseServiceUri(SutProvider sutProvider) + { + sutProvider.GetDependency() + .BaseServiceUri + .Vault + .Returns(_testGlobalSettingsBaseUri); + } +} diff --git a/test/Api.Test/Controllers/CollectionsControllerTests.cs b/test/Api.Test/Controllers/CollectionsControllerTests.cs index 09e93d15f7..bdcf6bc74e 100644 --- a/test/Api.Test/Controllers/CollectionsControllerTests.cs +++ b/test/Api.Test/Controllers/CollectionsControllerTests.cs @@ -9,7 +9,6 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Authorization; @@ -38,9 +37,11 @@ public class CollectionsControllerTests _ = await sutProvider.Sut.Post(organization.Id, collectionRequest); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) - .SaveAsync(Arg.Any(), Arg.Any>(), + .CreateAsync(Arg.Is(c => + c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId && c.OrganizationId == organization.Id), + Arg.Any>(), Arg.Any>()); } @@ -64,9 +65,9 @@ public class CollectionsControllerTests _ = await sutProvider.Sut.Put(collection.OrganizationId, collection.Id, collectionRequest); - await sutProvider.GetDependency() + await sutProvider.GetDependency() .Received(1) - .SaveAsync(ExpectedCollection(), Arg.Any>(), + .UpdateAsync(ExpectedCollection(), Arg.Any>(), Arg.Any>()); } diff --git a/test/Api.Test/Dirt/ReportsControllerTests.cs b/test/Api.Test/Dirt/ReportsControllerTests.cs index 37a6cb79c3..af285d8b85 100644 --- a/test/Api.Test/Dirt/ReportsControllerTests.cs +++ b/test/Api.Test/Dirt/ReportsControllerTests.cs @@ -142,4 +142,142 @@ public class ReportsControllerTests _.OrganizationId == request.OrganizationId && _.PasswordHealthReportApplicationIds == request.PasswordHealthReportApplicationIds)); } + + [Theory, BitAutoData] + public async Task AddOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + + // Act + var request = new AddOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + ReportData = "Report Data", + Date = DateTime.UtcNow + }; + await sutProvider.Sut.AddOrganizationReport(request); + + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .AddOrganizationReportAsync(Arg.Is(_ => + _.OrganizationId == request.OrganizationId && + _.ReportData == request.ReportData && + _.Date == request.Date)); + } + + [Theory, BitAutoData] + public async Task AddOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var request = new AddOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + ReportData = "Report Data", + Date = DateTime.UtcNow + }; + await Assert.ThrowsAsync(async () => + await sutProvider.Sut.AddOrganizationReport(request)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + // Act + var request = new DropOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + OrganizationReportIds = new List { Guid.NewGuid(), Guid.NewGuid() } + }; + await sutProvider.Sut.DropOrganizationReport(request); + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .DropOrganizationReportAsync(Arg.Is(_ => + _.OrganizationId == request.OrganizationId && + _.OrganizationReportIds.SequenceEqual(request.OrganizationReportIds))); + } + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var request = new DropOrganizationReportRequest + { + OrganizationId = Guid.NewGuid(), + OrganizationReportIds = new List { Guid.NewGuid(), Guid.NewGuid() } + }; + await Assert.ThrowsAsync(async () => + await sutProvider.Sut.DropOrganizationReport(request)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } + [Theory, BitAutoData] + public async Task GetOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + // Act + var orgId = Guid.NewGuid(); + var result = await sutProvider.Sut.GetOrganizationReports(orgId); + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .GetOrganizationReportAsync(Arg.Is(_ => _ == orgId)); + } + + [Theory, BitAutoData] + public async Task GetOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + // Act + var orgId = Guid.NewGuid(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReports(orgId)); + // Assert + _ = sutProvider.GetDependency() + .Received(0); + + } + + [Theory, BitAutoData] + public async Task GetLastestOrganizationReportAsync_withAccess_success(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(true); + + // Act + var orgId = Guid.NewGuid(); + var result = await sutProvider.Sut.GetLatestOrganizationReport(orgId); + + // Assert + _ = sutProvider.GetDependency() + .Received(1) + .GetLatestOrganizationReportAsync(Arg.Is(_ => _ == orgId)); + } + + [Theory, BitAutoData] + public async Task GetLastestOrganizationReportAsync_withoutAccess(SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().AccessReports(Arg.Any()).Returns(false); + + // Act + var orgId = Guid.NewGuid(); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetLatestOrganizationReport(orgId)); + + // Assert + _ = sutProvider.GetDependency() + .Received(0); + } } diff --git a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs index 6df09c17dc..d6a26255e9 100644 --- a/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs +++ b/test/Api.Test/Platform/Push/Controllers/PushControllerTests.cs @@ -18,210 +18,6 @@ namespace Bit.Api.Test.Platform.Push.Controllers; [SutProviderCustomize] public class PushControllerTests { - [Theory] - [BitAutoData(false, true)] - [BitAutoData(false, false)] - [BitAutoData(true, true)] - public async Task SendAsync_InstallationIdNotSetOrSelfHosted_BadRequest(bool haveInstallationId, bool selfHosted, - SutProvider sutProvider, Guid installationId, Guid userId, Guid organizationId) - { - sutProvider.GetDependency().SelfHosted = selfHosted; - if (haveInstallationId) - { - sutProvider.GetDependency().InstallationId.Returns(installationId); - } - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = userId.ToString(), - OrganizationId = organizationId.ToString(), - InstallationId = installationId.ToString(), - Payload = "test-payload" - })); - - Assert.Equal("Not correctly configured for push relays.", exception.Message); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [BitAutoData] - public async Task SendAsync_UserIdAndOrganizationIdAndInstallationIdEmpty_NoPushNotificationSent( - SutProvider sutProvider, Guid installationId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = null, - Payload = "test-payload" - }); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true], [false, true])] - public async Task SendAsync_UserIdSet_SendPayloadToUserAsync(bool haveIdentifier, bool haveDeviceId, - bool haveOrganizationId, SutProvider sutProvider, Guid installationId, Guid userId, - Guid identifier, Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedUserId = $"{installationId}_{userId}"; - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = userId.ToString(), - OrganizationId = haveOrganizationId ? Guid.NewGuid().ToString() : null, - InstallationId = null, - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToUserAsync(expectedUserId, PushType.Notification, "test-payload", expectedIdentifier, - expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true])] - public async Task SendAsync_OrganizationIdSet_SendPayloadToOrganizationAsync(bool haveIdentifier, bool haveDeviceId, - SutProvider sutProvider, Guid installationId, Guid organizationId, Guid identifier, - Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedOrganizationId = $"{installationId}_{organizationId}"; - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = organizationId.ToString(), - InstallationId = null, - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToOrganizationAsync(expectedOrganizationId, PushType.Notification, "test-payload", - expectedIdentifier, expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Theory] - [RepeatingPatternBitAutoData([false, true], [false, true])] - public async Task SendAsync_InstallationIdSet_SendPayloadToInstallationAsync(bool haveIdentifier, bool haveDeviceId, - SutProvider sutProvider, Guid installationId, Guid identifier, Guid deviceId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null; - var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null; - - await sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = installationId.ToString(), - Payload = "test-payload", - DeviceId = haveDeviceId ? deviceId.ToString() : null, - Identifier = haveIdentifier ? identifier.ToString() : null, - ClientType = ClientType.All, - }); - - await sutProvider.GetDependency().Received(1) - .SendPayloadToInstallationAsync(installationId.ToString(), PushType.Notification, "test-payload", - expectedIdentifier, expectedDeviceId, ClientType.All); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - } - - [Theory] - [BitAutoData] - public async Task SendAsync_InstallationIdNotMatching_BadRequest(SutProvider sutProvider, - Guid installationId) - { - sutProvider.GetDependency().SelfHosted = false; - sutProvider.GetDependency().InstallationId.Returns(installationId); - - var exception = await Assert.ThrowsAsync(() => - sutProvider.Sut.SendAsync(new PushSendRequestModel - { - Type = PushType.Notification, - UserId = null, - OrganizationId = null, - InstallationId = Guid.NewGuid().ToString(), - Payload = "test-payload", - DeviceId = null, - Identifier = null, - ClientType = ClientType.All, - })); - - Assert.Equal("InstallationId does not match current context.", exception.Message); - - await sutProvider.GetDependency().Received(0) - .SendPayloadToInstallationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToOrganizationAsync(Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any()); - await sutProvider.GetDependency().Received(0) - .SendPayloadToUserAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any()); - } - [Theory] [BitAutoData(false, true)] [BitAutoData(false, false)] diff --git a/test/Core.Test/AdminConsole/Models/Data/Integrations/IntegrationMessageTests.cs b/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs similarity index 91% rename from test/Core.Test/AdminConsole/Models/Data/Integrations/IntegrationMessageTests.cs rename to test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs index 6ed84717de..a68bfd4fcb 100644 --- a/test/Core.Test/AdminConsole/Models/Data/Integrations/IntegrationMessageTests.cs +++ b/test/Core.Test/AdminConsole/Models/Data/EventIntegrations/IntegrationMessageTests.cs @@ -1,9 +1,9 @@ using System.Text.Json; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; using Xunit; -namespace Bit.Core.Test.Models.Data.Integrations; +namespace Bit.Core.Test.Models.Data.EventIntegrations; public class IntegrationMessageTests { @@ -14,7 +14,7 @@ public class IntegrationMessageTests { var message = new IntegrationMessage { - Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"), + Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"), MessageId = _messageId, RetryCount = 2, RenderedTemplate = string.Empty, @@ -34,7 +34,7 @@ public class IntegrationMessageTests { var message = new IntegrationMessage { - Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"), + Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"), MessageId = _messageId, RenderedTemplate = "This is the message", IntegrationType = IntegrationType.Webhook, @@ -45,6 +45,7 @@ public class IntegrationMessageTests var json = message.ToJson(); var result = IntegrationMessage.FromJson(json); + Assert.NotNull(result); Assert.Equal(message.Configuration, result.Configuration); Assert.Equal(message.MessageId, result.MessageId); Assert.Equal(message.RenderedTemplate, result.RenderedTemplate); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs index 41486e1c00..b9f6964123 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs @@ -156,6 +156,24 @@ public class UpdateGroupCommandTests () => sutProvider.Sut.UpdateGroupAsync(group, organization, collectionAccess)); } + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithDefaultUserCollectionType_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization, List collectionAccess) + { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + + // Return collections with DefaultUserCollection type + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = group.OrganizationId, Type = CollectionType.DefaultUserCollection }).ToList()); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateGroupAsync(group, organization, collectionAccess)); + Assert.Contains("You cannot modify group access for collections with the type as DefaultUserCollection.", exception.Message); + } + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] public async Task UpdateGroup_MemberBelongsToDifferentOrganization_Throws(SutProvider sutProvider, Group group, Group oldGroup, Organization organization, IEnumerable userAccess) diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs index 366d8cb2d6..0bb38f7d0b 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs @@ -10,6 +10,7 @@ using Bit.Core.Billing.Enums; 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.Repositories; using Bit.Core.Services; @@ -442,4 +443,98 @@ public class ConfirmOrganizationUserCommandTests await sutProvider.GetDependency().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager); await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is>(users => users.Contains(orgUser) && users.Count == 1)); } + + [Theory, BitAutoData] + public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyApplicable_WithValidCollectionName_CreatesDefaultCollection( + Organization organization, OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, + string key, string collectionName, SutProvider sutProvider) + { + organization.PlanType = PlanType.EnterpriseAnnually; + orgUser.OrganizationId = confirmingUser.OrganizationId = organization.Id; + orgUser.UserId = user.Id; + + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true); + + sutProvider.GetDependency() + .GetAsync(user.Id) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [organization.Id])); + + await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is(c => c.Name == collectionName && + c.OrganizationId == organization.Id && + c.Type == CollectionType.DefaultUserCollection), + Arg.Is>(groups => groups == null), + Arg.Is>(u => + u.Count() == 1 && + u.First().Id == orgUser.Id && + u.First().Manage == true)); + } + + [Theory, BitAutoData] + public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyApplicable_WithInvalidCollectionName_DoesNotCreateDefaultCollection( + Organization org, OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, + string key, SutProvider sutProvider) + { + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; + orgUser.UserId = user.Id; + + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true); + + sutProvider.GetDependency() + .GetAsync(user.Id) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [org.Id])); + + await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, ""); + + await sutProvider.GetDependency() + .DidNotReceive() + .CreateAsync(Arg.Any(), Arg.Any>(), Arg.Any>()); + } + + [Theory, BitAutoData] + public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithOrganizationDataOwnershipPolicyNotApplicable_DoesNotCreateDefaultCollection( + Organization org, OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, + string key, string collectionName, SutProvider sutProvider) + { + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; + orgUser.UserId = user.Id; + + sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); + sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true); + + sutProvider.GetDependency() + .GetAsync(user.Id) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [Guid.NewGuid()])); + + await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName); + + await sutProvider.GetDependency() + .DidNotReceive() + .CreateAsync(Arg.Any(), Arg.Any>(), Arg.Any>()); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs index cd03f9583b..bd112c5d40 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs @@ -27,8 +27,10 @@ public class UpdateOrganizationUserCommandTests List collections, List groups, SutProvider sutProvider) { user.Id = default(Guid); + var existingUserType = OrganizationUserType.User; + var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collections, groups)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collections, groups)); Assert.Contains("invite the user first", exception.Message.ToLowerInvariant()); } @@ -37,9 +39,10 @@ public class UpdateOrganizationUserCommandTests Guid? savingUserId, SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(user.Id).Returns(originalUser); + var existingUserType = OrganizationUserType.User; await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, null)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, null)); } [Theory, BitAutoData] @@ -55,8 +58,10 @@ public class UpdateOrganizationUserCommandTests .Returns(callInfo => callInfo.Arg>() .Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + var existingUserType = OrganizationUserType.User; + await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null)); } [Theory, BitAutoData] @@ -76,9 +81,9 @@ public class UpdateOrganizationUserCommandTests result.RemoveAt(0); return result; }); - + var existingUserType = OrganizationUserType.User; await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null)); } [Theory, BitAutoData] @@ -94,8 +99,10 @@ public class UpdateOrganizationUserCommandTests .Returns(callInfo => callInfo.Arg>() .Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + var existingUserType = OrganizationUserType.User; + await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess)); } [Theory, BitAutoData] @@ -115,9 +122,9 @@ public class UpdateOrganizationUserCommandTests result.RemoveAt(0); return result; }); - + var existingUserType = OrganizationUserType.User; await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); + () => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess)); } [Theory, BitAutoData] @@ -165,7 +172,9 @@ public class UpdateOrganizationUserCommandTests .GetCountByFreeOrganizationAdminUserAsync(newUserData.Id) .Returns(0); - await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups); + var existingUserType = OrganizationUserType.User; + + await sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, savingUser.UserId, collections, groups); var organizationService = sutProvider.GetDependency(); await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions( @@ -184,7 +193,7 @@ public class UpdateOrganizationUserCommandTests [Theory] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Owner)] - public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws( + public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsNotAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws( OrganizationUserType userType, OrganizationUser oldUserData, OrganizationUser newUserData, @@ -199,13 +208,60 @@ public class UpdateOrganizationUserCommandTests sutProvider.GetDependency() .GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value) .Returns(1); + var existingUserType = OrganizationUserType.User; // Assert var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(newUserData, null, null, null)); + () => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null)); Assert.Contains("User can only be an admin of one free organization.", exception.Message); } + [Theory] + [BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Owner)] + [BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Owner)] + public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws( + OrganizationUserType newUserType, + OrganizationUserType existingUserType, + OrganizationUser oldUserData, + OrganizationUser newUserData, + Organization organization, + SutProvider sutProvider) + { + organization.PlanType = PlanType.Free; + newUserData.Type = newUserType; + + Setup(sutProvider, organization, newUserData, oldUserData); + + sutProvider.GetDependency() + .GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value) + .Returns(2); + + // Assert + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null)); + Assert.Contains("User can only be an admin of one free organization.", exception.Message); + } + + [Theory, BitAutoData] + public async Task UpdateUserAsync_WithDefaultUserCollectionType_Throws(OrganizationUser user, OrganizationUser originalUser, + List collectionAccess, Guid? savingUserId, SutProvider sutProvider, + Organization organization) + { + Setup(sutProvider, organization, user, originalUser); + + // Return collections with DefaultUserCollection type + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = user.OrganizationId, Type = CollectionType.DefaultUserCollection }).ToList()); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, OrganizationUserType.User, savingUserId, collectionAccess, null)); + Assert.Contains("You cannot modify member access for collections with the type as DefaultUserCollection.", exception.Message); + } + private void Setup(SutProvider sutProvider, Organization organization, OrganizationUser newUser, OrganizationUser oldUser) { diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ResellerClientOrganizationSignUpCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ResellerClientOrganizationSignUpCommandTests.cs new file mode 100644 index 0000000000..55e5698ad4 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Organizations/OrganizationSignUp/ResellerClientOrganizationSignUpCommandTests.cs @@ -0,0 +1,185 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models; +using Bit.Core.Entities; +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.OrganizationSignUp; + +[SutProviderCustomize] +public class ResellerClientOrganizationSignUpCommandTests +{ + [Theory] + [BitAutoData] + public async Task SignUpResellerClientAsync_WithValidParameters_CreatesOrganizationSuccessfully( + Organization organization, + string ownerEmail, + SutProvider sutProvider) + { + var result = await sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail); + + Assert.NotNull(result.Organization); + Assert.False(result.Organization.Enabled); + Assert.Equal(OrganizationStatusType.Pending, result.Organization.Status); + Assert.NotNull(result.OwnerOrganizationUser); + Assert.Equal(ownerEmail, result.OwnerOrganizationUser.Email); + Assert.Equal(OrganizationUserType.Owner, result.OwnerOrganizationUser.Type); + Assert.Equal(OrganizationUserStatusType.Invited, result.OwnerOrganizationUser.Status); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is(o => + o.Id != default && + o.Name == organization.Name && + o.Enabled == false && + o.Status == OrganizationStatusType.Pending + ) + ); + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is(k => + k.OrganizationId == result.Organization.Id && + k.Type == OrganizationApiKeyType.Default && + !string.IsNullOrEmpty(k.ApiKey) + ) + ); + await sutProvider.GetDependency() + .Received(1) + .UpsertOrganizationAbilityAsync(Arg.Is(o => o.Id == result.Organization.Id)); + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is(u => + u.OrganizationId == result.Organization.Id && + u.Email == ownerEmail && + u.Type == OrganizationUserType.Owner && + u.Status == OrganizationUserStatusType.Invited && + u.UserId == null + ) + ); + await sutProvider.GetDependency() + .Received(1) + .SendInvitesAsync( + Arg.Is(r => + r.Users.Count() == 1 && + r.Users.First().Email == ownerEmail && + r.Organization.Id == result.Organization.Id && + r.InitOrganization == true + ) + ); + await sutProvider.GetDependency() + .Received(1) + .LogOrganizationUserEventAsync( + Arg.Is(u => u.Email == ownerEmail), + EventType.OrganizationUser_Invited + ); + } + + [Theory] + [BitAutoData] + public async Task SignUpResellerClientAsync_WhenOrganizationRepositoryThrows_PerformsCleanup( + Organization organization, + string ownerEmail, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .When(x => x.CreateAsync(Arg.Any())) + .Do(_ => throw new Exception()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail)); + + await AssertCleanupIsPerformed(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task SignUpResellerClientAsync_WhenOrganizationUserCreationFails_PerformsCleanup( + Organization organization, + string ownerEmail, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .When(x => x.CreateAsync(Arg.Any())) + .Do(_ => throw new Exception()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail)); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Any()); + await AssertCleanupIsPerformed(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task SignUpResellerClientAsync_WhenInvitationSendingFails_PerformsCleanup( + Organization organization, + string ownerEmail, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .When(x => x.SendInvitesAsync(Arg.Any())) + .Do(_ => throw new Exception()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail)); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Any()); + await AssertCleanupIsPerformed(sutProvider); + } + + [Theory] + [BitAutoData] + public async Task SignUpResellerClientAsync_WhenEventLoggingFails_PerformsCleanup( + Organization organization, + string ownerEmail, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .When(x => x.LogOrganizationUserEventAsync(Arg.Any(), Arg.Any())) + .Do(_ => throw new Exception()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpResellerClientAsync(organization, ownerEmail)); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .CreateAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .SendInvitesAsync(Arg.Any()); + await AssertCleanupIsPerformed(sutProvider); + } + + private static async Task AssertCleanupIsPerformed(SutProvider sutProvider) + { + await sutProvider.GetDependency() + .Received(1) + .CancelAndRecoverChargesAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .DeleteAsync(Arg.Any()); + await sutProvider.GetDependency() + .Received(1) + .DeleteOrganizationAbilityAsync(Arg.Any()); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirementFactoryTests.cs new file mode 100644 index 0000000000..95037efb97 --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/OrganizationDataOwnershipPolicyRequirementFactoryTests.cs @@ -0,0 +1,53 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +[SutProviderCustomize] +public class OrganizationDataOwnershipPolicyRequirementFactoryTests +{ + [Theory, BitAutoData] + public void State_WithNoPolicies_ReturnsAllowed(SutProvider sutProvider) + { + var actual = sutProvider.Sut.Create([]); + + Assert.Equal(OrganizationDataOwnershipState.Disabled, actual.State); + } + + [Theory, BitAutoData] + public void State_WithOrganizationDataOwnershipPolicies_ReturnsRestricted( + [PolicyDetails(PolicyType.OrganizationDataOwnership)] PolicyDetails[] policies, + SutProvider sutProvider) + { + var actual = sutProvider.Sut.Create(policies); + + Assert.Equal(OrganizationDataOwnershipState.Enabled, actual.State); + } + + [Theory, BitAutoData] + public void RequiresDefaultCollection_WithNoPolicies_ReturnsFalse( + Guid organizationId, + SutProvider sutProvider) + { + var actual = sutProvider.Sut.Create([]); + + Assert.False(actual.RequiresDefaultCollection(organizationId)); + } + + [Theory, BitAutoData] + public void RequiresDefaultCollection_WithOrganizationDataOwnershipPolicies_ReturnsCorrectResult( + [PolicyDetails(PolicyType.OrganizationDataOwnership)] PolicyDetails[] policies, + Guid nonPolicyOrganizationId, + SutProvider sutProvider) + { + var actual = sutProvider.Sut.Create(policies); + + Assert.True(actual.RequiresDefaultCollection(policies[0].OrganizationId)); + Assert.False(actual.RequiresDefaultCollection(nonPolicyOrganizationId)); + } +} diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirementFactoryTests.cs deleted file mode 100644 index 2ce75ca61e..0000000000 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/PersonalOwnershipPolicyRequirementFactoryTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Bit.Core.AdminConsole.Enums; -using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; -using Bit.Core.Test.AdminConsole.AutoFixture; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using Xunit; - -namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; - -[SutProviderCustomize] -public class PersonalOwnershipPolicyRequirementFactoryTests -{ - [Theory, BitAutoData] - public void DisablePersonalOwnership_WithNoPolicies_ReturnsFalse(SutProvider sutProvider) - { - var actual = sutProvider.Sut.Create([]); - - Assert.False(actual.DisablePersonalOwnership); - } - - [Theory, BitAutoData] - public void DisablePersonalOwnership_WithPersonalOwnershipPolicies_ReturnsTrue( - [PolicyDetails(PolicyType.PersonalOwnership)] PolicyDetails[] policies, - SutProvider sutProvider) - { - var actual = sutProvider.Sut.Create(policies); - - Assert.True(actual.DisablePersonalOwnership); - } -} diff --git a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs b/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs index b1eb117cf0..32a305266d 100644 --- a/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/AzureServiceBusIntegrationListenerServiceTests.cs @@ -1,7 +1,7 @@ #nullable enable using Azure.Messaging.ServiceBus; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = 0; var result = new IntegrationHandlerResult(false, message); @@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = _maxRetries; var result = new IntegrationHandlerResult(false, message); result.Retryable = true; @@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); message.RetryCount = 0; var result = new IntegrationHandlerResult(false, message); result.Retryable = true; - result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); _handler.HandleAsync(Arg.Any()).Returns(result); var expected = (IntegrationMessage)IntegrationMessage.FromJson(message.ToJson())!; @@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); var result = new IntegrationHandlerResult(true, message); _handler.HandleAsync(Arg.Any()).Returns(result); diff --git a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs index 0962df52cd..e4ffabf691 100644 --- a/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/EventIntegrationHandlerTests.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -10,6 +10,7 @@ using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; +using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -25,6 +26,8 @@ public class EventIntegrationHandlerTests private const string _url = "https://localhost"; private const string _url2 = "https://example.com"; private readonly IEventIntegrationPublisher _eventIntegrationPublisher = Substitute.For(); + private readonly ILogger> _logger = + Substitute.For>>(); private SutProvider> GetSutProvider( List configurations) @@ -37,6 +40,7 @@ public class EventIntegrationHandlerTests .SetDependency(configurationRepository) .SetDependency(_eventIntegrationPublisher) .SetDependency(IntegrationType.Webhook) + .SetDependency(_logger) .Create(); } @@ -62,7 +66,7 @@ public class EventIntegrationHandlerTests { var config = Substitute.For(); config.Configuration = null; - config.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url }); + config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url }); config.Template = template; return [config]; @@ -72,16 +76,39 @@ public class EventIntegrationHandlerTests { var config = Substitute.For(); config.Configuration = null; - config.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url }); + config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url }); config.Template = template; var config2 = Substitute.For(); config2.Configuration = null; - config2.IntegrationConfiguration = JsonSerializer.Serialize(new { url = _url2 }); + config2.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url2 }); config2.Template = template; return [config, config2]; } + private static List InvalidFilterConfiguration() + { + var config = Substitute.For(); + config.Configuration = null; + config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url }); + config.Template = _templateBase; + config.Filters = "Invalid Configuration!"; + + return [config]; + } + + private static List ValidFilterConfiguration() + { + var config = Substitute.For(); + config.Configuration = null; + config.IntegrationConfiguration = JsonSerializer.Serialize(new { Url = _url }); + config.Template = _templateBase; + config.Filters = JsonSerializer.Serialize(new IntegrationFilterGroup() { }); + + return [config]; + } + + [Theory, BitAutoData] public async Task HandleEventAsync_BaseTemplateNoConfigurations_DoesNothing(EventMessage eventMessage) { @@ -92,7 +119,7 @@ public class EventIntegrationHandlerTests } [Theory, BitAutoData] - public async Task HandleEventAsync_BaseTemplateOneConfiguration_CallsProcessEventIntegrationAsync(EventMessage eventMessage) + public async Task HandleEventAsync_BaseTemplateOneConfiguration_PublishesIntegrationMessage(EventMessage eventMessage) { var sutProvider = GetSutProvider(OneConfiguration(_templateBase)); @@ -109,6 +136,27 @@ public class EventIntegrationHandlerTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any()); } + [Theory, BitAutoData] + public async Task HandleEventAsync_BaseTemplateTwoConfigurations_PublishesIntegrationMessages(EventMessage eventMessage) + { + var sutProvider = GetSutProvider(TwoConfigurations(_templateBase)); + + await sutProvider.Sut.HandleEventAsync(eventMessage); + + var expectedMessage = EventIntegrationHandlerTests.expectedMessage( + $"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}" + ); + await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is( + AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" }))); + + expectedMessage.Configuration = new WebhookIntegrationConfigurationDetails(_url2); + await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is( + AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" }))); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any()); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(Arg.Any()); + } + [Theory, BitAutoData] public async Task HandleEventAsync_ActingUserTemplate_LoadsUserFromRepository(EventMessage eventMessage) { @@ -170,6 +218,50 @@ public class EventIntegrationHandlerTests await sutProvider.GetDependency().Received(1).GetByIdAsync(eventMessage.UserId ?? Guid.Empty); } + [Theory, BitAutoData] + public async Task HandleEventAsync_FilterReturnsFalse_DoesNothing(EventMessage eventMessage) + { + var sutProvider = GetSutProvider(ValidFilterConfiguration()); + sutProvider.GetDependency().EvaluateFilterGroup( + Arg.Any(), Arg.Any()).Returns(false); + + await sutProvider.Sut.HandleEventAsync(eventMessage); + Assert.Empty(_eventIntegrationPublisher.ReceivedCalls()); + } + + [Theory, BitAutoData] + public async Task HandleEventAsync_FilterReturnsTrue_PublishesIntegrationMessage(EventMessage eventMessage) + { + var sutProvider = GetSutProvider(ValidFilterConfiguration()); + sutProvider.GetDependency().EvaluateFilterGroup( + Arg.Any(), Arg.Any()).Returns(true); + + await sutProvider.Sut.HandleEventAsync(eventMessage); + + var expectedMessage = EventIntegrationHandlerTests.expectedMessage( + $"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}" + ); + + Assert.Single(_eventIntegrationPublisher.ReceivedCalls()); + await _eventIntegrationPublisher.Received(1).PublishAsync(Arg.Is( + AssertHelper.AssertPropertyEqual(expectedMessage, new[] { "MessageId" }))); + } + + [Theory, BitAutoData] + public async Task HandleEventAsync_InvalidFilter_LogsErrorDoesNothing(EventMessage eventMessage) + { + var sutProvider = GetSutProvider(InvalidFilterConfiguration()); + + await sutProvider.Sut.HandleEventAsync(eventMessage); + Assert.Empty(_eventIntegrationPublisher.ReceivedCalls()); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } + [Theory, BitAutoData] public async Task HandleManyEventsAsync_BaseTemplateNoConfigurations_DoesNothing(List eventMessages) { @@ -180,7 +272,7 @@ public class EventIntegrationHandlerTests } [Theory, BitAutoData] - public async Task HandleManyEventsAsync_BaseTemplateOneConfiguration_CallsProcessEventIntegrationAsync(List eventMessages) + public async Task HandleManyEventsAsync_BaseTemplateOneConfiguration_PublishesIntegrationMessages(List eventMessages) { var sutProvider = GetSutProvider(OneConfiguration(_templateBase)); @@ -197,7 +289,7 @@ public class EventIntegrationHandlerTests } [Theory, BitAutoData] - public async Task HandleManyEventsAsync_BaseTemplateTwoConfigurations_CallsProcessEventIntegrationAsyncMultipleTimes( + public async Task HandleManyEventsAsync_BaseTemplateTwoConfigurations_PublishesIntegrationMessages( List eventMessages) { var sutProvider = GetSutProvider(TwoConfigurations(_templateBase)); diff --git a/test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs b/test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs new file mode 100644 index 0000000000..b408bc1501 --- /dev/null +++ b/test/Core.Test/AdminConsole/Services/IntegrationFilterFactoryTests.cs @@ -0,0 +1,46 @@ +using Bit.Core.Models.Data; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Services; + +public class IntegrationFilterFactoryTests +{ + [Theory, BitAutoData] + public void BuildEqualityFilter_ReturnsCorrectMatch(EventMessage message) + { + var different = Guid.NewGuid(); + var expected = Guid.NewGuid(); + message.UserId = expected; + + var filter = IntegrationFilterFactory.BuildEqualityFilter("UserId"); + + Assert.True(filter(message, expected)); + Assert.False(filter(message, different)); + } + + [Theory, BitAutoData] + public void BuildEqualityFilter_UserIdIsNull_ReturnsFalse(EventMessage message) + { + message.UserId = null; + + var filter = IntegrationFilterFactory.BuildEqualityFilter("UserId"); + + Assert.False(filter(message, Guid.NewGuid())); + } + + [Theory, BitAutoData] + public void BuildInFilter_ReturnsCorrectMatch(EventMessage message) + { + var match = Guid.NewGuid(); + message.UserId = match; + var inList = new List { Guid.NewGuid(), match, Guid.NewGuid() }; + var outList = new List { Guid.NewGuid(), Guid.NewGuid() }; + + var filter = IntegrationFilterFactory.BuildInFilter("UserId"); + + Assert.True(filter(message, inList)); + Assert.False(filter(message, outList)); + } +} diff --git a/test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs b/test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs new file mode 100644 index 0000000000..4143469a4b --- /dev/null +++ b/test/Core.Test/AdminConsole/Services/IntegrationFilterServiceTests.cs @@ -0,0 +1,399 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; +using Bit.Core.Models.Data; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Services; + +public class IntegrationFilterServiceTests +{ + private readonly IntegrationFilterService _service = new(); + + [Theory, BitAutoData] + public void EvaluateFilterGroup_EqualsUserId_Matches(EventMessage eventMessage) + { + var userId = Guid.NewGuid(); + eventMessage.UserId = userId; + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "UserId", + Operation = IntegrationFilterOperation.Equals, + Value = userId + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.True(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_EqualsUserId_DoesNotMatch(EventMessage eventMessage) + { + eventMessage.UserId = Guid.NewGuid(); + var otherUserId = Guid.NewGuid(); + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "UserId", + Operation = IntegrationFilterOperation.Equals, + Value = otherUserId + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NotEqualsUniqueUserId_ReturnsTrue(EventMessage eventMessage) + { + var otherId = Guid.NewGuid(); + eventMessage.UserId = otherId; + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "UserId", + Operation = IntegrationFilterOperation.NotEquals, + Value = Guid.NewGuid() + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.True(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NotEqualsMatchingUserId_ReturnsFalse(EventMessage eventMessage) + { + var id = Guid.NewGuid(); + eventMessage.UserId = id; + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "UserId", + Operation = IntegrationFilterOperation.NotEquals, + Value = id + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_InCollectionId_Matches(EventMessage eventMessage) + { + var id = Guid.NewGuid(); + eventMessage.CollectionId = id; + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.In, + Value = new Guid?[] { Guid.NewGuid(), id } + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.True(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_InCollectionId_DoesNotMatch(EventMessage eventMessage) + { + eventMessage.CollectionId = Guid.NewGuid(); + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.In, + Value = new Guid?[] { Guid.NewGuid(), Guid.NewGuid() } + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NotInCollectionIdUniqueId_ReturnsTrue(EventMessage eventMessage) + { + eventMessage.CollectionId = Guid.NewGuid(); + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.NotIn, + Value = new Guid?[] { Guid.NewGuid(), Guid.NewGuid() } + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.True(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NotInCollectionIdPresent_ReturnsFalse(EventMessage eventMessage) + { + var matchId = Guid.NewGuid(); + eventMessage.CollectionId = matchId; + + var group = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.NotIn, + Value = new Guid?[] { Guid.NewGuid(), matchId } + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + + var jsonGroup = JsonSerializer.Serialize(group); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.False(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NestedGroups_AllMatch(EventMessage eventMessage) + { + var id = Guid.NewGuid(); + var collectionId = Guid.NewGuid(); + eventMessage.UserId = id; + eventMessage.CollectionId = collectionId; + + var nestedGroup = new IntegrationFilterGroup + { + AndOperator = true, + Rules = + [ + new() { Property = "UserId", Operation = IntegrationFilterOperation.Equals, Value = id }, + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.In, + Value = new Guid?[] { collectionId, Guid.NewGuid() } + } + ] + }; + + var topGroup = new IntegrationFilterGroup + { + AndOperator = true, + Groups = [nestedGroup] + }; + + var result = _service.EvaluateFilterGroup(topGroup, eventMessage); + Assert.True(result); + + var jsonGroup = JsonSerializer.Serialize(topGroup); + var roundtrippedGroup = JsonSerializer.Deserialize(jsonGroup); + Assert.NotNull(roundtrippedGroup); + Assert.True(_service.EvaluateFilterGroup(roundtrippedGroup, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_UnknownProperty_ReturnsFalse(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Rules = + [ + new() { Property = "NotARealProperty", Operation = IntegrationFilterOperation.Equals, Value = "test" } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_UnsupportedOperation_ReturnsFalse(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Rules = + [ + new() + { + Property = "UserId", + Operation = (IntegrationFilterOperation)999, // Unknown operation + Value = eventMessage.UserId + } + ] + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_WrongTypeForInList_ThrowsException(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Rules = + [ + new() + { + Property = "CollectionId", + Operation = IntegrationFilterOperation.In, + Value = "not an array" // Should be Guid[] + } + ] + }; + + Assert.Throws(() => + _service.EvaluateFilterGroup(group, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_NullValue_ThrowsException(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Rules = + [ + new() + { + Property = "UserId", + Operation = IntegrationFilterOperation.Equals, + Value = null + } + ] + }; + + Assert.Throws(() => + _service.EvaluateFilterGroup(group, eventMessage)); + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_EmptyRuleList_ReturnsTrue(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Rules = [], + Groups = [], + AndOperator = true + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.True(result); // Nothing to fail, returns true by design + } + + [Theory, BitAutoData] + public void EvaluateFilterGroup_InvalidNestedGroup_ReturnsFalse(EventMessage eventMessage) + { + var group = new IntegrationFilterGroup + { + Groups = + [ + new() + { + Rules = + [ + new() + { + Property = "Nope", + Operation = IntegrationFilterOperation.Equals, + Value = "bad" + } + ] + } + ], + AndOperator = true + }; + + var result = _service.EvaluateFilterGroup(group, eventMessage); + Assert.False(result); + } +} diff --git a/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs index 10e42c92cc..b4a384d798 100644 --- a/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/IntegrationHandlerTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Enums; using Bit.Core.Services; using Xunit; @@ -14,7 +14,7 @@ public class IntegrationHandlerTests var sut = new TestIntegrationHandler(); var expected = new IntegrationMessage() { - Configuration = new WebhookIntegrationConfigurationDetails("https://localhost"), + Configuration = new WebhookIntegrationConfigurationDetails("https://localhost", "Bearer", "AUTH-TOKEN"), MessageId = "TestMessageId", IntegrationType = IntegrationType.Webhook, RenderedTemplate = "Template", diff --git a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs b/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs index 92a51e1831..bb3f211afa 100644 --- a/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/RabbitMqIntegrationListenerServiceTests.cs @@ -1,9 +1,10 @@ using System.Text; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; +using Microsoft.Extensions.Time.Testing; using NSubstitute; using RabbitMQ.Client; using RabbitMQ.Client.Events; @@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests private const string _queueName = "test_queue"; private const string _retryQueueName = "test_queue_retry"; private const string _routingKey = "test_routing_key"; + private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); private readonly IIntegrationHandler _handler = Substitute.For(); private readonly IRabbitMqService _rabbitMqService = Substitute.For(); private SutProvider GetSutProvider() { - return new SutProvider() + var sutProvider = new SutProvider() .SetDependency(_handler) .SetDependency(_rabbitMqService) .SetDependency(_queueName, "queueName") .SetDependency(_retryQueueName, "retryQueueName") .SetDependency(_routingKey, "routingKey") .SetDependency(_maxRetries, "maxRetries") + .WithFakeTimeProvider() .Create(); + sutProvider.GetDependency().SetUtcNow(_now); + + return sutProvider; } [Fact] @@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = 0; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = _maxRetries; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; message.RetryCount = 0; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, @@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests ); var result = new IntegrationHandlerResult(false, message); result.Retryable = true; - result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); + result.DelayUntilDate = _now.AddMinutes(1); _handler.HandleAsync(Arg.Any()).Returns(result); var expected = IntegrationMessage.FromJson(message.ToJson()); @@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1); + message.DelayUntilDate = null; var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, deliveryTag: 0, @@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests var cancellationToken = CancellationToken.None; await sutProvider.Sut.StartAsync(cancellationToken); - message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1); + message.DelayUntilDate = _now.AddMinutes(1); var eventArgs = new BasicDeliverEventArgs( consumerTag: string.Empty, deliveryTag: 0, diff --git a/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs index 9f66e2eb2f..dab6c41b61 100644 --- a/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/SlackIntegrationHandlerTests.cs @@ -1,4 +1,4 @@ -using Bit.Core.AdminConsole.Models.Data.Integrations; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs b/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs index 7870f543d1..9a03fb28f0 100644 --- a/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/WebhookIntegrationHandlerTests.cs @@ -1,10 +1,12 @@ using System.Net; -using Bit.Core.AdminConsole.Models.Data.Integrations; +using System.Net.Http.Headers; +using Bit.Core.AdminConsole.Models.Data.EventIntegrations; 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 Microsoft.Extensions.Time.Testing; using NSubstitute; using Xunit; @@ -15,6 +17,8 @@ public class WebhookIntegrationHandlerTests { private readonly MockedHttpMessageHandler _handler; private readonly HttpClient _httpClient; + private const string _scheme = "Bearer"; + private const string _token = "AUTH_TOKEN"; private const string _webhookUrl = "http://localhost/test/event"; public WebhookIntegrationHandlerTests() @@ -33,11 +37,12 @@ public class WebhookIntegrationHandlerTests return new SutProvider() .SetDependency(clientFactory) + .WithFakeTimeProvider() .Create(); } [Theory, BitAutoData] - public async Task HandleAsync_SuccessfulRequest_ReturnsSuccess(IntegrationMessage message) + public async Task HandleAsync_SuccessfulRequestWithoutAuth_ReturnsSuccess(IntegrationMessage message) { var sutProvider = GetSutProvider(); message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); @@ -57,15 +62,46 @@ public class WebhookIntegrationHandlerTests var returned = await request.Content.ReadAsStringAsync(); Assert.Equal(HttpMethod.Post, request.Method); + Assert.Null(request.Headers.Authorization); Assert.Equal(_webhookUrl, request.RequestUri.ToString()); AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned); } [Theory, BitAutoData] - public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage message) + public async Task HandleAsync_SuccessfulRequestWithAuthorizationHeader_ReturnsSuccess(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); + message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token); + + var result = await sutProvider.Sut.HandleAsync(message); + + Assert.True(result.Success); + Assert.Equal(result.Message, message); + + sutProvider.GetDependency().Received(1).CreateClient( + Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName)) + ); + + Assert.Single(_handler.CapturedRequests); + var request = _handler.CapturedRequests[0]; + Assert.NotNull(request); + var returned = await request.Content.ReadAsStringAsync(); + + Assert.Equal(HttpMethod.Post, request.Method); + Assert.Equal(new AuthenticationHeaderValue(_scheme, _token), request.Headers.Authorization); + Assert.Equal(_webhookUrl, request.RequestUri.ToString()); + AssertHelper.AssertPropertyEqual(message.RenderedTemplate, returned); + } + + [Theory, BitAutoData] + public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage message) + { + var sutProvider = GetSutProvider(); + var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); + var retryAfter = now.AddSeconds(60); + + sutProvider.GetDependency().SetUtcNow(now); + message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token); _handler.Fallback .WithStatusCode(HttpStatusCode.TooManyRequests) @@ -78,19 +114,21 @@ public class WebhookIntegrationHandlerTests Assert.True(result.Retryable); Assert.Equal(result.Message, message); Assert.True(result.DelayUntilDate.HasValue); - Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); + Assert.Equal(retryAfter, result.DelayUntilDate.Value); Assert.Equal("Too Many Requests", result.FailureReason); } [Theory, BitAutoData] - public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage message) + public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); + var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc); + var retryAfter = now.AddSeconds(60); + message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token); _handler.Fallback .WithStatusCode(HttpStatusCode.TooManyRequests) - .WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123 + .WithHeader("Retry-After", retryAfter.ToString("r")) .WithContent(new StringContent("testtest")); var result = await sutProvider.Sut.HandleAsync(message); @@ -99,7 +137,7 @@ public class WebhookIntegrationHandlerTests Assert.True(result.Retryable); Assert.Equal(result.Message, message); Assert.True(result.DelayUntilDate.HasValue); - Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61)); + Assert.Equal(retryAfter, result.DelayUntilDate.Value); Assert.Equal("Too Many Requests", result.FailureReason); } @@ -107,7 +145,7 @@ public class WebhookIntegrationHandlerTests public async Task HandleAsync_InternalServerError_ReturnsFailureSetsRetryable(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); + message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token); _handler.Fallback .WithStatusCode(HttpStatusCode.InternalServerError) @@ -126,7 +164,7 @@ public class WebhookIntegrationHandlerTests public async Task HandleAsync_UnexpectedRedirect_ReturnsFailureNotRetryable(IntegrationMessage message) { var sutProvider = GetSutProvider(); - message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl); + message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl, _scheme, _token); _handler.Fallback .WithStatusCode(HttpStatusCode.TemporaryRedirect) diff --git a/test/Core.Test/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLoggerTests.cs b/test/Core.Test/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLoggerTests.cs new file mode 100644 index 0000000000..a8f14bf1dc --- /dev/null +++ b/test/Core.Test/AdminConsole/Utilities/DebuggingInstruments/UserInviteDebuggingLoggerTests.cs @@ -0,0 +1,113 @@ +using Bit.Core.AdminConsole.Utilities.DebuggingInstruments; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.Utilities.DebuggingInstruments; + +public class UserInviteDebuggingLoggerTests +{ + [Fact] + public void LogUserInviteStateDiagnostics_WhenInvitedUserHasNoEmail_LogsWarning() + { + // Arrange + var organizationUser = new OrganizationUser + { + OrganizationId = Guid.Parse("3e1f2196-9ad6-4ba7-b69d-ba33bc25f774"), + Status = OrganizationUserStatusType.Invited, + Email = string.Empty, + UserId = Guid.Parse("93fbddd1-e96d-491d-a38b-6966ff59ac28"), + Id = Guid.Parse("326f043f-afdc-47e5-9646-a76ab709b69a"), + }; + + var logger = Substitute.For(); + + // Act + logger.LogUserInviteStateDiagnostics(organizationUser); + + // Assert + logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Is(errorMessage => + errorMessage.ToString().Contains("Warning invalid invited state") + && errorMessage.ToString().Contains(organizationUser.OrganizationId.ToString()) + && errorMessage.ToString().Contains(organizationUser.UserId.ToString()) + && errorMessage.ToString().Contains(organizationUser.Id.ToString()) + ), + null, + Arg.Any>() + ); + } + + public static IEnumerable ConfirmedOrAcceptedTestCases => + [ + new object[] { OrganizationUserStatusType.Accepted }, + new object[] { OrganizationUserStatusType.Confirmed }, + ]; + + [Theory] + [MemberData(nameof(ConfirmedOrAcceptedTestCases))] + public void LogUserInviteStateDiagnostics_WhenNonInvitedUserHasEmail_LogsWarning(OrganizationUserStatusType userStatusType) + { + // Arrange + var organizationUser = new OrganizationUser + { + OrganizationId = Guid.Parse("3e1f2196-9ad6-4ba7-b69d-ba33bc25f774"), + Status = userStatusType, + Email = "someone@example.com", + UserId = Guid.Parse("93fbddd1-e96d-491d-a38b-6966ff59ac28"), + Id = Guid.Parse("326f043f-afdc-47e5-9646-a76ab709b69a"), + }; + + var logger = Substitute.For(); + + // Act + logger.LogUserInviteStateDiagnostics(organizationUser); + + // Assert + logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Is(errorMessage => + errorMessage.ToString().Contains("Warning invalid confirmed or accepted state") + && errorMessage.ToString().Contains(organizationUser.OrganizationId.ToString()) + && errorMessage.ToString().Contains(organizationUser.UserId.ToString()) + && errorMessage.ToString().Contains(organizationUser.Id.ToString()) + // Ensure that no PII is included in the log. + && !errorMessage.ToString().Contains(organizationUser.Email) + ), + null, + Arg.Any>() + ); + } + + + public static List ShouldNotLogTestCases => + [ + new object[] { new OrganizationUser { Status = OrganizationUserStatusType.Accepted, Email = null } }, + new object[] { new OrganizationUser { Status = OrganizationUserStatusType.Confirmed, Email = null } }, + new object[] { new OrganizationUser { Status = OrganizationUserStatusType.Invited, Email = "someone@example.com" } }, + new object[] { new OrganizationUser { Status = OrganizationUserStatusType.Revoked, Email = null } }, + ]; + + [Theory] + [MemberData(nameof(ShouldNotLogTestCases))] + public void LogUserInviteStateDiagnostics_WhenStateAreValid_ShouldNotLog(OrganizationUser user) + { + var logger = Substitute.For(); + + // Act + logger.LogUserInviteStateDiagnostics(user); + + // Assert + logger.DidNotReceive().Log( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>()); + } +} diff --git a/test/Core.Test/Dirt/ReportFeatures/AddOrganizationReportCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/AddOrganizationReportCommandTests.cs new file mode 100644 index 0000000000..618558142b --- /dev/null +++ b/test/Core.Test/Dirt/ReportFeatures/AddOrganizationReportCommandTests.cs @@ -0,0 +1,133 @@ + +using AutoFixture; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Dirt.ReportFeatures; + +[SutProviderCustomize] +public class AddOrganizationReportCommandTests +{ + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_ShouldReturnOrganizationReport( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Create(); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(fixture.Create()); + + sutProvider.GetDependency() + .CreateAsync(Arg.Any()) + .Returns(c => c.Arg()); + + // Act + var result = await sutProvider.Sut.AddOrganizationReportAsync(request); + + // Assert + Assert.NotNull(result); + } + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Create(); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(null as Organization); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.AddOrganizationReportAsync(request)); + Assert.Equal("Invalid Organization", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_WithInvalidUrl_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .Without(_ => _.ReportData) + .Create(); + + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(fixture.Create()); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.AddOrganizationReportAsync(request)); + Assert.Equal("Report Data is required", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_Multiples_WithInvalidOrganizationId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Create(); + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(null as Organization); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.AddOrganizationReportAsync(request)); + Assert.Equal("Invalid Organization", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_Multiples_WithInvalidUrl_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .Without(_ => _.ReportData) + .Create(); + + sutProvider.GetDependency() + .GetByIdAsync(Arg.Any()) + .Returns(fixture.Create()); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.AddOrganizationReportAsync(request)); + Assert.Equal("Report Data is required", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task AddOrganizationReportAsync_WithNullOrganizationId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .With(x => x.OrganizationId, default(Guid)) + .Create(); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.AddOrganizationReportAsync(request)); + Assert.Equal("Invalid Organization", exception.Message); + } +} diff --git a/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs index be54bc5310..849732685c 100644 --- a/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/AddPasswordHealthReportApplicationCommandTests.cs @@ -1,9 +1,9 @@ using AutoFixture; using Bit.Core.AdminConsole.Entities; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; diff --git a/test/Core.Test/Dirt/ReportFeatures/DeleteOrganizationReportCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/DeleteOrganizationReportCommandTests.cs new file mode 100644 index 0000000000..f6a5c13be9 --- /dev/null +++ b/test/Core.Test/Dirt/ReportFeatures/DeleteOrganizationReportCommandTests.cs @@ -0,0 +1,194 @@ +using AutoFixture; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures; +using Bit.Core.Dirt.Reports.ReportFeatures.Requests; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Dirt.ReportFeatures; + +[SutProviderCustomize] +public class DeleteOrganizationReportCommandTests +{ + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withValidRequest_Success( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var OrganizationReports = fixture.CreateMany(2).ToList(); + // only take one id from the list - we only want to drop one record + var request = fixture.Build() + .With(x => x.OrganizationReportIds, + OrganizationReports.Select(x => x.Id).Take(1).ToList()) + .Create(); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(OrganizationReports); + + // Act + await sutProvider.Sut.DropOrganizationReportAsync(request); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .GetByOrganizationIdAsync(request.OrganizationId); + + await sutProvider.GetDependency() + .Received(1) + .DeleteAsync(Arg.Is(_ => + request.OrganizationReportIds.Contains(_.Id))); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withValidRequest_nothingToDrop( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var OrganizationReports = fixture.CreateMany(2).ToList(); + // we are passing invalid data + var request = fixture.Build() + .With(x => x.OrganizationReportIds, new List { Guid.NewGuid() }) + .Create(); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(OrganizationReports); + + // Act + await sutProvider.Sut.DropOrganizationReportAsync(request); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .GetByOrganizationIdAsync(request.OrganizationId); + + await sutProvider.GetDependency() + .Received(0) + .DeleteAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withNodata_fails( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + // we are passing invalid data + var request = fixture.Build() + .Create(); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(null as List); + + // Act + await Assert.ThrowsAsync(() => + sutProvider.Sut.DropOrganizationReportAsync(request)); + + // Assert + await sutProvider.GetDependency() + .Received(1) + .GetByOrganizationIdAsync(request.OrganizationId); + + await sutProvider.GetDependency() + .Received(0) + .DeleteAsync(Arg.Any()); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withInvalidOrganizationId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Create(); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(null as List); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withInvalidOrganizationReportId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Create(); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(new List()); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withNullOrganizationId_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .With(x => x.OrganizationId, default(Guid)) + .Create(); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withNullOrganizationReportIds_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .With(x => x.OrganizationReportIds, default(List)) + .Create(); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withEmptyOrganizationReportIds_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var request = fixture.Build() + .With(x => x.OrganizationReportIds, new List()) + .Create(); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + + [Theory, BitAutoData] + public async Task DropOrganizationReportAsync_withEmptyRequest_ShouldThrowError( + SutProvider sutProvider) + { + // Arrange + var request = new DropOrganizationReportRequest(); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.DropOrganizationReportAsync(request)); + Assert.Equal("No data found.", exception.Message); + } + +} diff --git a/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs b/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs index 7756995805..6265f463c7 100644 --- a/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/DeletePasswordHealthReportApplicationCommandTests.cs @@ -1,8 +1,8 @@ using AutoFixture; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; using Bit.Core.Dirt.Reports.ReportFeatures.Requests; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/Dirt/ReportFeatures/GetOrganizationReportQueryTests.cs b/test/Core.Test/Dirt/ReportFeatures/GetOrganizationReportQueryTests.cs new file mode 100644 index 0000000000..19d020be12 --- /dev/null +++ b/test/Core.Test/Dirt/ReportFeatures/GetOrganizationReportQueryTests.cs @@ -0,0 +1,188 @@ +using AutoFixture; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Reports.ReportFeatures; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Exceptions; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Dirt.ReportFeatures; + +[SutProviderCustomize] +public class GetOrganizationReportQueryTests +{ + [Theory] + [BitAutoData] + public async Task GetOrganizationReportAsync_WithValidOrganizationId_ShouldReturnOrganizationReport( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = fixture.Create(); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(fixture.CreateMany(2).ToList()); + + // Act + var result = await sutProvider.Sut.GetOrganizationReportAsync(organizationId); + + // Assert + Assert.NotNull(result); + Assert.True(result.Count() == 2); + } + + [Theory] + [BitAutoData] + public async Task GetOrganizationReportAsync_WithInvalidOrganizationId_ShouldFail( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Is(x => x == Guid.Empty)) + .Returns(new List()); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReportAsync(Guid.Empty)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task GetLatestOrganizationReportAsync_WithValidOrganizationId_ShouldReturnOrganizationReport( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = fixture.Create(); + sutProvider.GetDependency() + .GetLatestByOrganizationIdAsync(Arg.Any()) + .Returns(fixture.Create()); + + // Act + var result = await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId); + + // Assert + Assert.NotNull(result); + } + + [Theory] + [BitAutoData] + public async Task GetLatestOrganizationReportAsync_WithInvalidOrganizationId_ShouldFail( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + sutProvider.GetDependency() + .GetLatestByOrganizationIdAsync(Arg.Is(x => x == Guid.Empty)) + .Returns(default(OrganizationReport)); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReportAsync(Guid.Empty)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task GetOrganizationReportAsync_WithNoReports_ShouldReturnEmptyList( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = fixture.Create(); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(Arg.Any()) + .Returns(new List()); + + // Act + var result = await sutProvider.Sut.GetOrganizationReportAsync(organizationId); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + [Theory] + [BitAutoData] + public async Task GetLatestOrganizationReportAsync_WithNoReports_ShouldReturnNull( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = fixture.Create(); + sutProvider.GetDependency() + .GetLatestByOrganizationIdAsync(Arg.Any()) + .Returns(default(OrganizationReport)); + + // Act + var result = await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId); + + // Assert + Assert.Null(result); + } + [Theory] + [BitAutoData] + public async Task GetOrganizationReportAsync_WithNullOrganizationId_ShouldThrowException( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = default(Guid); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReportAsync(organizationId)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } + [Theory] + [BitAutoData] + public async Task GetLatestOrganizationReportAsync_WithNullOrganizationId_ShouldThrowException( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = default(Guid); + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } + [Theory] + [BitAutoData] + public async Task GetOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowException( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = Guid.Empty; + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetOrganizationReportAsync(organizationId)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } + [Theory] + [BitAutoData] + public async Task GetLatestOrganizationReportAsync_WithInvalidOrganizationId_ShouldThrowException( + SutProvider sutProvider) + { + // Arrange + var fixture = new Fixture(); + var organizationId = Guid.Empty; + + // Act & Assert + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetLatestOrganizationReportAsync(organizationId)); + + // Assert + Assert.Equal("OrganizationId is required.", exception.Message); + } +} diff --git a/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs b/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs index eddfd6c1bc..8513ddb9cb 100644 --- a/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs +++ b/test/Core.Test/Dirt/ReportFeatures/GetPasswordHealthReportApplicationQueryTests.cs @@ -1,7 +1,7 @@ using AutoFixture; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Core.Dirt.Reports.ReportFeatures; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Repositories; using Bit.Core.Exceptions; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs b/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs deleted file mode 100644 index 000fa7e90c..0000000000 --- a/test/Core.Test/KeyManagement/UserKey/RotateUserKeyCommandTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Bit.Core.Auth.Entities; -using Bit.Core.Auth.Repositories; -using Bit.Core.Entities; -using Bit.Core.KeyManagement.Models.Data; -using Bit.Core.KeyManagement.UserKey.Implementations; -using Bit.Core.Platform.Push; -using Bit.Core.Services; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using Microsoft.AspNetCore.Identity; -using NSubstitute; -using Xunit; - -namespace Bit.Core.Test.KeyManagement.UserKey; - -[SutProviderCustomize] -public class RotateUserKeyCommandTests -{ - [Theory, BitAutoData] - public async Task RotateUserKeyAsync_Success(SutProvider sutProvider, User user, - RotateUserKeyData model) - { - sutProvider.GetDependency().CheckPasswordAsync(user, model.MasterPasswordHash) - .Returns(true); - foreach (var webauthnCred in model.WebAuthnKeys) - { - var dbWebauthnCred = new WebAuthnCredential - { - EncryptedPublicKey = "encryptedPublicKey", - EncryptedUserKey = "encryptedUserKey" - }; - sutProvider.GetDependency().GetByIdAsync(webauthnCred.Id, user.Id) - .Returns(dbWebauthnCred); - } - - var result = await sutProvider.Sut.RotateUserKeyAsync(user, model); - - Assert.Equal(IdentityResult.Success, result); - } - - [Theory, BitAutoData] - public async Task RotateUserKeyAsync_InvalidMasterPasswordHash_ReturnsFailedIdentityResult( - SutProvider sutProvider, User user, RotateUserKeyData model) - { - sutProvider.GetDependency().CheckPasswordAsync(user, model.MasterPasswordHash) - .Returns(false); - foreach (var webauthnCred in model.WebAuthnKeys) - { - var dbWebauthnCred = new WebAuthnCredential - { - EncryptedPublicKey = "encryptedPublicKey", - EncryptedUserKey = "encryptedUserKey" - }; - sutProvider.GetDependency().GetByIdAsync(webauthnCred.Id, user.Id) - .Returns(dbWebauthnCred); - } - - var result = await sutProvider.Sut.RotateUserKeyAsync(user, model); - - Assert.False(result.Succeeded); - } - - [Theory, BitAutoData] - public async Task RotateUserKeyAsync_LogsOutUser( - SutProvider sutProvider, User user, RotateUserKeyData model) - { - sutProvider.GetDependency().CheckPasswordAsync(user, model.MasterPasswordHash) - .Returns(true); - foreach (var webauthnCred in model.WebAuthnKeys) - { - var dbWebauthnCred = new WebAuthnCredential - { - EncryptedPublicKey = "encryptedPublicKey", - EncryptedUserKey = "encryptedUserKey" - }; - sutProvider.GetDependency().GetByIdAsync(webauthnCred.Id, user.Id) - .Returns(dbWebauthnCred); - } - - await sutProvider.Sut.RotateUserKeyAsync(user, model); - - await sutProvider.GetDependency().ReceivedWithAnyArgs() - .PushLogOutAsync(default, default); - } - -} diff --git a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs index 2d3dbffcf6..e372899599 100644 --- a/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs +++ b/test/Core.Test/Models/Api/Request/PushSendRequestModelTests.cs @@ -11,16 +11,14 @@ namespace Bit.Core.Test.Models.Api.Request; public class PushSendRequestModelTests { - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "], [null, "", " "])] - public void Validate_UserIdOrganizationIdInstallationIdNullOrEmpty_Invalid(string? userId, string? organizationId, - string? installationId) + [Fact] + public void Validate_UserIdOrganizationIdInstallationIdNull_Invalid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = organizationId, - InstallationId = installationId, + UserId = null, + OrganizationId = null, + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -32,16 +30,14 @@ public class PushSendRequestModelTests result => result.ErrorMessage == "UserId or OrganizationId or InstallationId is required."); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_UserIdProvidedOrganizationIdInstallationIdNullOrEmpty_Valid(string? organizationId, - string? installationId) + [Fact] + public void Validate_UserIdProvidedOrganizationIdInstallationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = organizationId, - InstallationId = installationId, + UserId = Guid.NewGuid(), + OrganizationId = null, + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -51,16 +47,14 @@ public class PushSendRequestModelTests Assert.Empty(results); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_OrganizationIdProvidedUserIdInstallationIdNullOrEmpty_Valid(string? userId, - string? installationId) + [Fact] + public void Validate_OrganizationIdProvidedUserIdInstallationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = Guid.NewGuid().ToString(), - InstallationId = installationId, + UserId = null, + OrganizationId = Guid.NewGuid(), + InstallationId = null, Type = PushType.SyncCiphers, Payload = "test" }; @@ -70,16 +64,14 @@ public class PushSendRequestModelTests Assert.Empty(results); } - [Theory] - [RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])] - public void Validate_InstallationIdProvidedUserIdOrganizationIdNullOrEmpty_Valid(string? userId, - string? organizationId) + [Fact] + public void Validate_InstallationIdProvidedUserIdOrganizationIdNull_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = userId, - OrganizationId = organizationId, - InstallationId = Guid.NewGuid().ToString(), + UserId = null, + OrganizationId = null, + InstallationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test" }; @@ -94,10 +86,10 @@ public class PushSendRequestModelTests [BitAutoData("Type")] public void Validate_RequiredFieldNotProvided_Invalid(string requiredField) { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = Guid.NewGuid().ToString(), + UserId = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test" }; @@ -115,7 +107,7 @@ public class PushSendRequestModelTests var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull); var jsonException = - Assert.Throws(() => JsonSerializer.Deserialize(serialized)); + Assert.Throws(() => JsonSerializer.Deserialize>(serialized)); Assert.Contains($"missing required properties, including the following: {requiredField}", jsonException.Message); } @@ -123,15 +115,15 @@ public class PushSendRequestModelTests [Fact] public void Validate_AllFieldsPresent_Valid() { - var model = new PushSendRequestModel + var model = new PushSendRequestModel { - UserId = Guid.NewGuid().ToString(), - OrganizationId = Guid.NewGuid().ToString(), + UserId = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), Type = PushType.SyncCiphers, Payload = "test payload", Identifier = Guid.NewGuid().ToString(), ClientType = ClientType.All, - DeviceId = Guid.NewGuid().ToString() + DeviceId = Guid.NewGuid() }; var results = Validate(model); @@ -139,7 +131,7 @@ public class PushSendRequestModelTests Assert.Empty(results); } - private static List Validate(PushSendRequestModel model) + private static List Validate(PushSendRequestModel model) { var results = new List(); Validator.TryValidateObject(model, new ValidationContext(model), results, true); diff --git a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs index 6f4ea9ca12..54a6f84339 100644 --- a/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs +++ b/test/Core.Test/NotificationHub/NotificationHubPushNotificationServiceTests.cs @@ -5,12 +5,11 @@ using Bit.Core.Auth.Entities; 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.NotificationCenter.Enums; using Bit.Core.NotificationHub; +using Bit.Core.Platform.Push; using Bit.Core.Repositories; -using Bit.Core.Settings; using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; @@ -33,483 +32,6 @@ public class NotificationHubPushNotificationServiceTests private static readonly DateTime _now = DateTime.UtcNow; private static readonly Guid _installationId = Guid.Parse("da73177b-513f-4444-b582-595c890e1022"); - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdDefault_NotSent( - SutProvider sutProvider, Notification notification) - { - sutProvider.GetDependency().Installation.Id = default; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency() - .Received(0) - .AllClients - .Received(0) - .SendTemplateNotificationAsync(Arg.Any>(), Arg.Any()); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId( - SutProvider sutProvider, Notification notification, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, null, installationId); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && installationId:{installationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize] - public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType( - ClientType clientType, SutProvider sutProvider, - Notification notification, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = clientType; - var expectedNotification = ToNotificationPushNotification(notification, null, installationId); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && installationId:{installationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(false)] - [BitAutoData(true)] - [NotificationCustomize(false)] - public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( - bool organizationIdNull, SutProvider sutProvider, - Notification notification) - { - if (organizationIdNull) - { - notification.OrganizationId = null; - } - - notification.ClientType = ClientType.All; - var expectedNotification = ToNotificationPushNotification(notification, null, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - 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, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - 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, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - 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, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - 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, null); - - await sutProvider.Sut.PushNotificationAsync(notification); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification, - expectedNotification, - $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationStatusAsync_GlobalInstallationIdDefault_NotSent( - SutProvider sutProvider, Notification notification, - NotificationStatus notificationStatus) - { - sutProvider.GetDependency().Installation.Id = default; - - 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] - [NotificationCustomize] - public async Task PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId( - SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = ClientType.All; - - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && installationId:{installationId})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - - [Theory] - [BitAutoData(ClientType.Browser)] - [BitAutoData(ClientType.Desktop)] - [BitAutoData(ClientType.Web)] - [BitAutoData(ClientType.Mobile)] - [NotificationCustomize] - public async Task - PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType( - ClientType clientType, SutProvider sutProvider, - Notification notification, NotificationStatus notificationStatus, Guid installationId) - { - sutProvider.GetDependency().Installation.Id = installationId; - notification.ClientType = clientType; - - var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - expectedNotification, - $"(template:payload && installationId:{installationId} && clientType:{clientType})"); - 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, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - 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, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - 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, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - 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, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - 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, null); - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus, - 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()); - } - - [Theory] - [BitAutoData([null])] - [BitAutoData(ClientType.All)] - public async Task SendPayloadToInstallationAsync_ClientTypeNullOrAll_SentToInstallation(ClientType? clientType, - SutProvider sutProvider, Guid installationId, PushType pushType, - string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && installationId:{installationId} && !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 SendPayloadToInstallationAsync_ClientTypeExplicit_SentToInstallationAndClientType( - ClientType clientType, SutProvider sutProvider, Guid installationId, - PushType pushType, string payload, string identifier) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier, - null, clientType); - - await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload, - $"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier} && clientType:{clientType})"); - await sutProvider.GetDependency() - .Received(0) - .UpsertAsync(Arg.Any()); - } - [Fact] public async Task PushSyncCipherCreateAsync_SendExpectedData() { @@ -1066,7 +588,7 @@ public class NotificationHubPushNotificationServiceTests ); } - private async Task VerifyNotificationAsync(Func test, + private async Task VerifyNotificationAsync(Func test, PushType type, JsonNode expectedPayload, string tag) { var installationDeviceRepository = Substitute.For(); @@ -1104,12 +626,11 @@ public class NotificationHubPushNotificationServiceTests notificationHubPool, httpContextAccessor, NullLogger.Instance, - globalSettings, - fakeTimeProvider + globalSettings ); // Act - await test(sut); + await test(new EngineWrapper(sut, fakeTimeProvider, _installationId)); // Assert var calls = notificationHubProxy.ReceivedCalls(); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs index 63f2bac896..713edeefbf 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs @@ -27,6 +27,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) @@ -107,6 +109,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + collections.First().OrganizationId = Guid.NewGuid(); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(collections, @@ -127,6 +131,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + organizationUsers.RemoveAt(0); sutProvider.GetDependency() @@ -155,6 +161,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + organizationUsers.First().OrganizationId = Guid.NewGuid(); sutProvider.GetDependency() @@ -184,6 +192,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + groups.RemoveAt(0); sutProvider.GetDependency() @@ -221,6 +231,8 @@ public class BulkAddCollectionAccessCommandTests IEnumerable collectionUsers, IEnumerable collectionGroups) { + SetCollectionsToSharedType(collections); + groups.First().OrganizationId = Guid.NewGuid(); sutProvider.GetDependency() @@ -250,6 +262,37 @@ public class BulkAddCollectionAccessCommandTests ); } + [Theory, BitAutoData, CollectionCustomization] + public async Task AddAccessAsync_WithDefaultUserCollectionType_ThrowsBadRequest(SutProvider sutProvider, + IList collections, + IEnumerable collectionUsers, + IEnumerable collectionGroups) + { + // Arrange + collections.First().Type = CollectionType.DefaultUserCollection; + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(collections, + ToAccessSelection(collectionUsers), + ToAccessSelection(collectionGroups) + )); + + Assert.Contains("You cannot add access to collections with the type as DefaultUserCollection.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateOrUpdateAccessForManyAsync(default, default, default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogCollectionEventsAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); + } + + private static void SetCollectionsToSharedType(IEnumerable collections) + { + foreach (var collection in collections) + { + collection.Type = CollectionType.SharedCollection; + } + } + private static ICollection ToAccessSelection(IEnumerable collectionUsers) { return collectionUsers.Select(cu => new CollectionAccessSelection diff --git a/test/Core.Test/OrganizationFeatures/OrganizationCollections/CreateCollectionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationCollections/CreateCollectionCommandTests.cs new file mode 100644 index 0000000000..8937e8628d --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationCollections/CreateCollectionCommandTests.cs @@ -0,0 +1,225 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.OrganizationCollections; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture; +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.OrganizationFeatures.OrganizationCollections; + +[SutProviderCustomize] +[OrganizationCustomize] +public class CreateCollectionCommandTests +{ + [Theory, BitAutoData] + public async Task CreateAsync_WithoutGroupsAndUsers_CreatesCollection( + Organization organization, Collection collection, + SutProvider sutProvider) + { + collection.Id = default; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.CreateAsync(collection, null, null); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + collection, + Arg.Is>(l => l == null), + Arg.Is>(l => l == null)); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Created); + Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithGroupsAndUsers_CreatesCollectionWithGroupsAndUsers( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize(true)] IEnumerable groups, + IEnumerable users, + SutProvider sutProvider) + { + collection.Id = default; + organization.UseGroups = true; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.CreateAsync(collection, groups, users); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + collection, + Arg.Is>(l => l.Any(i => i.Manage == true)), + Arg.Any>()); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Created); + Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithOrganizationUseGroupDisabled_CreatesCollectionWithoutGroups( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize] IEnumerable groups, + [CollectionAccessSelectionCustomize(true)] IEnumerable users, + SutProvider sutProvider) + { + collection.Id = default; + organization.UseGroups = false; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.CreateAsync(collection, groups, users); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + collection, + Arg.Is>(l => l == null), + Arg.Is>(l => l.Any(i => i.Manage == true))); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Created); + Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithNonExistingOrganizationId_ThrowsBadRequest( + Collection collection, SutProvider sutProvider) + { + collection.Id = default; + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(collection)); + Assert.Contains("Organization not found", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithoutManageAccess_ThrowsBadRequest( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize] IEnumerable users, + SutProvider sutProvider) + { + collection.Id = default; + organization.AllowAdminAccessToAllCollectionItems = false; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(collection, null, users)); + Assert.Contains("At least one member or group must have can manage permission.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithExceedsOrganizationMaxCollections_ThrowsBadRequest( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize(true)] IEnumerable users, + SutProvider sutProvider) + { + collection.Id = default; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + sutProvider.GetDependency() + .GetCountByOrganizationIdAsync(organization.Id) + .Returns(organization.MaxCollections.Value); + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(collection, null, users)); + Assert.Equal($@"You have reached the maximum number of collections ({organization.MaxCollections.Value}) for this organization.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithInvalidManageAssociations_ThrowsBadRequest( + Organization organization, Collection collection, SutProvider sutProvider) + { + collection.Id = default; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + var invalidGroups = new List + { + new() { Id = Guid.NewGuid(), Manage = true, ReadOnly = true } + }; + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(collection, invalidGroups, null)); + Assert.Contains("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task CreateAsync_WithDefaultUserCollectionType_ThrowsBadRequest( + Organization organization, Collection collection, SutProvider sutProvider) + { + collection.Id = default; + collection.Type = CollectionType.DefaultUserCollection; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(collection)); + Assert.Contains("You cannot create a collection with the type as DefaultUserCollection.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CreateAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommandTests.cs index 99eca20a09..efe9223f1b 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationCollections/DeleteCollectionCommandTests.cs @@ -1,6 +1,6 @@ - -using Bit.Core.Entities; +using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationCollections; using Bit.Core.Repositories; using Bit.Core.Services; @@ -34,6 +34,7 @@ public class DeleteCollectionCommandTests { // Arrange var collectionIds = new[] { collection.Id, collection2.Id }; + collection.Type = collection2.Type = CollectionType.SharedCollection; sutProvider.GetDependency() .GetManyByManyIdsAsync(collectionIds) @@ -51,5 +52,42 @@ public class DeleteCollectionCommandTests a.All(c => collectionIds.Contains(c.Item1.Id) && c.Item2 == EventType.Collection_Deleted))); } + [Theory, BitAutoData] + [OrganizationCustomize] + public async Task DeleteAsync_WithDefaultUserCollectionType_ThrowsBadRequest(Collection collection, SutProvider sutProvider) + { + // Arrange + collection.Type = CollectionType.DefaultUserCollection; + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(collection)); + Assert.Contains("You cannot delete a collection with the type as DefaultUserCollection.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .DeleteAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default, default); + } + + [Theory, BitAutoData] + [OrganizationCustomize] + public async Task DeleteManyAsync_WithDefaultUserCollectionType_ThrowsBadRequest(Collection collection, Collection collection2, SutProvider sutProvider) + { + // Arrange + collection.Type = CollectionType.DefaultUserCollection; + collection2.Type = CollectionType.SharedCollection; + var collections = new List { collection, collection2 }; + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteManyAsync(collections)); + Assert.Contains("You cannot delete collections with the type as DefaultUserCollection.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .DeleteManyAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventsAsync(default); + } } diff --git a/test/Core.Test/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommandTests.cs new file mode 100644 index 0000000000..2b8c180989 --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationCollections/UpdateCollectionCommandTests.cs @@ -0,0 +1,191 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.OrganizationFeatures.OrganizationCollections; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture; +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.OrganizationFeatures.OrganizationCollections; + +[SutProviderCustomize] +[OrganizationCustomize] +public class UpdateCollectionCommandTests +{ + [Theory, BitAutoData] + public async Task UpdateAsync_WithoutGroupsAndUsers_ReplacesCollection( + Organization organization, Collection collection, SutProvider sutProvider) + { + var creationDate = collection.CreationDate; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.UpdateAsync(collection, null, null); + + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync( + collection, + Arg.Is>(l => l == null), + Arg.Is>(l => l == null)); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Updated); + Assert.Equal(collection.CreationDate, creationDate); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithGroupsAndUsers_ReplacesCollectionWithGroupsAndUsers( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize(true)] IEnumerable groups, + IEnumerable users, + SutProvider sutProvider) + { + var creationDate = collection.CreationDate; + organization.UseGroups = true; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.UpdateAsync(collection, groups, users); + + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync( + collection, + Arg.Is>(l => l.Any(i => i.Manage == true)), + Arg.Any>()); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Updated); + Assert.Equal(collection.CreationDate, creationDate); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithOrganizationUseGroupDisabled_ReplacesCollectionWithoutGroups( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize] IEnumerable groups, + [CollectionAccessSelectionCustomize(true)] IEnumerable users, + SutProvider sutProvider) + { + var creationDate = collection.CreationDate; + organization.UseGroups = false; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + var utcNow = DateTime.UtcNow; + + await sutProvider.Sut.UpdateAsync(collection, groups, users); + + await sutProvider.GetDependency() + .Received(1) + .ReplaceAsync( + collection, + Arg.Is>(l => l == null), + Arg.Is>(l => l.Any(i => i.Manage == true))); + await sutProvider.GetDependency() + .Received(1) + .LogCollectionEventAsync(collection, EventType.Collection_Updated); + Assert.Equal(collection.CreationDate, creationDate); + Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithNonExistingOrganizationId_ThrowsBadRequest( + Collection collection, SutProvider sutProvider) + { + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(collection)); + Assert.Contains("Organization not found", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithoutManageAccess_ThrowsBadRequest( + Organization organization, Collection collection, + [CollectionAccessSelectionCustomize] IEnumerable users, + SutProvider sutProvider) + { + organization.AllowAdminAccessToAllCollectionItems = false; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(collection, null, users)); + Assert.Contains("At least one member or group must have can manage permission.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithInvalidManageAssociations_ThrowsBadRequest( + Organization organization, Collection collection, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + + var invalidGroups = new List + { + new() { Id = Guid.NewGuid(), Manage = true, HidePasswords = true } + }; + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(collection, invalidGroups, null)); + Assert.Contains("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } + + [Theory, BitAutoData] + public async Task UpdateAsync_WithDefaultUserCollectionType_ThrowsBadRequest( + Organization organization, Collection collection, SutProvider sutProvider) + { + collection.Type = CollectionType.DefaultUserCollection; + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(collection)); + Assert.Contains("You cannot edit a collection with the type as DefaultUserCollection.", ex.Message); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .ReplaceAsync(default, default, default); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogCollectionEventAsync(default, default); + } +} diff --git a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs index e57544b48a..4e2ec19086 100644 --- a/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/AzureQueuePushNotificationServiceTests.cs @@ -9,14 +9,11 @@ using Bit.Core.Enums; using Bit.Core.Models; using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Enums; +using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; -using Bit.Core.Settings; using Bit.Core.Test.AutoFixture; -using Bit.Core.Test.AutoFixture.CurrentContextFixtures; -using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; -using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -42,96 +39,6 @@ public class AzureQueuePushNotificationServiceTests _fakeTimeProvider.SetUtcNow(DateTime.UtcNow); } - [Theory] - [BitAutoData] - [NotificationCustomize] - [CurrentContextCustomize] - public async Task PushNotificationAsync_NotificationGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.Notification, message, - new NotificationPushNotificationEquals(notification, null, installationId), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - [CurrentContextCustomize] - public async Task PushNotificationAsync_NotificationNotGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.Notification, message, - new NotificationPushNotificationEquals(notification, null, null), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - [NotificationStatusCustomize] - [CurrentContextCustomize] - public async Task PushNotificationStatusAsync_NotificationGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.NotificationStatus, message, - new NotificationPushNotificationEquals(notification, notificationStatus, installationId), - deviceIdentifier.ToString()))); - } - - [Theory] - [BitAutoData] - [NotificationCustomize(false)] - [NotificationStatusCustomize] - [CurrentContextCustomize] - public async Task PushNotificationStatusAsync_NotificationNotGlobal_Sent( - SutProvider sutProvider, Notification notification, Guid deviceIdentifier, - ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId) - { - currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); - sutProvider.GetDependency().HttpContext!.RequestServices - .GetService(Arg.Any()).Returns(currentContext); - sutProvider.GetDependency().Installation.Id = installationId; - - await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus); - - await sutProvider.GetDependency().Received(1) - .SendMessageAsync(Arg.Is(message => - MatchMessage(PushType.NotificationStatus, message, - new NotificationPushNotificationEquals(notification, notificationStatus, null), - deviceIdentifier.ToString()))); - } - [Theory] [InlineData("6a5bbe1b-cf16-49a6-965f-5c2eac56a531", null)] [InlineData(null, "b9a3fcb4-2447-45c1-aad2-24de43c88c44")] @@ -844,7 +751,7 @@ public class AzureQueuePushNotificationServiceTests // ); // } - private async Task VerifyNotificationAsync(Func test, JsonNode expectedMessage) + private async Task VerifyNotificationAsync(Func test, JsonNode expectedMessage) { var queueClient = Substitute.For(); @@ -872,7 +779,7 @@ public class AzureQueuePushNotificationServiceTests _fakeTimeProvider ); - await test(sut); + await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id)); // Hoist equality checker outside the expression so that we // can more easily place a breakpoint diff --git a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs index 68acf7ec72..a1bc2c6547 100644 --- a/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/MultiServicePushNotificationServiceTests.cs @@ -1,98 +1,8 @@ #nullable enable -using Bit.Core.Enums; -using Bit.Core.NotificationCenter.Entities; -using Bit.Core.Platform.Push; -using Bit.Core.Platform.Push.Internal; -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.Platform.Push.Services; -[SutProviderCustomize] public class MultiServicePushNotificationServiceTests { - [Theory] - [BitAutoData] - [NotificationCustomize] - public async Task PushNotificationAsync_Notification_Sent( - SutProvider sutProvider, Notification notification) - { - await sutProvider.Sut.PushNotificationAsync(notification); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .PushNotificationAsync(notification); - } - - [Theory] - [BitAutoData] - [NotificationCustomize] - [NotificationStatusCustomize] - public async Task PushNotificationStatusAsync_Notification_Sent( - SutProvider sutProvider, Notification notification, - NotificationStatus notificationStatus) - { - 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); - } - - [Theory] - [BitAutoData([null, null])] - [BitAutoData(ClientType.All, null)] - [BitAutoData([null, "test device id"])] - [BitAutoData(ClientType.All, "test device id")] - public async Task SendPayloadToInstallationAsync_Message_Sent(ClientType? clientType, string? deviceId, - string installationId, PushType type, object payload, string identifier, - SutProvider sutProvider) - { - await sutProvider.Sut.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, - clientType); - - await sutProvider.GetDependency>() - .First() - .Received(1) - .SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType); - } + // TODO: Can add a couple tests here } diff --git a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs index d206d96d44..92706c6ccc 100644 --- a/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/NotificationsApiPushNotificationServiceTests.cs @@ -19,14 +19,13 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase protected override string ExpectedClientUrl() => "https://localhost:7777/send"; - protected override IPushNotificationService CreateService() + protected override IPushEngine CreateService() { return new NotificationsApiPushNotificationService( HttpClientFactory, GlobalSettings, HttpContextAccessor, - NullLogger.Instance, - FakeTimeProvider + NullLogger.Instance ); } @@ -221,7 +220,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } @@ -236,7 +235,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } @@ -251,7 +250,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase ["UserId"] = send.UserId, ["RevisionDate"] = send.RevisionDate, }, - ["ContextId"] = null, + ["ContextId"] = DeviceIdentifier, }; } diff --git a/test/Core.Test/Platform/Push/Services/PushTestBase.cs b/test/Core.Test/Platform/Push/Services/PushTestBase.cs index 111df7ca26..3538a68127 100644 --- a/test/Core.Test/Platform/Push/Services/PushTestBase.cs +++ b/test/Core.Test/Platform/Push/Services/PushTestBase.cs @@ -15,11 +15,28 @@ using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; using RichardSzalay.MockHttp; using Xunit; +public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService +{ + public Guid InstallationId { get; } = installationId; + + public TimeProvider TimeProvider { get; } = fakeTimeProvider; + + public ILogger Logger => NullLogger.Instance; + + public Task PushAsync(PushNotification pushNotification) where T : class + => pushEngine.PushAsync(pushNotification); + + public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable? collectionIds) + => pushEngine.PushCipherAsync(cipher, pushType, collectionIds); +} + public abstract class PushTestBase { protected static readonly string DeviceIdentifier = "test_device_identifier"; @@ -51,7 +68,7 @@ public abstract class PushTestBase FakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow); } - protected abstract IPushNotificationService CreateService(); + protected abstract IPushEngine CreateService(); protected abstract string ExpectedClientUrl(); @@ -480,7 +497,7 @@ public abstract class PushTestBase }) .Respond(HttpStatusCode.OK); - await test(CreateService()); + await test(new EngineWrapper(CreateService(), FakeTimeProvider, GlobalSettings.Installation.Id)); Assert.NotNull(actualNode); diff --git a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs index faa6b5dfa7..f95531c944 100644 --- a/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs +++ b/test/Core.Test/Platform/Push/Services/RelayPushNotificationServiceTests.cs @@ -4,8 +4,8 @@ using System.Text.Json.Nodes; using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Entities; using Bit.Core.Entities; -using Bit.Core.Enums; using Bit.Core.NotificationCenter.Entities; +using Bit.Core.Platform.Push; using Bit.Core.Platform.Push.Internal; using Bit.Core.Repositories; using Bit.Core.Settings; @@ -14,7 +14,6 @@ using Bit.Core.Vault.Entities; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using NSubstitute; -using Xunit; namespace Bit.Core.Test.Platform.Push.Services; @@ -38,47 +37,19 @@ public class RelayPushNotificationServiceTests : PushTestBase GlobalSettings.Installation.IdentityUri = "https://localhost:8888"; } - protected override RelayPushNotificationService CreateService() + protected override IPushEngine CreateService() { return new RelayPushNotificationService( HttpClientFactory, _deviceRepository, GlobalSettings, HttpContextAccessor, - NullLogger.Instance, - FakeTimeProvider + NullLogger.Instance ); } protected override string ExpectedClientUrl() => "https://localhost:7777/push/send"; - [Fact] - public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new { }, null) - ); - } - - [Fact] - public async Task SendPayloadToUserAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new { }, null) - ); - } - - [Fact] - public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException() - { - var sut = CreateService(); - await Assert.ThrowsAsync( - async () => await sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new { }, null) - ); - } - protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionIds) { return new JsonObject diff --git a/test/Core.Test/Services/CollectionServiceTests.cs b/test/Core.Test/Services/CollectionServiceTests.cs index 6d788deb05..118c0fa6b2 100644 --- a/test/Core.Test/Services/CollectionServiceTests.cs +++ b/test/Core.Test/Services/CollectionServiceTests.cs @@ -2,10 +2,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -18,135 +16,12 @@ namespace Bit.Core.Test.Services; [OrganizationCustomize] public class CollectionServiceTest { - [Theory, BitAutoData] - public async Task SaveAsync_DefaultIdWithUsers_CreatesCollectionInTheRepository(Collection collection, Organization organization, [CollectionAccessSelectionCustomize(true)] IEnumerable users, SutProvider sutProvider) - { - collection.Id = default; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(collection, null, users); - - await sutProvider.GetDependency().Received() - .CreateAsync(collection, Arg.Is>(l => l == null), - Arg.Is>(l => l.Any(i => i.Manage == true))); - await sutProvider.GetDependency().Received() - .LogCollectionEventAsync(collection, EventType.Collection_Created); - Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_DefaultIdWithGroupsAndUsers_CreateCollectionWithGroupsAndUsersInRepository(Collection collection, - [CollectionAccessSelectionCustomize(true)] IEnumerable groups, IEnumerable users, Organization organization, SutProvider sutProvider) - { - collection.Id = default; - organization.UseGroups = true; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(collection, groups, users); - - await sutProvider.GetDependency().Received() - .CreateAsync(collection, Arg.Is>(l => l.Any(i => i.Manage == true)), - Arg.Any>()); - await sutProvider.GetDependency().Received() - .LogCollectionEventAsync(collection, EventType.Collection_Created); - Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NonDefaultId_ReplacesCollectionInRepository(Collection collection, Organization organization, [CollectionAccessSelectionCustomize(true)] IEnumerable users, SutProvider sutProvider) - { - var creationDate = collection.CreationDate; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(collection, null, users); - - await sutProvider.GetDependency().Received().ReplaceAsync(collection, - Arg.Is>(l => l == null), - Arg.Is>(l => l.Any(i => i.Manage == true))); - await sutProvider.GetDependency().Received() - .LogCollectionEventAsync(collection, EventType.Collection_Updated); - Assert.Equal(collection.CreationDate, creationDate); - Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection, - [CollectionAccessSelectionCustomize] IEnumerable groups, [CollectionAccessSelectionCustomize(true)] IEnumerable users, - Organization organization, SutProvider sutProvider) - { - collection.Id = default; - organization.UseGroups = false; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - var utcNow = DateTime.UtcNow; - - await sutProvider.Sut.SaveAsync(collection, groups, users); - - await sutProvider.GetDependency().Received().CreateAsync(collection, - Arg.Is>(l => l == null), - Arg.Is>(l => l.Any(i => i.Manage == true))); - await sutProvider.GetDependency().Received() - .LogCollectionEventAsync(collection, EventType.Collection_Created); - Assert.True(collection.CreationDate - utcNow < TimeSpan.FromSeconds(1)); - Assert.True(collection.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NonExistingOrganizationId_ThrowsBadRequest(Collection collection, SutProvider sutProvider) - { - var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.SaveAsync(collection)); - Assert.Contains("Organization not found", ex.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default, default, default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default); - } - - [Theory, BitAutoData] - public async Task SaveAsync_NoManageAccess_ThrowsBadRequest(Collection collection, Organization organization, - [CollectionAccessSelectionCustomize] IEnumerable users, SutProvider sutProvider) - { - collection.Id = default; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - organization.AllowAdminAccessToAllCollectionItems = false; - - var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.SaveAsync(collection, null, users)); - Assert.Contains("At least one member or group must have can manage permission.", ex.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default, default, default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default); - } - - [Theory, BitAutoData] - public async Task SaveAsync_ExceedsOrganizationMaxCollections_ThrowsBadRequest(Collection collection, - Organization organization, [CollectionAccessSelectionCustomize(true)] IEnumerable users, - SutProvider sutProvider) - { - collection.Id = default; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetCountByOrganizationIdAsync(organization.Id) - .Returns(organization.MaxCollections.Value); - - var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.SaveAsync(collection, null, users)); - Assert.Equal($@"You have reached the maximum number of collections ({organization.MaxCollections.Value}) for this organization.", ex.Message); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default, default, default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogCollectionEventAsync(default, default); - } - [Theory, BitAutoData] public async Task DeleteUserAsync_DeletesValidUserWhoBelongsToCollection(Collection collection, Organization organization, OrganizationUser organizationUser, SutProvider sutProvider) { collection.OrganizationId = organization.Id; organizationUser.OrganizationId = organization.Id; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) .Returns(organizationUser); @@ -162,7 +37,6 @@ public class CollectionServiceTest OrganizationUser organizationUser, SutProvider sutProvider) { collection.OrganizationId = organization.Id; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) .Returns(organizationUser); @@ -175,4 +49,22 @@ public class CollectionServiceTest await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .LogOrganizationUserEventAsync(default, default); } + + [Theory, BitAutoData] + public async Task DeleteUserAsync_WithDefaultUserCollectionType_ThrowsBadRequest(Collection collection, + Organization organization, OrganizationUser organizationUser, SutProvider sutProvider) + { + collection.Type = CollectionType.DefaultUserCollection; + collection.OrganizationId = organization.Id; + organizationUser.OrganizationId = organization.Id; + + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.DeleteUserAsync(collection, organizationUser.Id)); + Assert.Contains("You cannot modify member access for collections with the type as DefaultUserCollection.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetByIdAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(default, default); + } } diff --git a/test/Core.Test/Services/SendGridMailDeliveryServiceTests.cs b/test/Core.Test/Services/SendGridMailDeliveryServiceTests.cs index 3c64e5c406..a6132543b7 100644 --- a/test/Core.Test/Services/SendGridMailDeliveryServiceTests.cs +++ b/test/Core.Test/Services/SendGridMailDeliveryServiceTests.cs @@ -25,7 +25,8 @@ public class SendGridMailDeliveryServiceTests : IDisposable { Mail = { - SendGridApiKey = "SendGridApiKey" + SendGridApiKey = "SendGridApiKey", + SendGridApiHost = "https://api.sendgrid.com" } }; diff --git a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs index 5605b5ab2a..1b50779c57 100644 --- a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs +++ b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs @@ -31,7 +31,7 @@ public class ImportCiphersAsyncCommandTests SutProvider sutProvider) { sutProvider.GetDependency() - .AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.PersonalOwnership) + .AnyPoliciesApplicableToUserAsync(importingUserId, PolicyType.OrganizationDataOwnership) .Returns(false); sutProvider.GetDependency() @@ -51,7 +51,7 @@ public class ImportCiphersAsyncCommandTests } [Theory, BitAutoData] - public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyDisabled_Success( + public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyDisabled_Success( Guid importingUserId, List ciphers, SutProvider sutProvider) @@ -61,8 +61,10 @@ public class ImportCiphersAsyncCommandTests .Returns(true); sutProvider.GetDependency() - .GetAsync(importingUserId) - .Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false }); + .GetAsync(importingUserId) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Disabled, + [])); sutProvider.GetDependency() .GetManyByUserIdAsync(importingUserId) @@ -89,7 +91,7 @@ public class ImportCiphersAsyncCommandTests ciphers.ForEach(c => c.UserId = userId); sutProvider.GetDependency() - .AnyPoliciesApplicableToUserAsync(userId, PolicyType.PersonalOwnership) + .AnyPoliciesApplicableToUserAsync(userId, PolicyType.OrganizationDataOwnership) .Returns(true); var folderRelationships = new List>(); @@ -101,7 +103,7 @@ public class ImportCiphersAsyncCommandTests } [Theory, BitAutoData] - public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyEnabled_ThrowsBadRequestException( + public async Task ImportIntoIndividualVaultAsync_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyEnabled_ThrowsBadRequestException( List folders, List ciphers, SutProvider sutProvider) @@ -115,8 +117,10 @@ public class ImportCiphersAsyncCommandTests .Returns(true); sutProvider.GetDependency() - .GetAsync(userId) - .Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true }); + .GetAsync(userId) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [Guid.NewGuid()])); var folderRelationships = new List>(); diff --git a/test/Core.Test/Tools/Services/NonAnonymousSendCommandTests.cs b/test/Core.Test/Tools/Services/NonAnonymousSendCommandTests.cs index 674cca7d5f..1ad6a08516 100644 --- a/test/Core.Test/Tools/Services/NonAnonymousSendCommandTests.cs +++ b/test/Core.Test/Tools/Services/NonAnonymousSendCommandTests.cs @@ -10,10 +10,10 @@ using Bit.Core.Tools.Entities; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Data; using Bit.Core.Tools.Repositories; -using Bit.Core.Tools.SendFeatures; using Bit.Core.Tools.SendFeatures.Commands; using Bit.Core.Tools.Services; using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.Extensions.Logging; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; @@ -35,6 +35,8 @@ public class NonAnonymousSendCommandTests private readonly ISendCoreHelperService _sendCoreHelperService; private readonly NonAnonymousSendCommand _nonAnonymousSendCommand; + private readonly ILogger _logger; + public NonAnonymousSendCommandTests() { _sendRepository = Substitute.For(); @@ -45,6 +47,7 @@ public class NonAnonymousSendCommandTests _sendValidationService = Substitute.For(); _currentContext = Substitute.For(); _sendCoreHelperService = Substitute.For(); + _logger = Substitute.For>(); _nonAnonymousSendCommand = new NonAnonymousSendCommand( _sendRepository, @@ -52,7 +55,8 @@ public class NonAnonymousSendCommandTests _pushNotificationService, _sendAuthorizationService, _sendValidationService, - _sendCoreHelperService + _sendCoreHelperService, + _logger ); } @@ -652,11 +656,11 @@ public class NonAnonymousSendCommandTests UserId = userId }; var fileData = new SendFileData(); - var fileLength = 15L * 1024L * 1024L * 1024L; // 15GB + var fileLength = 15L * 1024L * 1024L; // 15 MB - // Configure validation service to return large but insufficient storage (10GB for self-hosted non-premium) + // Configure validation service to return insufficient storage _sendValidationService.StorageRemainingForSendAsync(send) - .Returns(10L * 1024L * 1024L * 1024L); // 10GB remaining (self-hosted default) + .Returns(10L * 1024L * 1024L); // 10 MB remaining // Act & Assert var exception = await Assert.ThrowsAsync(() => @@ -687,11 +691,40 @@ public class NonAnonymousSendCommandTests UserId = userId }; var fileData = new SendFileData(); - var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB + var fileLength = 2L * 1024L * 1024L * 1024L; // 2MB - // Configure validation service to return 1GB storage (cloud non-premium default) + // Act & Assert + var exception = await Assert.ThrowsAsync(() => + _nonAnonymousSendCommand.SaveFileSendAsync(send, fileData, fileLength)); + + Assert.Contains("Max file size is ", exception.Message); + + // Verify no further methods were called + await _sendValidationService.DidNotReceive().StorageRemainingForSendAsync(Arg.Any()); + await _sendRepository.DidNotReceive().CreateAsync(Arg.Any()); + await _sendRepository.DidNotReceive().UpsertAsync(Arg.Any()); + await _sendFileStorageService.DidNotReceive().GetSendFileUploadUrlAsync(Arg.Any(), Arg.Any()); + await _pushNotificationService.DidNotReceive().PushSyncSendCreateAsync(Arg.Any()); + await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any()); + } + + [Fact] + public async Task SaveFileSendAsync_UserCanAccessPremium_IsNotPremium_IsNotSelfHosted_NotEnoughSpace_ThrowsBadRequest() + { + // Arrange + var userId = Guid.NewGuid(); + var send = new Send + { + Id = Guid.NewGuid(), + Type = SendType.File, + UserId = userId + }; + var fileData = new SendFileData(); + var fileLength = 2L * 1024L * 1024L; // 2MB + + // Configure validation service to return 1 MB storage remaining _sendValidationService.StorageRemainingForSendAsync(send) - .Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining (cloud default) + .Returns(1L * 1024L * 1024L); // Act & Assert var exception = await Assert.ThrowsAsync(() => @@ -756,7 +789,7 @@ public class NonAnonymousSendCommandTests UserId = null }; var fileData = new SendFileData(); - var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB + var fileLength = 2L * 1024L * 1024L; // 2 MB // Configure validation service to throw BadRequest when checking storage for org without storage _sendValidationService.StorageRemainingForSendAsync(send) @@ -792,11 +825,10 @@ public class NonAnonymousSendCommandTests UserId = null }; var fileData = new SendFileData(); - var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB + var fileLength = 2L * 1024L * 1024L; // 2 MB - // Configure validation service to return 1GB storage (org's max storage limit) _sendValidationService.StorageRemainingForSendAsync(send) - .Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining + .Returns(1L * 1024L * 1024L); // 1 MB remaining // Act & Assert var exception = await Assert.ThrowsAsync(() => @@ -980,7 +1012,7 @@ public class NonAnonymousSendCommandTests }; // Setup validation to succeed - _sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size)); + _sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any(), Arg.Any()).Returns((true, sendFileData.Size)); // Act await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send); @@ -1014,7 +1046,7 @@ public class NonAnonymousSendCommandTests Data = JsonSerializer.Serialize(sendFileData) }; - _sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size)); + _sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any(), Arg.Any()).Returns((true, sendFileData.Size)); // Act await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send); diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index 0941372963..0cee6530c2 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -114,7 +114,7 @@ public class CipherServiceTests [Theory] [BitAutoData] - public async Task SaveDetailsAsync_PersonalVault_WithDisablePersonalOwnershipPolicyEnabled_Throws( + public async Task SaveDetailsAsync_PersonalVault_WithOrganizationDataOwnershipPolicyEnabled_Throws( SutProvider sutProvider, CipherDetails cipher, Guid savingUserId) @@ -124,7 +124,7 @@ public class CipherServiceTests cipher.OrganizationId = null; sutProvider.GetDependency() - .AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership) + .AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.OrganizationDataOwnership) .Returns(true); var exception = await Assert.ThrowsAsync( @@ -134,7 +134,7 @@ public class CipherServiceTests [Theory] [BitAutoData] - public async Task SaveDetailsAsync_PersonalVault_WithDisablePersonalOwnershipPolicyDisabled_Succeeds( + public async Task SaveDetailsAsync_PersonalVault_WithOrganizationDataOwnershipPolicyDisabled_Succeeds( SutProvider sutProvider, CipherDetails cipher, Guid savingUserId) @@ -144,7 +144,7 @@ public class CipherServiceTests cipher.OrganizationId = null; sutProvider.GetDependency() - .AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.PersonalOwnership) + .AnyPoliciesApplicableToUserAsync(savingUserId, PolicyType.OrganizationDataOwnership) .Returns(false); await sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null); @@ -156,7 +156,7 @@ public class CipherServiceTests [Theory] [BitAutoData] - public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyEnabled_Throws( + public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyEnabled_Throws( SutProvider sutProvider, CipherDetails cipher, Guid savingUserId) @@ -170,8 +170,10 @@ public class CipherServiceTests .Returns(true); sutProvider.GetDependency() - .GetAsync(savingUserId) - .Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true }); + .GetAsync(savingUserId) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Enabled, + [Guid.NewGuid()])); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null)); @@ -180,7 +182,7 @@ public class CipherServiceTests [Theory] [BitAutoData] - public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithDisablePersonalOwnershipPolicyDisabled_Succeeds( + public async Task SaveDetailsAsync_PersonalVault_WithPolicyRequirementsEnabled_WithOrganizationDataOwnershipPolicyDisabled_Succeeds( SutProvider sutProvider, CipherDetails cipher, Guid savingUserId) @@ -194,8 +196,10 @@ public class CipherServiceTests .Returns(true); sutProvider.GetDependency() - .GetAsync(savingUserId) - .Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false }); + .GetAsync(savingUserId) + .Returns(new OrganizationDataOwnershipPolicyRequirement( + OrganizationDataOwnershipState.Disabled, + [])); await sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null); diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs index 4a56d2cb22..5c7b3ed99d 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/EntityFrameworkRepositoryFixtures.cs @@ -92,6 +92,7 @@ public class EfRepositoryListBuilder : ISpecimenBuilder where T : BaseEntityF cfg.AddProfile(); cfg.AddProfile(); cfg.AddProfile(); + cfg.AddProfile(); }) .CreateMapper())); diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/OrganizationReportFixture.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/OrganizationReportFixture.cs new file mode 100644 index 0000000000..b5b2626d7a --- /dev/null +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/OrganizationReportFixture.cs @@ -0,0 +1,80 @@ +using AutoFixture; +using AutoFixture.Kernel; +using Bit.Core.Dirt.Entities; +using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; +using Bit.Infrastructure.EntityFramework.Dirt.Repositories; +using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; + +namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture; + +internal class OrganizationReportBuilder : 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(OrganizationReport)) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + var obj = fixture.WithAutoNSubstitutions().Create(); + return obj; + } +} + +internal class EfOrganizationReport : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new OrganizationReportBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + } +} + +internal class EfOrganizationReportApplicableToUser : ICustomization +{ + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new IgnoreVirtualMembersCustomization()); + fixture.Customizations.Add(new GlobalSettingsBuilder()); + fixture.Customizations.Add(new OrganizationReportBuilder()); + fixture.Customizations.Add(new OrganizationBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + fixture.Customizations.Add(new EfRepositoryListBuilder()); + } +} + +internal class EfOrganizationReportAutoDataAttribute : CustomAutoDataAttribute +{ + public EfOrganizationReportAutoDataAttribute() : base(new SutProviderCustomization(), new EfOrganizationReport()) { } +} + +internal class EfOrganizationReportApplicableToUserInlineAutoDataAttribute : InlineCustomAutoDataAttribute +{ + public EfOrganizationReportApplicableToUserInlineAutoDataAttribute(params object[] values) + : base(new[] { typeof(SutProviderCustomization), typeof(EfOrganizationReportApplicableToUser) }, values) { } +} + +internal class InlineEfOrganizationReportAutoDataAttribute : InlineCustomAutoDataAttribute +{ + public InlineEfOrganizationReportAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization), + typeof(EfPolicy) }, values) + { } +} diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs index f6100fc71f..31ab99dc4a 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/PasswordHealthReportApplicationFixtures.cs @@ -1,6 +1,6 @@ using AutoFixture; using AutoFixture.Kernel; -using Bit.Core.Dirt.Reports.Entities; +using Bit.Core.Dirt.Entities; using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Infrastructure.EntityFramework.Dirt.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; diff --git a/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/OrganizationReportRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/OrganizationReportRepositoryTests.cs new file mode 100644 index 0000000000..dd2adc0970 --- /dev/null +++ b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/OrganizationReportRepositoryTests.cs @@ -0,0 +1,131 @@ +using AutoFixture; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; +using Bit.Core.Repositories; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Infrastructure.Dapper.Dirt; +using Bit.Infrastructure.EFIntegration.Test.AutoFixture; +using Xunit; +using EfRepo = Bit.Infrastructure.EntityFramework.Repositories; +using SqlRepo = Bit.Infrastructure.Dapper.Repositories; + +namespace Bit.Infrastructure.EFIntegration.Test.Dirt.Repositories; + +public class OrganizationReportRepositoryTests +{ + [CiSkippedTheory, EfOrganizationReportAutoData] + public async Task CreateAsync_ShouldCreateReport_WhenValidDataProvided( + OrganizationReport report, + Organization organization, + List suts, + List efOrganizationRepos, + OrganizationReportRepository sqlOrganizationReportRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + var records = new List(); + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + var efOrganization = await efOrganizationRepos[i].CreateAsync(organization); + sut.ClearChangeTracking(); + + report.OrganizationId = efOrganization.Id; + var postEfOrganizationReport = await sut.CreateAsync(report); + sut.ClearChangeTracking(); + + var savedOrganizationReport = await sut.GetByIdAsync(postEfOrganizationReport.Id); + records.Add(savedOrganizationReport); + } + + var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization); + + report.OrganizationId = sqlOrganization.Id; + var sqlOrgnizationReportRecord = await sqlOrganizationReportRepo.CreateAsync(report); + var savedSqlOrganizationReport = await sqlOrganizationReportRepo.GetByIdAsync(sqlOrgnizationReportRecord.Id); + records.Add(savedSqlOrganizationReport); + + Assert.True(records.Count == 4); + } + + [CiSkippedTheory, EfOrganizationReportAutoData] + public async Task RetrieveByOrganisation_Works( + OrganizationReportRepository sqlPasswordHealthReportApplicationRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + var (firstOrg, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo); + var (secondOrg, _) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo); + + var firstSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(firstOrg.Id); + var nextSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(secondOrg.Id); + + Assert.True(firstSetOfRecords.Count == 1 && firstSetOfRecords.First().OrganizationId == firstOrg.Id); + Assert.True(nextSetOfRecords.Count == 1 && nextSetOfRecords.First().OrganizationId == secondOrg.Id); + } + + [CiSkippedTheory, EfOrganizationReportAutoData] + public async Task Delete_Works( + List suts, + List efOrganizationRepos, + OrganizationReportRepository sqlOrganizationReportRepo, + SqlRepo.OrganizationRepository sqlOrganizationRepo) + { + var fixture = new Fixture(); + var rawOrg = fixture.Build().Create(); + var rawRecord = fixture.Build() + .With(_ => _.OrganizationId, rawOrg.Id) + .Create(); + var dbRecords = new List(); + + foreach (var sut in suts) + { + var i = suts.IndexOf(sut); + + // create a new organization for each repository + var organization = await efOrganizationRepos[i].CreateAsync(rawOrg); + + // map the organization Id and use Upsert to save new record + rawRecord.OrganizationId = organization.Id; + rawRecord = await sut.CreateAsync(rawRecord); + sut.ClearChangeTracking(); + + // apply update using Upsert to make changes to db + await sut.DeleteAsync(rawRecord); + sut.ClearChangeTracking(); + + // retrieve the data and add to the list for assertions + var recordFromDb = await sut.GetByIdAsync(rawRecord.Id); + dbRecords.Add(recordFromDb); + + sut.ClearChangeTracking(); + } + + // sql - create new records + var (org, organizationReport) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo); + await sqlOrganizationReportRepo.DeleteAsync(organizationReport); + var sqlDbRecord = await sqlOrganizationReportRepo.GetByIdAsync(organizationReport.Id); + dbRecords.Add(sqlDbRecord); + + // assertions + // all records should be null - as they were deleted before querying + Assert.True(dbRecords.Where(_ => _ == null).Count() == 4); + } + + private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportAsync( + IOrganizationRepository orgRepo, + IOrganizationReportRepository orgReportRepo) + { + var fixture = new Fixture(); + var organization = fixture.Create(); + + var orgReportRecord = fixture.Build() + .With(x => x.OrganizationId, organization.Id) + .Create(); + + organization = await orgRepo.CreateAsync(organization); + orgReportRecord = await orgReportRepo.CreateAsync(orgReportRecord); + + return (organization, orgReportRecord); + } +} diff --git a/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs index d796635153..5dfbb3e942 100644 --- a/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs +++ b/test/Infrastructure.EFIntegration.Test/Dirt/Repositories/PasswordHealthReportApplicationRepositoryTests.cs @@ -1,7 +1,7 @@ using AutoFixture; using Bit.Core.AdminConsole.Entities; -using Bit.Core.Dirt.Reports.Entities; -using Bit.Core.Dirt.Reports.Repositories; +using Bit.Core.Dirt.Entities; +using Bit.Core.Dirt.Repositories; using Bit.Core.Repositories; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Infrastructure.Dapper.Dirt; diff --git a/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs index 8cd8cb607c..56f01748fb 100644 --- a/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Auth/Repositories/AuthRequestRepositoryTests.cs @@ -66,10 +66,8 @@ public class AuthRequestRepositoryTests Assert.NotNull(await authRequestRepository.GetByIdAsync(notExpiredAdminApprovalRequest.Id)); Assert.NotNull(await authRequestRepository.GetByIdAsync(notExpiredApprovedAdminApprovalRequest.Id)); - // Ensure the repository responds with the amount of items it deleted and it deleted the right amount. - // NOTE: On local development this might fail on it's first run because the developer could have expired AuthRequests - // on their machine but aren't running the job that would delete them. The second run of this test should succeed. - Assert.Equal(4, numberOfDeleted); + // Ensure the repository responds with the amount of items it deleted and it deleted the right amount, which could include other auth requests from other tests so we take the minimum known acceptable amount. + Assert.True(numberOfDeleted >= 4); } [DatabaseTheory, DatabaseData] @@ -182,7 +180,157 @@ public class AuthRequestRepositoryTests Assert.Null(uncreatedAuthRequest); } - private static AuthRequest CreateAuthRequest(Guid userId, AuthRequestType authRequestType, DateTime creationDate, bool? approved = null, DateTime? responseDate = null) + /// + /// Test to determine that when no valid authRequest exists in the database the return value is null. + /// + [DatabaseTheory, DatabaseData] + public async Task GetManyPendingAuthRequestByUserId_AuthRequestsInvalid_ReturnsEmptyEnumerable_Success( + IAuthRequestRepository authRequestRepository, + IUserRepository userRepository) + { + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + List authRequests = []; + + // A user auth request type that has passed its expiration time, should not be returned. + var authRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + CreateExpiredDate(_userRequestExpiration)); + authRequest.RequestDeviceIdentifier = "auth_request_expired"; + authRequests.Add(await authRequestRepository.CreateAsync(authRequest)); + + // A valid time AuthRequest but for pending we do not fetch admin auth requests + authRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AdminApproval, + DateTime.UtcNow.AddMinutes(-1)); + authRequest.RequestDeviceIdentifier = "admin_auth_request"; + authRequests.Add(await authRequestRepository.CreateAsync(authRequest)); + + // A valid time AuthRequest but the request has been approved/rejected, so it should not be returned. + authRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-1), + false); + authRequest.RequestDeviceIdentifier = "approved_auth_request"; + authRequests.Add(await authRequestRepository.CreateAsync(authRequest)); + + var result = await authRequestRepository.GetManyPendingAuthRequestByUserId(user.Id); + Assert.NotNull(result); + Assert.Empty(result); + + // Verify that there are authRequests associated with the user. + Assert.NotEmpty(await authRequestRepository.GetManyByUserIdAsync(user.Id)); + + await CleanupTestAsync(authRequests, authRequestRepository); + } + + /// + /// Test to determine that when multiple valid authRequest exist for a device only the soonest one is returned. + /// + [DatabaseTheory, DatabaseData] + public async Task GetManyPendingAuthRequestByUserId_MultipleRequestForSingleDevice_ReturnsMostRecent( + IAuthRequestRepository authRequestRepository, + IUserRepository userRepository) + { + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var oneMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-1)); + oneMinuteOldAuthRequest = await authRequestRepository.CreateAsync(oneMinuteOldAuthRequest); + + var fiveMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-5)); + fiveMinuteOldAuthRequest = await authRequestRepository.CreateAsync(fiveMinuteOldAuthRequest); + + var tenMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-10)); + tenMinuteOldAuthRequest = await authRequestRepository.CreateAsync(tenMinuteOldAuthRequest); + + var result = await authRequestRepository.GetManyPendingAuthRequestByUserId(user.Id); + Assert.NotNull(result); + // since we group by device there should only be a single return since the device Id is the same + Assert.Single(result); + var resultAuthRequest = result.First(); + Assert.Equal(oneMinuteOldAuthRequest.Id, resultAuthRequest.Id); + + List authRequests = [oneMinuteOldAuthRequest, fiveMinuteOldAuthRequest, tenMinuteOldAuthRequest]; + + await CleanupTestAsync(authRequests, authRequestRepository); + } + + /// + /// Test to determine that when multiple authRequests exist for a device if the most recent is approved then + /// there should be no return. + /// + [DatabaseTheory, DatabaseData] + public async Task GetManyPendingAuthRequestByUserId_MultipleRequestForSingleDevice_MostRecentIsApproved_ReturnsEmpty( + IAuthRequestRepository authRequestRepository, + IUserRepository userRepository) + { + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + // approved auth request + var oneMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-1), + false); + oneMinuteOldAuthRequest = await authRequestRepository.CreateAsync(oneMinuteOldAuthRequest); + + var fiveMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-5)); + fiveMinuteOldAuthRequest = await authRequestRepository.CreateAsync(fiveMinuteOldAuthRequest); + + var tenMinuteOldAuthRequest = CreateAuthRequest( + user.Id, + AuthRequestType.AuthenticateAndUnlock, + DateTime.UtcNow.AddMinutes(-10)); + tenMinuteOldAuthRequest = await authRequestRepository.CreateAsync(tenMinuteOldAuthRequest); + + var result = await authRequestRepository.GetManyPendingAuthRequestByUserId(user.Id); + Assert.NotNull(result); + // result should be empty since the most recent request was addressed + Assert.Empty(result); + + List authRequests = [oneMinuteOldAuthRequest, fiveMinuteOldAuthRequest, tenMinuteOldAuthRequest]; + await CleanupTestAsync(authRequests, authRequestRepository); + } + + private static AuthRequest CreateAuthRequest( + Guid userId, + AuthRequestType authRequestType, + DateTime creationDate, + bool? approved = null, + DateTime? responseDate = null) { return new AuthRequest { @@ -203,4 +351,20 @@ public class AuthRequestRepositoryTests var exp = expirationPeriod + TimeSpan.FromMinutes(1); return DateTime.UtcNow.Add(exp.Negate()); } + + /// + /// Cleans up the test data created by the test methods. This supports the DeleteExpiredAsync Test. + /// + /// Created Auth Requests + /// repository context for the current test + /// void + private static async Task CleanupTestAsync( + IEnumerable authRequests, + IAuthRequestRepository authRequestRepository) + { + foreach (var authRequest in authRequests) + { + await authRequestRepository.DeleteAsync(authRequest); + } + } } diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs index b7feeaa79b..0a186e43be 100644 --- a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs @@ -5,6 +5,8 @@ using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; +using Bit.Core.NotificationCenter.Entities; +using Bit.Core.NotificationCenter.Repositories; using Bit.Core.Repositories; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Enums; @@ -571,6 +573,65 @@ public class CipherRepositoryTests Assert.True(personalDetails.Manage, "Personal ciphers should always have Manage permission"); } + [DatabaseTheory, DatabaseData] + public async Task GetManyByUserIdAsync_WhenOneCipherIsAssignedToTwoCollectionsWithDifferentPermissions_MostPrivilegedAccessIsReturnedOnTheCipher( + ICipherRepository cipherRepository, + IUserRepository userRepository, + ICollectionCipherRepository collectionCipherRepository, + ICollectionRepository collectionRepository, + IOrganizationRepository organizationRepository, + IOrganizationUserRepository organizationUserRepository) + { + //Arrange + var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository); + + var cipher = await cipherRepository.CreateAsync(new Cipher + { + Type = CipherType.Login, + OrganizationId = organization.Id, + Data = "" + }); + + var managedPermissionsCollection = await collectionRepository.CreateAsync(new Collection + { + Name = "Managed", + OrganizationId = organization.Id + }); + + var unmanagedPermissionsCollection = await collectionRepository.CreateAsync(new Collection + { + Name = "Unmanaged", + OrganizationId = organization.Id + }); + await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipher.Id, organization.Id, + [managedPermissionsCollection.Id, unmanagedPermissionsCollection.Id]); + + await collectionRepository.UpdateUsersAsync(managedPermissionsCollection.Id, new List + { + new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true } + }); + + await collectionRepository.UpdateUsersAsync(unmanagedPermissionsCollection.Id, new List + { + new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false } + }); + + // Act + var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id); + + // Assert + Assert.Single(ciphers); + var deletableCipher = ciphers.SingleOrDefault(x => x.Id == cipher.Id); + Assert.NotNull(deletableCipher); + Assert.True(deletableCipher.Manage); + + // Annul + await cipherRepository.DeleteAsync(cipher); + await organizationUserRepository.DeleteAsync(orgUser); + await organizationRepository.DeleteAsync(organization); + await userRepository.DeleteAsync(user); + } + private async Task<(User user, Organization org, OrganizationUser orgUser)> CreateTestUserAndOrganization( IUserRepository userRepository, IOrganizationRepository organizationRepository, @@ -917,8 +978,11 @@ public class CipherRepositoryTests [DatabaseTheory, DatabaseData] public async Task DeleteCipherWithSecurityTaskAsync_Works( IOrganizationRepository organizationRepository, + IUserRepository userRepository, ICipherRepository cipherRepository, - ISecurityTaskRepository securityTaskRepository) + ISecurityTaskRepository securityTaskRepository, + INotificationRepository notificationRepository, + INotificationStatusRepository notificationStatusRepository) { var organization = await organizationRepository.CreateAsync(new Organization { @@ -928,6 +992,14 @@ public class CipherRepositoryTests BillingEmail = "" }); + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", }; await cipherRepository.CreateAsync(cipher1); @@ -953,6 +1025,20 @@ public class CipherRepositoryTests }; await securityTaskRepository.CreateManyAsync(tasks); + var notification = await notificationRepository.CreateAsync(new Notification + { + OrganizationId = organization.Id, + UserId = user.Id, + TaskId = tasks[1].Id, + CreationDate = DateTime.UtcNow, + RevisionDate = DateTime.UtcNow, + }); + await notificationStatusRepository.CreateAsync(new NotificationStatus + { + NotificationId = notification.Id, + UserId = user.Id, + ReadDate = DateTime.UtcNow, + }); // Delete cipher with pending security task await cipherRepository.DeleteAsync(cipher1); diff --git a/util/Migrator/DbScripts/2025-05-30_00_Notification_MarkAsDeletedByTask.sql b/util/Migrator/DbScripts/2025-05-30_00_Notification_MarkAsDeletedByTask.sql new file mode 100644 index 0000000000..43989a4cac --- /dev/null +++ b/util/Migrator/DbScripts/2025-05-30_00_Notification_MarkAsDeletedByTask.sql @@ -0,0 +1,35 @@ +CREATE OR ALTER PROCEDURE [dbo].[Notification_MarkAsDeletedByTask] + @TaskId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON; + + -- Collect UserIds as they are altered + DECLARE @UserIdsForAlteredNotifications TABLE ( + UserId UNIQUEIDENTIFIER + ); + + -- Update existing NotificationStatus as deleted + UPDATE ns + SET ns.DeletedDate = GETUTCDATE() + OUTPUT inserted.UserId INTO @UserIdsForAlteredNotifications + FROM NotificationStatus ns + INNER JOIN Notification n ON ns.NotificationId = n.Id + WHERE n.TaskId = @TaskId + AND ns.DeletedDate IS NULL; + + -- Insert NotificationStatus records for notifications that don't have one yet + INSERT INTO NotificationStatus (NotificationId, UserId, DeletedDate) + OUTPUT inserted.UserId INTO @UserIdsForAlteredNotifications + SELECT n.Id, n.UserId, GETUTCDATE() + FROM Notification n + LEFT JOIN NotificationStatus ns + ON n.Id = ns.NotificationId + WHERE n.TaskId = @TaskId + AND ns.NotificationId IS NULL; + + -- Return the UserIds associated with the altered notifications + SELECT u.UserId + FROM @UserIdsForAlteredNotifications u; +END +GO diff --git a/util/Migrator/DbScripts/2025-06-04-00_AddReadPendingAuthRequestsByUserId.sql b/util/Migrator/DbScripts/2025-06-04-00_AddReadPendingAuthRequestsByUserId.sql new file mode 100644 index 0000000000..86f4683cff --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-04-00_AddReadPendingAuthRequestsByUserId.sql @@ -0,0 +1,53 @@ +CREATE OR ALTER VIEW [dbo].[AuthRequestPendingDetailsView] +AS + WITH + PendingRequests + AS + ( + SELECT + [AR].*, + [D].[Id] AS [DeviceId], + ROW_NUMBER() OVER (PARTITION BY [AR].[RequestDeviceIdentifier] ORDER BY [AR].[CreationDate] DESC) AS [rn] + FROM [dbo].[AuthRequest] [AR] + LEFT JOIN [dbo].[Device] [D] + ON [AR].[RequestDeviceIdentifier] = [D].[Identifier] + AND [D].[UserId] = [AR].[UserId] + WHERE [AR].[Type] IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock + ) + SELECT + [PR].[Id], + [PR].[UserId], + [PR].[OrganizationId], + [PR].[Type], + [PR].[RequestDeviceIdentifier], + [PR].[RequestDeviceType], + [PR].[RequestIpAddress], + [PR].[RequestCountryName], + [PR].[ResponseDeviceId], + [PR].[AccessCode], + [PR].[PublicKey], + [PR].[Key], + [PR].[MasterPasswordHash], + [PR].[Approved], + [PR].[CreationDate], + [PR].[ResponseDate], + [PR].[AuthenticationDate], + [PR].[DeviceId] + FROM [PendingRequests] [PR] + WHERE [PR].[rn] = 1 + AND [PR].[Approved] IS NULL -- since we only want pending requests we only want the most recent that is also approved = null + GO + +CREATE OR ALTER PROCEDURE [dbo].[AuthRequest_ReadPendingByUserId] + @UserId UNIQUEIDENTIFIER, + @ExpirationMinutes INT +AS +BEGIN + SET NOCOUNT ON + + SELECT * + FROM [dbo].[AuthRequestPendingDetailsView] + WHERE [UserId] = @UserId + AND [CreationDate] >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) +END + GO diff --git a/util/Migrator/DbScripts/2025-06-09_00_AddMemberAccessReportStoreProcedure.sql b/util/Migrator/DbScripts/2025-06-09_00_AddMemberAccessReportStoreProcedure.sql new file mode 100644 index 0000000000..afb682e97a --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-09_00_AddMemberAccessReportStoreProcedure.sql @@ -0,0 +1,70 @@ +CREATE OR ALTER PROC dbo.MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + IF @OrganizationId IS NULL + THROW 50000, 'OrganizationId cannot be null', 1; + +SELECT + U.Id AS UserGuid, + U.Name AS UserName, + U.Email, + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + NULL AS GroupId, + NULL AS GroupName, + CU.ReadOnly, + CU.HidePasswords, + CU.Manage, + Cipher.Id AS CipherId +FROM dbo.OrganizationUser OU + INNER JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.CollectionUser CU ON CU.OrganizationUserId = OU.Id + INNER JOIN dbo.Collection C ON C.Id = CU.CollectionId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId + AND Cipher.OrganizationId = @OrganizationId +WHERE OU.Status in (0,1,2) + AND Cipher.DeletedDate IS NULL + +UNION ALL + +-- Group-based collection permissions +SELECT + U.Id AS UserGuid, + U.Name AS UserName, + U.Email, + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + G.Id AS GroupId, + G.Name AS GroupName, + CG.ReadOnly, + CG.HidePasswords, + CG.Manage, + Cipher.Id AS CipherId +FROM dbo.OrganizationUser OU + INNER JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.GroupUser GU ON GU.OrganizationUserId = OU.Id + INNER JOIN dbo.[Group] G ON G.Id = GU.GroupId + INNER JOIN dbo.CollectionGroup CG ON CG.GroupId = G.Id + INNER JOIN dbo.Collection C ON C.Id = CG.CollectionId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId + AND Cipher.OrganizationId = @OrganizationId +WHERE OU.Status in (0,1,2) + AND Cipher.DeletedDate IS NULL + +GO diff --git a/util/Migrator/DbScripts/2025-06-12_00_AlterMemberAccessReportStoreProcedure.sql b/util/Migrator/DbScripts/2025-06-12_00_AlterMemberAccessReportStoreProcedure.sql new file mode 100644 index 0000000000..fb3b842fc6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-12_00_AlterMemberAccessReportStoreProcedure.sql @@ -0,0 +1,94 @@ +CREATE OR ALTER PROC dbo.MemberAccessReport_GetMemberAccessCipherDetailsByOrganizationId + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + IF @OrganizationId IS NULL + THROW 50000, 'OrganizationId cannot be null', 1; + + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + NULL AS GroupId, + NULL AS GroupName, + CU.ReadOnly, + CU.HidePasswords, + CU.Manage, + Cipher.Id AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.CollectionUser CU ON CU.OrganizationUserId = OU.Id + INNER JOIN dbo.Collection C ON C.Id = CU.CollectionId and C.OrganizationId = @OrganizationId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId AND Cipher.OrganizationId = @OrganizationId + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND Cipher.DeletedDate IS NULL +UNION ALL + -- Group-based collection permissions + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + CC.CollectionId, + C.Name AS CollectionName, + G.Id AS GroupId, + G.Name AS GroupName, + CG.ReadOnly, + CG.HidePasswords, + CG.Manage, + Cipher.Id AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId + AND O.Id = @OrganizationId + AND O.Enabled = 1 + INNER JOIN dbo.GroupUser GU ON GU.OrganizationUserId = OU.Id + INNER JOIN dbo.[Group] G ON G.Id = GU.GroupId + INNER JOIN dbo.CollectionGroup CG ON CG.GroupId = G.Id + INNER JOIN dbo.Collection C ON C.Id = CG.CollectionId AND C.OrganizationId = @OrganizationId + INNER JOIN dbo.CollectionCipher CC ON CC.CollectionId = C.Id + INNER JOIN dbo.Cipher Cipher ON Cipher.Id = CC.CipherId and Cipher.OrganizationId = @OrganizationId + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND Cipher.DeletedDate IS NULL +UNION ALL + -- Users without collection access (invited users) + -- typically invited users who have not yet accepted the invitation + -- and not yet assigned to any collection + SELECT + OU.Id AS UserGuid, + U.Name AS UserName, + ISNULL(U.Email, OU.Email) as 'Email', + U.TwoFactorProviders, + U.UsesKeyConnector, + OU.ResetPasswordKey, + null as CollectionId, + null AS CollectionName, + NULL AS GroupId, + NULL AS GroupName, + null as [ReadOnly], + null as HidePasswords, + null as Manage, + null AS CipherId + FROM dbo.OrganizationUser OU + LEFT JOIN dbo.[User] U ON U.Id = OU.UserId + INNER JOIN dbo.Organization O ON O.Id = OU.OrganizationId AND O.Id = @OrganizationId AND O.Enabled = 1 + WHERE OU.Status IN (0,1,2) -- Invited, Accepted and Confirmed Users + AND OU.Id not in ( + select OU1.Id from dbo.OrganizationUser OU1 + inner join dbo.CollectionUser CU1 on CU1.OrganizationUserId = OU1.Id + WHERE OU1.OrganizationId = @organizationId + ) + +GO diff --git a/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql b/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql new file mode 100644 index 0000000000..ad256c3ea6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13-00_OrganizationReport.sql @@ -0,0 +1,85 @@ +IF OBJECT_ID('dbo.OrganizationReport') IS NULL +BEGIN + CREATE TABLE [dbo].[OrganizationReport] + ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Date] DATETIME2 (7) NOT NULL, + [ReportData] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationReport] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationReport_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); + + CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId] ON [dbo].[OrganizationReport]([OrganizationId] ASC); + + CREATE NONCLUSTERED INDEX [IX_OrganizationReport_OrganizationId_Date] ON [dbo].[OrganizationReport]([OrganizationId] ASC, [Date] DESC); + +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationReportView] +AS + SELECT + * + FROM + [dbo].[OrganizationReport]; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Date DATETIME2(7), + @ReportData NVARCHAR(MAX), + @CreationDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationReport]( + [Id], + [OrganizationId], + [Date], + [ReportData], + [CreationDate] + ) + VALUES ( + @Id, + @OrganizationId, + @Date, + @ReportData, + @CreationDate + ); +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationReport] + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationReportView] + WHERE [OrganizationId] = @OrganizationId; +GO diff --git a/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql b/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql new file mode 100644 index 0000000000..2f2a357ad3 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13-01_OrganizationApplication.sql @@ -0,0 +1,97 @@ +IF OBJECT_ID('dbo.OrganizationApplication') IS NULL +BEGIN + CREATE TABLE [dbo].[OrganizationApplication] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Applications] NVARCHAR(MAX) NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + CONSTRAINT [PK_OrganizationApplication] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganizationApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) + ); + + CREATE NONCLUSTERED INDEX [IX_OrganizationApplication_OrganizationId] + ON [dbo].[OrganizationApplication]([OrganizationId] ASC); +END +GO + +CREATE OR ALTER VIEW [dbo].[OrganizationApplicationView] AS + SELECT * FROM [dbo].[OrganizationApplication]; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + INSERT INTO [dbo].[OrganizationApplication] + ( + [Id], + [OrganizationId], + [Applications], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Applications, + @CreationDate, + @RevisionDate + ); +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [OrganizationId] = @OrganizationId; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_ReadById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + SELECT + * + FROM [dbo].[OrganizationApplicationView] + WHERE [Id] = @Id; +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Applications NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS + SET NOCOUNT ON; + + UPDATE [dbo].[OrganizationApplication] + SET + [OrganizationId] = @OrganizationId, + [Applications] = @Applications, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE [Id] = @Id; + +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationApplication_DeleteById] + @Id UNIQUEIDENTIFIER +AS + SET NOCOUNT ON; + + DELETE FROM [dbo].[OrganizationApplication] + WHERE [Id] = @Id; +GO diff --git a/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql b/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql new file mode 100644 index 0000000000..70e6e7a2c6 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-13_02_UpdateOrgDeleteByIdProc.sql @@ -0,0 +1,161 @@ +CREATE OR ALTER PROCEDURE [dbo].[Organization_DeleteById] + @Id UNIQUEIDENTIFIER +WITH RECOMPILE +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @Id + + DECLARE @BatchSize INT = 100 + WHILE @BatchSize > 0 + BEGIN + BEGIN TRANSACTION Organization_DeleteById_Ciphers + + DELETE TOP(@BatchSize) + FROM + [dbo].[Cipher] + WHERE + [UserId] IS NULL + AND [OrganizationId] = @Id + + SET @BatchSize = @@ROWCOUNT + + COMMIT TRANSACTION Organization_DeleteById_Ciphers + END + + BEGIN TRANSACTION Organization_DeleteById + + DELETE + FROM + [dbo].[AuthRequest] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[SsoUser] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[SsoConfig] + WHERE + [OrganizationId] = @Id + + DELETE CU + FROM + [dbo].[CollectionUser] CU + INNER JOIN + [dbo].[OrganizationUser] OU ON [CU].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE AP + FROM + [dbo].[AccessPolicy] AP + INNER JOIN + [dbo].[OrganizationUser] OU ON [AP].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE GU + FROM + [dbo].[GroupUser] GU + INNER JOIN + [dbo].[OrganizationUser] OU ON [GU].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + + DELETE + FROM + [dbo].[OrganizationUser] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[ProviderOrganization] + WHERE + [OrganizationId] = @Id + + EXEC [dbo].[OrganizationApiKey_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationConnection_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationSponsorship_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationDomain_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationIntegration_OrganizationDeleted] @Id + + DELETE + FROM + [dbo].[Project] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[Secret] + WHERE + [OrganizationId] = @Id + + DELETE AK + FROM + [dbo].[ApiKey] AK + INNER JOIN + [dbo].[ServiceAccount] SA ON [AK].[ServiceAccountId] = [SA].[Id] + WHERE + [SA].[OrganizationId] = @Id + + DELETE AP + FROM + [dbo].[AccessPolicy] AP + INNER JOIN + [dbo].[ServiceAccount] SA ON [AP].[GrantedServiceAccountId] = [SA].[Id] + WHERE + [SA].[OrganizationId] = @Id + + DELETE + FROM + [dbo].[ServiceAccount] + WHERE + [OrganizationId] = @Id + + -- Delete Notification Status + DELETE + NS + FROM + [dbo].[NotificationStatus] NS + INNER JOIN + [dbo].[Notification] N ON N.[Id] = NS.[NotificationId] + WHERE + N.[OrganizationId] = @Id + + -- Delete Notification + DELETE + FROM + [dbo].[Notification] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Application + DELETE + FROM + [dbo].[OrganizationApplication] + WHERE + [OrganizationId] = @Id + + -- Delete Organization Report + DELETE + FROM + [dbo].[OrganizationReport] + WHERE + [OrganizationId] = @Id + + DELETE + FROM + [dbo].[Organization] + WHERE + [Id] = @Id + COMMIT TRANSACTION Organization_DeleteById + END + GO diff --git a/util/Migrator/DbScripts/2025-06-19_00_AddFiltersToOrganizationIntegrationConfiguration.sql b/util/Migrator/DbScripts/2025-06-19_00_AddFiltersToOrganizationIntegrationConfiguration.sql new file mode 100644 index 0000000000..686c46e514 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-19_00_AddFiltersToOrganizationIntegrationConfiguration.sql @@ -0,0 +1,104 @@ +/* add new column "Filters", nullable to OrganizationIntegrationConfiguration */ + +IF COL_LENGTH('[dbo].[OrganizationIntegrationConfiguration]', 'Filters') IS NULL + BEGIN + ALTER TABLE + [dbo].[OrganizationIntegrationConfiguration] + ADD + [Filters] VARCHAR (MAX) NULL + END +GO + +/* add column "Filters" to OrganizationIntegrationConfigurationDetailsView */ +CREATE OR ALTER VIEW [dbo].[OrganizationIntegrationConfigurationDetailsView] +AS +SELECT + oi.[OrganizationId], + oi.[Type] AS [IntegrationType], + oic.[EventType], + oic.[Configuration], + oi.[Configuration] AS [IntegrationConfiguration], + oic.[Template], + oic.[Filters] +FROM + [dbo].[OrganizationIntegrationConfiguration] oic + INNER JOIN + [dbo].[OrganizationIntegration] oi ON oi.[Id] = oic.[OrganizationIntegrationId] +GO + +/* add column "Filters" to OrganizationIntegrationConfigurationView */ +CREATE OR ALTER VIEW [dbo].[OrganizationIntegrationConfigurationView] +AS +SELECT + * +FROM + [dbo].[OrganizationIntegrationConfiguration] +GO + +/* add column to OrganizationIntegrationConfiguration_Create */ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationIntegrationConfiguration_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationIntegrationId UNIQUEIDENTIFIER, + @EventType SMALLINT, + @Configuration VARCHAR(MAX), + @Template VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Filters VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationIntegrationConfiguration] + ( + [Id], + [OrganizationIntegrationId], + [EventType], + [Configuration], + [Template], + [CreationDate], + [RevisionDate], + [Filters] + ) + VALUES + ( + @Id, + @OrganizationIntegrationId, + @EventType, + @Configuration, + @Template, + @CreationDate, + @RevisionDate, + @Filters + ) +END +GO + +/* add column to OrganizationIntegrationConfiguration_Update */ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationIntegrationConfiguration_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationIntegrationId UNIQUEIDENTIFIER, + @EventType SMALLINT, + @Configuration VARCHAR(MAX), + @Template VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Filters VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + +UPDATE + [dbo].[OrganizationIntegrationConfiguration] +SET + [OrganizationIntegrationId] = @OrganizationIntegrationId, + [EventType] = @EventType, + [Configuration] = @Configuration, + [Template] = @Template, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [Filters] = @Filters +WHERE + [Id] = @Id +END +GO diff --git a/util/Migrator/DbScripts/2025-06-24_00_AttachmentCipherUpdateDetails.sql b/util/Migrator/DbScripts/2025-06-24_00_AttachmentCipherUpdateDetails.sql new file mode 100644 index 0000000000..1af98dba76 --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-24_00_AttachmentCipherUpdateDetails.sql @@ -0,0 +1,118 @@ +CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), -- not used + @Folders NVARCHAR(MAX), -- not used + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @FolderId UNIQUEIDENTIFIER, + @Favorite BIT, + @Edit BIT, -- not used + @ViewPassword BIT, -- not used + @Manage BIT, -- not used + @OrganizationUseTotp BIT, -- not used + @DeletedDate DATETIME2(2), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"') + DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey) + + UPDATE + [dbo].[Cipher] + SET + [UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + [OrganizationId] = @OrganizationId, + [Type] = @Type, + [Data] = @Data, + [Folders] = + CASE + WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN + CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') + WHEN @FolderId IS NOT NULL THEN + JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50))) + ELSE + JSON_MODIFY([Folders], @UserIdPath, NULL) + END, + [Favorites] = + CASE + WHEN @Favorite = 1 AND [Favorites] IS NULL THEN + CONCAT('{', @UserIdKey, ':true}') + WHEN @Favorite = 1 THEN + JSON_MODIFY([Favorites], @UserIdPath, CAST(1 AS BIT)) + ELSE + JSON_MODIFY([Favorites], @UserIdPath, NULL) + END, + [Attachments] = @Attachments, + [Reprompt] = @Reprompt, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [DeletedDate] = @DeletedDate, + [Key] = @Key + WHERE + [Id] = @Id + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Type TINYINT, + @Data NVARCHAR(MAX), + @Favorites NVARCHAR(MAX), + @Folders NVARCHAR(MAX), + @Attachments NVARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @DeletedDate DATETIME2(7), + @Reprompt TINYINT, + @Key VARCHAR(MAX) = NULL +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Cipher] + SET + [UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END, + [OrganizationId] = @OrganizationId, + [Type] = @Type, + [Data] = @Data, + [Favorites] = @Favorites, + [Folders] = @Folders, + [Attachments] = @Attachments, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [DeletedDate] = @DeletedDate, + [Reprompt] = @Reprompt, + [Key] = @Key + WHERE + [Id] = @Id + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId + END + ELSE IF @UserId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId + END +END +GO diff --git a/util/Migrator/DbScripts/2025-06-26_01_CascadeDeleteNotificationStatus.sql b/util/Migrator/DbScripts/2025-06-26_01_CascadeDeleteNotificationStatus.sql new file mode 100644 index 0000000000..474ac14a7a --- /dev/null +++ b/util/Migrator/DbScripts/2025-06-26_01_CascadeDeleteNotificationStatus.sql @@ -0,0 +1,10 @@ +BEGIN + IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_NotificationStatus_Notification') + BEGIN + ALTER TABLE [dbo].[NotificationStatus] DROP CONSTRAINT [FK_NotificationStatus_Notification] + END + + ALTER TABLE [dbo].[NotificationStatus] + ADD CONSTRAINT [FK_NotificationStatus_Notification] FOREIGN KEY ([NotificationId]) REFERENCES [dbo].[Notification]([Id]) ON DELETE CASCADE +END +GO diff --git a/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs b/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs new file mode 100644 index 0000000000..bd813ae927 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs @@ -0,0 +1,3166 @@ +// +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("20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql")] + partial class _20250609_00_AddMemberAccessReportStoreProceduresql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs b/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs new file mode 100644 index 0000000000..2c63b0bcfa --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250609182150_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class _20250609_00_AddMemberAccessReportStoreProceduresql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationMemberBaseDetails", + columns: table => new + { + UserGuid = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + UserName = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + Email = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + TwoFactorProviders = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + UsesKeyConnector = table.Column(type: "tinyint(1)", nullable: false), + ResetPasswordKey = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CollectionId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + GroupId = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), + GroupName = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CollectionName = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + ReadOnly = table.Column(type: "tinyint(1)", nullable: true), + HidePasswords = table.Column(type: "tinyint(1)", nullable: true), + Manage = table.Column(type: "tinyint(1)", nullable: true), + CipherId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci") + }, + constraints: table => + { + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationMemberBaseDetails"); + } +} diff --git a/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..0c8987658f --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3252 @@ +// +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("20250613215532_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..13650faf47 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250613215532_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,95 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Applications = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false), + RevisionDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Date = table.Column(type: "datetime(6)", nullable: false), + ReportData = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs b/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs new file mode 100644 index 0000000000..731289a770 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs @@ -0,0 +1,3175 @@ +// +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("20250619185012_AddFiltersToOrganizationIntegrationConfiguration")] + partial class AddFiltersToOrganizationIntegrationConfiguration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("tinyint(1)"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("LimitItemDeletion") + .HasColumnType("tinyint(1)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UseOrganizationDomains") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseRiskInsights") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Configuration") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("Filters") + .HasColumnType("longtext"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Template") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DiscountId") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AssignedSeats") + .HasColumnType("int"); + + b.Property("ClientId") + .HasColumnType("char(36)"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Created") + .HasColumnType("datetime(6)"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Total") + .HasColumnType("decimal(65,30)"); + + b.Property("UsedSeats") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllocatedSeats") + .HasColumnType("int"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("PurchasedSeats") + .HasColumnType("int"); + + b.Property("SeatMinimum") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("varchar(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAtTime") + .HasColumnType("datetime(6)"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("longtext"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Active") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("longtext"); + + b.Property("EncryptedPublicKey") + .HasColumnType("longtext"); + + b.Property("EncryptedUserKey") + .HasColumnType("longtext"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("tinyint(1)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasColumnType("longtext"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("VerifyDevices") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("varchar(3000)"); + + b.Property("ClientType") + .HasColumnType("tinyint unsigned"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Global") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Priority") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("TaskId") + .HasColumnType("char(36)"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("NotificationId") + .HasColumnType("char(36)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("ReadDate") + .HasColumnType("datetime(6)"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("LastActivityDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("varchar(34)"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("varchar(128)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.cs b/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.cs new file mode 100644 index 0000000000..62e28a63b5 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20250619185012_AddFiltersToOrganizationIntegrationConfiguration.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class AddFiltersToOrganizationIntegrationConfiguration : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index a22b5baf85..0f983f8f06 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -22,6 +22,53 @@ namespace Bit.MySqlMigrations.Migrations MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CollectionName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("GroupName") + .HasColumnType("longtext"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("Manage") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("UserGuid") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasColumnType("longtext"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -269,6 +316,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("EventType") .HasColumnType("int"); + b.Property("Filters") + .HasColumnType("longtext"); + b.Property("OrganizationIntegrationId") .HasColumnType("char(36)"); @@ -920,6 +970,92 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("ProviderPlan", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Uri") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => { b.Property("Id") @@ -2023,34 +2159,6 @@ namespace Bit.MySqlMigrations.Migrations 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") @@ -2532,6 +2640,39 @@ namespace Bit.MySqlMigrations.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -2825,17 +2966,6 @@ namespace Bit.MySqlMigrations.Migrations 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") diff --git a/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs b/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs new file mode 100644 index 0000000000..2056f50c54 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs @@ -0,0 +1,3172 @@ +// +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("20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql")] + partial class _20250609_00_AddMemberAccessReportStoreProceduresql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs b/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs new file mode 100644 index 0000000000..bd765900f6 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250609182157_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class _20250609_00_AddMemberAccessReportStoreProceduresql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationMemberBaseDetails", + columns: table => new + { + UserGuid = table.Column(type: "uuid", nullable: true), + UserName = table.Column(type: "text", nullable: true), + Email = table.Column(type: "text", nullable: true), + TwoFactorProviders = table.Column(type: "text", nullable: true), + UsesKeyConnector = table.Column(type: "boolean", nullable: false), + ResetPasswordKey = table.Column(type: "text", nullable: true), + CollectionId = table.Column(type: "uuid", nullable: true), + GroupId = table.Column(type: "uuid", nullable: true), + GroupName = table.Column(type: "text", nullable: true), + CollectionName = table.Column(type: "text", nullable: true), + ReadOnly = table.Column(type: "boolean", nullable: true), + HidePasswords = table.Column(type: "boolean", nullable: true), + Manage = table.Column(type: "boolean", nullable: true), + CipherId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationMemberBaseDetails"); + } +} diff --git a/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..c8d49c17f8 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3258 @@ +// +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("20250613215536_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..55fe031009 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250613215536_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,91 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Applications = table.Column(type: "text", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false), + RevisionDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Date = table.Column(type: "timestamp with time zone", nullable: false), + ReportData = table.Column(type: "text", nullable: false), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs b/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs new file mode 100644 index 0000000000..0c445c92bb --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs @@ -0,0 +1,3181 @@ +// +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("20250619184947_AddFiltersToOrganizationIntegrationConfiguration")] + partial class AddFiltersToOrganizationIntegrationConfiguration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreation") + .HasColumnType("boolean"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("boolean"); + + b.Property("LimitItemDeletion") + .HasColumnType("boolean"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UseOrganizationDomains") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseRiskInsights") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Configuration") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .HasColumnType("integer"); + + b.Property("Filters") + .HasColumnType("text"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Template") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountId") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AssignedSeats") + .HasColumnType("integer"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Created") + .HasColumnType("timestamp with time zone"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasColumnType("numeric"); + + b.Property("UsedSeats") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllocatedSeats") + .HasColumnType("integer"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("PurchasedSeats") + .HasColumnType("integer"); + + b.Property("SeatMinimum") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("character varying(449)"); + + b.Property("AbsoluteExpiration") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAtTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("bigint"); + + b.Property("Value") + .IsRequired() + .HasColumnType("bytea"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("text"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Active") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("text"); + + b.Property("EncryptedPublicKey") + .HasColumnType("text"); + + b.Property("EncryptedUserKey") + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("boolean"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.Property("VerifyDevices") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("character varying(3000)"); + + b.Property("ClientType") + .HasColumnType("smallint"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Global") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Priority") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TaskId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReadDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("LastActivityDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.cs b/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.cs new file mode 100644 index 0000000000..7faa197aae --- /dev/null +++ b/util/PostgresMigrations/Migrations/20250619184947_AddFiltersToOrganizationIntegrationConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class AddFiltersToOrganizationIntegrationConfiguration : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index a016e9f350..aaf6124d7b 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -23,6 +23,53 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CollectionName") + .HasColumnType("text"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("GroupName") + .HasColumnType("text"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("Manage") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("UserGuid") + .HasColumnType("uuid"); + + b.Property("UserName") + .HasColumnType("text"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -272,6 +319,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("EventType") .HasColumnType("integer"); + b.Property("Filters") + .HasColumnType("text"); + b.Property("OrganizationIntegrationId") .HasColumnType("uuid"); @@ -925,6 +975,92 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("ProviderPlan", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Uri") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => { b.Property("Id") @@ -2029,34 +2165,6 @@ namespace Bit.PostgresMigrations.Migrations 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") @@ -2538,6 +2646,39 @@ namespace Bit.PostgresMigrations.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -2831,17 +2972,6 @@ namespace Bit.PostgresMigrations.Migrations 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") diff --git a/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs b/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs new file mode 100644 index 0000000000..b6bb26bbe5 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.Designer.cs @@ -0,0 +1,3155 @@ +// +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("20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql")] + partial class _20250609_00_AddMemberAccessReportStoreProceduresql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.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") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs b/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs new file mode 100644 index 0000000000..caace2ca13 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250609182153_2025-06-09_00_AddMemberAccessReportStoreProcedure.sql.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class _20250609_00_AddMemberAccessReportStoreProceduresql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationMemberBaseDetails", + columns: table => new + { + UserGuid = table.Column(type: "TEXT", nullable: true), + UserName = table.Column(type: "TEXT", nullable: true), + Email = table.Column(type: "TEXT", nullable: true), + TwoFactorProviders = table.Column(type: "TEXT", nullable: true), + UsesKeyConnector = table.Column(type: "INTEGER", nullable: false), + ResetPasswordKey = table.Column(type: "TEXT", nullable: true), + CollectionId = table.Column(type: "TEXT", nullable: true), + GroupId = table.Column(type: "TEXT", nullable: true), + GroupName = table.Column(type: "TEXT", nullable: true), + CollectionName = table.Column(type: "TEXT", nullable: true), + ReadOnly = table.Column(type: "INTEGER", nullable: true), + HidePasswords = table.Column(type: "INTEGER", nullable: true), + Manage = table.Column(type: "INTEGER", nullable: true), + CipherId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationMemberBaseDetails"); + } +} diff --git a/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs new file mode 100644 index 0000000000..f6c241ff5e --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.Designer.cs @@ -0,0 +1,3241 @@ +// +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("20250613215539_2025-06-13-00_OrganizationReport.sql")] + partial class _2025061300_OrganizationReportsql + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs new file mode 100644 index 0000000000..c2e7afdc22 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250613215539_2025-06-13-00_OrganizationReport.sql.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class _2025061300_OrganizationReportsql : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationApplication", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + Applications = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false), + RevisionDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationApplication", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationApplication_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "OrganizationReport", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + OrganizationId = table.Column(type: "TEXT", nullable: false), + Date = table.Column(type: "TEXT", nullable: false), + ReportData = table.Column(type: "TEXT", nullable: false), + CreationDate = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationReport", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationReport_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_Id", + table: "OrganizationApplication", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationApplication_OrganizationId", + table: "OrganizationApplication", + column: "OrganizationId"); + + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_Id", + table: "OrganizationReport", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId", + table: "OrganizationReport", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationReport_OrganizationId_Date", + table: "OrganizationReport", + columns: ["OrganizationId", "Date"], + descending: [false, true]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationApplication"); + + migrationBuilder.DropTable( + name: "OrganizationReport"); + } +} diff --git a/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs b/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs new file mode 100644 index 0000000000..5cf399164c --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.Designer.cs @@ -0,0 +1,3164 @@ +// +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("20250619184959_AddFiltersToOrganizationIntegrationConfiguration")] + partial class AddFiltersToOrganizationIntegrationConfiguration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("LimitCollectionCreation") + .HasColumnType("INTEGER"); + + b.Property("LimitCollectionDeletion") + .HasColumnType("INTEGER"); + + b.Property("LimitItemDeletion") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseAdminSponsoredFamilies") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UseOrganizationDomains") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseRiskInsights") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationIntegration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Configuration") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EventType") + .HasColumnType("INTEGER"); + + b.Property("Filters") + .HasColumnType("TEXT"); + + b.Property("OrganizationIntegrationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Template") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationIntegrationId"); + + b.ToTable("OrganizationIntegrationConfiguration", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DiscountId") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestCountryName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ClientOrganizationMigrationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("GatewayCustomerId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "OrganizationId") + .IsUnique(); + + b.ToTable("ClientOrganizationMigrationRecord", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("InstallationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationInstallation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AssignedSeats") + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .HasColumnType("TEXT"); + + b.Property("ClientName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("InvoiceId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("InvoiceNumber") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Total") + .HasColumnType("TEXT"); + + b.Property("UsedSeats") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderInvoiceItem", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AllocatedSeats") + .HasColumnType("INTEGER"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("PurchasedSeats") + .HasColumnType("INTEGER"); + + b.Property("SeatMinimum") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("Id", "PlanType") + .IsUnique(); + + b.ToTable("ProviderPlan", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => + { + b.Property("Id") + .HasMaxLength(449) + .HasColumnType("TEXT"); + + b.Property("AbsoluteExpiration") + .HasColumnType("TEXT"); + + b.Property("ExpiresAtTime") + .HasColumnType("TEXT"); + + b.Property("SlidingExpirationInSeconds") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("BLOB"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpiresAtTime") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Cache", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DefaultUserCollectionEmail") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "Identifier") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("DomainName") + .HasColumnType("TEXT"); + + b.Property("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("IsAdminInitiated") + .HasColumnType("INTEGER"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("Emails") + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("VerifyDevices") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Premium", "PremiumExpirationDate", "RenewalReminderDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Body") + .HasMaxLength(3000) + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Global") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("TaskId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("TaskId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("ClientType", "Global", "UserId", "OrganizationId", "Priority", "CreationDate") + .IsDescending(false, false, false, false, true, true) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Notification", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("NotificationId") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("ReadDate") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "NotificationId") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("NotificationId"); + + b.ToTable("NotificationStatus", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Platform.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator().HasValue("AccessPolicy"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecretHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("CipherId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SecurityTask", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegrationConfiguration", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.OrganizationIntegration", "OrganizationIntegration") + .WithMany() + .HasForeignKey("OrganizationIntegrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrganizationIntegration"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.OrganizationInstallation", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Platform.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Installation"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderInvoiceItem", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.ProviderPlan", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Collections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task") + .WithMany() + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("Task"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.NotificationStatus", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.NotificationCenter.Models.Notification", "Notification") + .WithMany() + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ApiKeys") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher") + .WithMany() + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedSecret"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany("ProjectAccessPolicies") + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", "GrantedSecret") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedSecretId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedSecret"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Collections"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ProjectAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.cs b/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.cs new file mode 100644 index 0000000000..f526021ee3 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20250619184959_AddFiltersToOrganizationIntegrationConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class AddFiltersToOrganizationIntegrationConfiguration : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Filters", + table: "OrganizationIntegrationConfiguration"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index b730831439..4bed680478 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -17,6 +17,53 @@ namespace Bit.SqliteMigrations.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "8.0.8"); + modelBuilder.Entity("Bit.Core.Dirt.Reports.Models.Data.OrganizationMemberBaseDetail", b => + { + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CollectionName") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("GroupName") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("Manage") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("UserGuid") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.ToTable("OrganizationMemberBaseDetails"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { b.Property("Id") @@ -264,6 +311,9 @@ namespace Bit.SqliteMigrations.Migrations b.Property("EventType") .HasColumnType("INTEGER"); + b.Property("Filters") + .HasColumnType("TEXT"); + b.Property("OrganizationIntegrationId") .HasColumnType("TEXT"); @@ -909,6 +959,92 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("ProviderPlan", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Applications") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationApplication", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ReportData") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationReport", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Uri") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("PasswordHealthReportApplication", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cache", b => { b.Property("Id") @@ -2012,34 +2148,6 @@ namespace Bit.SqliteMigrations.Migrations 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") @@ -2521,6 +2629,39 @@ namespace Bit.SqliteMigrations.Migrations b.Navigation("Provider"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.OrganizationReport", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Dirt.Models.PasswordHealthReportApplication", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => { b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") @@ -2814,17 +2955,6 @@ namespace Bit.SqliteMigrations.Migrations 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")