diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3cccda576..9e4e552ca 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,7 +7,7 @@ "commands": ["swagger"] }, "dotnet-ef": { - "version": "8.0.6", + "version": "8.0.7", "commands": ["dotnet-ef"] } } diff --git a/.devcontainer/community_dev/devcontainer.json b/.devcontainer/community_dev/devcontainer.json index b9c31709a..78a652a84 100644 --- a/.devcontainer/community_dev/devcontainer.json +++ b/.devcontainer/community_dev/devcontainer.json @@ -3,6 +3,13 @@ "dockerComposeFile": "../../.devcontainer/bitwarden_common/docker-compose.yml", "service": "bitwarden_server", "workspaceFolder": "/workspace", + "mounts": [ + { + "source": "../../dev/.data/keys", + "target": "/home/vscode/.aspnet/DataProtection-Keys", + "type": "bind" + } + ], "customizations": { "vscode": { "settings": {}, diff --git a/.devcontainer/internal_dev/devcontainer.json b/.devcontainer/internal_dev/devcontainer.json index 6c2b7350b..ee9ab7a96 100644 --- a/.devcontainer/internal_dev/devcontainer.json +++ b/.devcontainer/internal_dev/devcontainer.json @@ -3,8 +3,16 @@ "dockerComposeFile": [ "../../.devcontainer/bitwarden_common/docker-compose.yml", "../../.devcontainer/internal_dev/docker-compose.override.yml" - ], "service": "bitwarden_server", + ], + "service": "bitwarden_server", "workspaceFolder": "/workspace", + "mounts": [ + { + "source": "../../dev/.data/keys", + "target": "/home/vscode/.aspnet/DataProtection-Keys", + "type": "bind" + } + ], "customizations": { "vscode": { "settings": {}, diff --git a/.github/renovate.json b/.github/renovate.json index a5405bb3f..46c0d1c35 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -41,9 +41,9 @@ "reviewers": ["team:team-auth-dev"] }, { - "matchPackageNames": ["bootstrap", "del", "gulp"], + "matchPackageNames": ["bootstrap"], "matchUpdateTypes": ["major"], - "description": "Lock bootstrap, del, and gulp major versions due to ASP.NET conflicts", + "description": "Lock bootstrap major versions due to ASP.NET conflicts", "enabled": false }, { @@ -59,8 +59,6 @@ "DuoUniversal", "Fido2.AspNet", "Duende.IdentityServer", - "Microsoft.Azure.Cosmos", - "Microsoft.Extensions.Caching.StackExchangeRedis", "Microsoft.Extensions.Identity.Stores", "Otp.NET", "Sustainsys.Saml2.AspNetCore2", @@ -112,12 +110,15 @@ "dbup-sqlserver", "dotnet-ef", "linq2db.EntityFrameworkCore", + "Microsoft.Azure.Cosmos", "Microsoft.Data.SqlClient", "Microsoft.EntityFrameworkCore.Design", "Microsoft.EntityFrameworkCore.InMemory", "Microsoft.EntityFrameworkCore.Relational", "Microsoft.EntityFrameworkCore.Sqlite", "Microsoft.EntityFrameworkCore.SqlServer", + "Microsoft.Extensions.Caching.SqlServer", + "Microsoft.Extensions.Caching.StackExchangeRedis", "Npgsql.EntityFrameworkCore.PostgreSQL", "Pomelo.EntityFrameworkCore.MySql" ], diff --git a/.github/workflows/_move_finalization_db_scripts.yml b/.github/workflows/_move_finalization_db_scripts.yml index 0b1d18797..fc8a7b76e 100644 --- a/.github/workflows/_move_finalization_db_scripts.yml +++ b/.github/workflows/_move_finalization_db_scripts.yml @@ -18,7 +18,7 @@ jobs: copy_finalization_scripts: ${{ steps.check-finalization-scripts-existence.outputs.copy_finalization_scripts }} steps: - name: Log in to Azure - uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -30,7 +30,7 @@ jobs: secrets: "github-pat-bitwarden-devops-bot-repo-scope" - name: Check out branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} @@ -54,7 +54,7 @@ jobs: if: ${{ needs.setup.outputs.copy_finalization_scripts == 'true' }} steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 @@ -94,7 +94,7 @@ jobs: echo "moved_files=$moved_files" >> $GITHUB_OUTPUT - name: Log in to Azure - production subscription - uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -108,7 +108,7 @@ jobs: devops-alerts-slack-webhook-url" - name: Import GPG keys - uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 + uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 with: gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} @@ -154,7 +154,7 @@ jobs: - name: Notify Slack about creation of PR if: ${{ steps.commit.outputs.pr_needed == 'true' }} - uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 + uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0 env: SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa74ead1b..b156e0593 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 @@ -68,13 +68,13 @@ jobs: node: true steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - name: Set up Node - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2 with: cache: "npm" cache-dependency-path: "**/package-lock.json" @@ -173,7 +173,7 @@ jobs: dotnet: true steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check branch to publish env: @@ -190,7 +190,7 @@ jobs: ########## ACRs ########## - name: Log in to Azure - production subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -198,7 +198,7 @@ jobs: run: az acr login -n bitwardenprod - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -275,14 +275,14 @@ jobs: - name: Scan Docker image id: container-scan - uses: anchore/scan-action@3343887d815d7b07465f6fdcd395bd66508d486a # v3.6.4 + uses: anchore/scan-action@d43cc1dfea6a99ed123bf8f3133f1797c9b44492 # v4.1.0 with: image: ${{ steps.image-tags.outputs.primary_tag }} fail-build: false output-format: sarif - name: Upload Grype results to GitHub - uses: github/codeql-action/upload-sarif@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} @@ -292,13 +292,13 @@ jobs: needs: build-docker steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - name: Log in to Azure - production subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -426,7 +426,7 @@ jobs: - win-x64 steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 @@ -465,7 +465,7 @@ jobs: needs: build-docker steps: - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -498,7 +498,7 @@ jobs: needs: build-docker steps: - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -547,7 +547,7 @@ jobs: run: exit 1 - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 if: failure() with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -561,7 +561,7 @@ jobs: secrets: "devops-alerts-slack-webhook-url" - name: Notify Slack on failure - uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 + uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0 if: failure() env: SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} diff --git a/.github/workflows/cleanup-after-pr.yml b/.github/workflows/cleanup-after-pr.yml index f9b75e83d..1bed3542d 100644 --- a/.github/workflows/cleanup-after-pr.yml +++ b/.github/workflows/cleanup-after-pr.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Log in to Azure - production subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} diff --git a/.github/workflows/cleanup-rc-branch.yml b/.github/workflows/cleanup-rc-branch.yml index 9617cef9e..abd7c4bb4 100644 --- a/.github/workflows/cleanup-rc-branch.yml +++ b/.github/workflows/cleanup-rc-branch.yml @@ -24,7 +24,7 @@ jobs: secrets: "github-pat-bitwarden-devops-bot-repo-scope" - name: Checkout main - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: main token: ${{ steps.retrieve-bot-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} diff --git a/.github/workflows/code-references.yml b/.github/workflows/code-references.yml index ca584a1d3..e7bad76cc 100644 --- a/.github/workflows/code-references.yml +++ b/.github/workflows/code-references.yml @@ -16,11 +16,11 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Collect id: collect - uses: launchdarkly/find-code-references-in-pull-request@2e9333c88539377cfbe818c265ba8b9ebced3c91 # v1.1.0 + uses: launchdarkly/find-code-references-in-pull-request@d008aa4f321d8cd35314d9cb095388dcfde84439 # v2.0.0 with: project-key: default environment-key: dev diff --git a/.github/workflows/container-registry-purge.yml b/.github/workflows/container-registry-purge.yml index 550096d1a..1fc4c511b 100644 --- a/.github/workflows/container-registry-purge.yml +++ b/.github/workflows/container-registry-purge.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Log in to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -80,7 +80,7 @@ jobs: run: exit 1 - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 if: failure() with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -94,7 +94,7 @@ jobs: secrets: "devops-alerts-slack-webhook-url" - name: Notify Slack on failure - uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 + uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0 if: failure() env: SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} diff --git a/.github/workflows/protect-files.yml b/.github/workflows/protect-files.yml index dea02dd91..9e2e03d67 100644 --- a/.github/workflows/protect-files.yml +++ b/.github/workflows/protect-files.yml @@ -29,7 +29,7 @@ jobs: label: "DB-migrations-changed" steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e4c238755..35940bc53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: branch-name: ${{ steps.branch.outputs.branch-name }} steps: - name: Branch check - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} run: | if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then echo "===================================" @@ -37,13 +37,13 @@ jobs: fi - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check release version id: version uses: bitwarden/gh-actions/release-version-check@main with: - release-type: ${{ github.event.inputs.release_type }} + release-type: ${{ inputs.release_type }} project-type: dotnet file: Directory.Build.props @@ -53,125 +53,6 @@ jobs: BRANCH_NAME=$(basename ${{ github.ref }}) echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT - deploy: - name: Deploy - runs-on: ubuntu-22.04 - needs: setup - strategy: - fail-fast: false - matrix: - include: - - name: Admin - - name: Api - - name: Billing - - name: Events - - name: Identity - - name: Sso - steps: - - name: Setup - id: setup - run: | - NAME_LOWER=$(echo "${{ matrix.name }}" | awk '{print tolower($0)}') - echo "Matrix name: ${{ matrix.name }}" - echo "NAME_LOWER: $NAME_LOWER" - echo "name_lower=$NAME_LOWER" >> $GITHUB_OUTPUT - - - name: Create GitHub deployment for ${{ matrix.name }} - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5 - id: deployment - with: - token: "${{ secrets.GITHUB_TOKEN }}" - initial-status: "in_progress" - environment: "Production Cloud" - task: "deploy" - description: "Deploy from ${{ needs.setup.outputs.branch-name }} branch" - - - name: Download latest release ${{ matrix.name }} asset - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build.yml - workflow_conclusion: success - branch: ${{ needs.setup.outputs.branch-name }} - artifacts: ${{ matrix.name }}.zip - - - name: Dry run - Download latest release ${{ matrix.name }} asset - if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - workflow: build.yml - workflow_conclusion: success - branch: main - artifacts: ${{ matrix.name }}.zip - - - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - env: - VAULT_NAME: "bitwarden-ci" - run: | - webapp_name=$( - az keyvault secret show --vault-name $VAULT_NAME \ - --name appservices-${{ steps.setup.outputs.name_lower }}-webapp-name \ - --query value --output tsv - ) - publish_profile=$( - az keyvault secret show --vault-name $VAULT_NAME \ - --name appservices-${{ steps.setup.outputs.name_lower }}-webapp-publish-profile \ - --query value --output tsv - ) - echo "::add-mask::$webapp_name" - echo "webapp-name=$webapp_name" >> $GITHUB_OUTPUT - echo "::add-mask::$publish_profile" - echo "publish-profile=$publish_profile" >> $GITHUB_OUTPUT - - - name: Log in to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 - with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Deploy app - uses: azure/webapps-deploy@4bca689e4c7129e55923ea9c45401b22dc6aa96f # v2.2.11 - with: - app-name: ${{ steps.retrieve-secrets.outputs.webapp-name }} - publish-profile: ${{ steps.retrieve-secrets.outputs.publish-profile }} - package: ./${{ matrix.name }}.zip - slot-name: "staging" - - - name: Start staging slot - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - env: - SERVICE: ${{ matrix.name }} - WEBAPP_NAME: ${{ steps.retrieve-secrets.outputs.webapp-name }} - run: | - if [[ "$SERVICE" = "Api" ]] || [[ "$SERVICE" = "Identity" ]]; then - RESOURCE_GROUP=bitwardenappservices - else - RESOURCE_GROUP=bitwarden - fi - az webapp start -n $WEBAPP_NAME -g $RESOURCE_GROUP -s staging - - - name: Update ${{ matrix.name }} deployment status to success - if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "success" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - - name: Update ${{ matrix.name }} deployment status to failure - if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 - with: - token: "${{ secrets.GITHUB_TOKEN }}" - state: "failure" - deployment-id: ${{ steps.deployment.outputs.deployment_id }} - release-docker: name: Build Docker images runs-on: ubuntu-22.04 @@ -202,7 +83,7 @@ jobs: steps: - name: Print environment env: - RELEASE_OPTION: ${{ github.event.inputs.release_type }} + RELEASE_OPTION: ${{ inputs.release_type }} run: | whoami docker --version @@ -211,7 +92,7 @@ jobs: echo "Github Release Option: $RELEASE_OPTION" - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up project name id: setup @@ -223,7 +104,7 @@ jobs: ########## ACR PROD ########## - name: Log in to Azure - production subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -234,7 +115,7 @@ jobs: env: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then docker pull $_AZ_REGISTRY/$PROJECT_NAME:latest else docker pull $_AZ_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME @@ -244,7 +125,7 @@ jobs: env: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then docker tag $_AZ_REGISTRY/$PROJECT_NAME:latest $_AZ_REGISTRY/$PROJECT_NAME:dryrun else docker tag $_AZ_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME $_AZ_REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION @@ -255,7 +136,7 @@ jobs: env: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then + if [[ "${{ inputs.release_type }}" == "Dry Run" ]]; then docker push $_AZ_REGISTRY/$PROJECT_NAME:dryrun else docker push $_AZ_REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION @@ -268,12 +149,10 @@ jobs: release: name: Create GitHub release runs-on: ubuntu-22.04 - needs: - - setup - - deploy + needs: setup steps: - name: Download latest release Docker stubs - if: ${{ github.event.inputs.release_type != 'Dry Run' }} + if: ${{ inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build.yml @@ -286,7 +165,7 @@ jobs: swagger.json" - name: Dry Run - Download latest release Docker stubs - if: ${{ github.event.inputs.release_type == 'Dry Run' }} + if: ${{ inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build.yml @@ -299,8 +178,8 @@ jobs: swagger.json" - name: Create release - if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0 + if: ${{ inputs.release_type != 'Dry Run' }} + uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0 with: artifacts: "docker-stub-US.zip, docker-stub-US-sha256.txt, diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index df01a4646..17f9abc0d 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -26,12 +26,12 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with Checkmarx - uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23 + uses: checkmarx/ast-github-action@4c637b1cb6b6b63637c7b99578c9fceefebbb08d # 2.0.30 env: INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}" with: @@ -46,7 +46,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13 with: sarif_file: cx_result.sarif @@ -59,19 +59,33 @@ jobs: pull-requests: write steps: + - name: Set up JDK 17 + uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 + with: + java-version: 17 + distribution: "zulu" + - name: Check out repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} + - name: Set up .NET + uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1 + + - name: Install SonarCloud scanner + run: dotnet tool install dotnet-sonarscanner -g + - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: > - -Dsonar.organization=${{ github.repository_owner }} - -Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }} - -Dsonar.tests=test/ + run: | + dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" \ + /d:sonar.test.inclusions=test/,bitwarden_license/test/ \ + /d:sonar.exclusions=test/,bitwarden_license/test/ \ + /o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ + /d:sonar.host.url="https://sonarcloud.io" + dotnet build + dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.github/workflows/stop-staging-slots.yml b/.github/workflows/stop-staging-slots.yml deleted file mode 100644 index 0ffe94ecd..000000000 --- a/.github/workflows/stop-staging-slots.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: Stop staging slots - -on: - workflow_dispatch: - inputs: {} - -jobs: - stop-slots: - name: Stop slots - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - include: - - name: Api - - name: Admin - - name: Billing - - name: Events - - name: Sso - - name: Identity - steps: - - name: Setup - id: setup - run: | - NAME_LOWER=$(echo "${{ matrix.name }}" | awk '{print tolower($0)}') - echo "Matrix name: ${{ matrix.name }}" - echo "NAME_LOWER: $NAME_LOWER" - echo "name_lower=$NAME_LOWER" >> $GITHUB_OUTPUT - - - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Retrieve secrets - id: retrieve-secrets - env: - VAULT_NAME: "bitwarden-ci" - run: | - webapp_name=$( - az keyvault secret show --vault-name $VAULT_NAME \ - --name appservices-${{ steps.setup.outputs.name_lower }}-webapp-name \ - --query value --output tsv - ) - echo "::add-mask::$webapp_name" - echo "webapp-name=$webapp_name" >> $GITHUB_OUTPUT - - - name: Log in to Azure - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 - with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Stop staging slot - env: - SERVICE: ${{ matrix.name }} - WEBAPP_NAME: ${{ steps.retrieve-secrets.outputs.webapp-name }} - run: | - if [[ "$SERVICE" = "Api" ]] || [[ "$SERVICE" = "Identity" ]]; then - RESOURCE_GROUP=bitwardenappservices - else - RESOURCE_GROUP=bitwarden - fi - az webapp stop -n $WEBAPP_NAME -g $RESOURCE_GROUP -s staging diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml index b57cc8786..6a123c0d4 100644 --- a/.github/workflows/test-database.yml +++ b/.github/workflows/test-database.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 @@ -98,7 +98,7 @@ jobs: shell: pwsh - name: Report test results - uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0 + uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 if: always() with: name: Test Results @@ -117,7 +117,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ffb37d5c..1d35ed41d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages steps: - name: Check out repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 @@ -44,7 +44,7 @@ jobs: run: dotnet test ./bitwarden_license/test --configuration Debug --logger "trx;LogFileName=bw-test-results.trx" /p:CoverletOutputFormatter="cobertura" --collect:"XPlat Code Coverage" - name: Report test results - uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0 + uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 if: always() with: name: Test Results @@ -53,6 +53,6 @@ jobs: fail-on-error: true - name: Upload to codecov.io - uses: codecov/codecov-action@0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 # v4.0.2 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index d254cbdd0..6d6b01b20 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -39,9 +39,7 @@ jobs: fi - name: Check out branch - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - with: - ref: main + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check if RC branch exists if: ${{ inputs.cut_rc_branch == true }} @@ -54,7 +52,7 @@ jobs: fi - name: Log in to Azure - CI subscription - uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 + uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -68,7 +66,7 @@ jobs: github-pat-bitwarden-devops-bot-repo-scope" - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 + uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 with: gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} @@ -225,7 +223,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out branch - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: main diff --git a/.gitignore b/.gitignore index 76e15fe78..65157bf4a 100644 --- a/.gitignore +++ b/.gitignore @@ -205,12 +205,10 @@ mail_dist/ src/Core/Properties/launchSettings.json *.override.env **/*.DS_Store -src/Admin/wwwroot/lib -src/Admin/wwwroot/css +src/Admin/wwwroot/assets .vscode/* **/.vscode/* -bitwarden_license/src/Sso/wwwroot/lib -bitwarden_license/src/Sso/wwwroot/css +bitwarden_license/src/Sso/wwwroot/assets .github/test/build.secrets **/CoverageOutput/ .idea/* diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2b003ad9a..9de8c98ce 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,6 +3,7 @@ "tasks": [ { "label": "buildIdentityApi", + "hide": true, "dependsOrder": "sequence", "dependsOn": [ "buildIdentity", @@ -14,6 +15,7 @@ }, { "label": "buildIdentityApiAdmin", + "hide": true, "dependsOrder": "sequence", "dependsOn": [ "buildIdentity", @@ -26,6 +28,7 @@ }, { "label": "buildFullServer", + "hide": true, "dependsOrder": "sequence", "dependsOn": [ "buildAdmin", @@ -40,6 +43,7 @@ }, { "label": "buildSelfHostBit", + "hide": true, "dependsOrder": "sequence", "dependsOn": [ "buildAdmin", @@ -52,6 +56,7 @@ }, { "label": "buildSelfHostOss", + "hide": true, "dependsOrder": "sequence", "dependsOn": [ "buildAdmin", @@ -62,6 +67,7 @@ }, { "label": "buildIcons", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -74,6 +80,7 @@ }, { "label": "buildPortal", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -86,6 +93,7 @@ }, { "label": "buildSso", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -98,6 +106,7 @@ }, { "label": "buildEvents", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -110,6 +119,7 @@ }, { "label": "buildEventsProcessor", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -122,6 +132,7 @@ }, { "label": "buildAdmin", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -134,6 +145,7 @@ }, { "label": "buildIdentity", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -146,6 +158,7 @@ }, { "label": "buildAPI", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -162,6 +175,7 @@ }, { "label": "buildNotifications", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -178,6 +192,7 @@ }, { "label": "buildBilling", + "hide": true, "command": "dotnet", "type": "process", "args": [ @@ -192,20 +207,6 @@ "isDefault": true } }, - { - "label": "clean", - "type": "shell", - "command": "dotnet clean", - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": false - }, - "problemMatcher": "$msCompile" - }, { "label": "test", "type": "shell", @@ -225,13 +226,15 @@ "problemMatcher": "$msCompile" }, { - "label": "Setup Secrets", + "label": "Set Up Secrets", + "detail": "A task to run setup_secrets.ps1", "type": "shell", "command": "pwsh -WorkingDirectory ${workspaceFolder}/dev -Command '${workspaceFolder}/dev/setup_secrets.ps1 -clear:$${input:setupSecretsClear}'", "problemMatcher": [] }, { "label": "Install Dev Cert", + "detail": "A task to install the Bitwarden developer cert to run your local install as an admin.", "type": "shell", "command": "dotnet tool install -g dotnet-certificate-tool -g && certificate-tool add --file ${workspaceFolder}/dev/dev.pfx --password '${input:certPassword}'", "problemMatcher": [] diff --git a/Directory.Build.props b/Directory.Build.props index 019e3a11f..7c824623c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net8.0 - 2024.7.0 + 2024.7.4 Bit.$(MSBuildProjectName) enable diff --git a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs index 380d404b2..dd97aaca0 100644 --- a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs @@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; +using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -45,6 +46,7 @@ public class ProviderService : IProviderService private readonly IFeatureService _featureService; private readonly IDataProtectorTokenFactory _providerDeleteTokenDataFactory; private readonly IApplicationCacheService _applicationCacheService; + private readonly IProviderBillingService _providerBillingService; public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository, IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository, @@ -53,7 +55,7 @@ public class ProviderService : IProviderService IOrganizationRepository organizationRepository, GlobalSettings globalSettings, ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService, IDataProtectorTokenFactory providerDeleteTokenDataFactory, - IApplicationCacheService applicationCacheService) + IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService) { _providerRepository = providerRepository; _providerUserRepository = providerUserRepository; @@ -71,9 +73,10 @@ public class ProviderService : IProviderService _featureService = featureService; _providerDeleteTokenDataFactory = providerDeleteTokenDataFactory; _applicationCacheService = applicationCacheService; + _providerBillingService = providerBillingService; } - public async Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) + public async Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null) { var owner = await _userService.GetUserByIdAsync(ownerUserId); if (owner == null) @@ -98,8 +101,24 @@ public class ProviderService : IProviderService throw new BadRequestException("Invalid owner."); } - provider.Status = ProviderStatusType.Created; - await _providerRepository.UpsertAsync(provider); + if (!_featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)) + { + provider.Status = ProviderStatusType.Created; + await _providerRepository.UpsertAsync(provider); + } + else + { + if (taxInfo == null || string.IsNullOrEmpty(taxInfo.BillingAddressCountry) || string.IsNullOrEmpty(taxInfo.BillingAddressPostalCode)) + { + throw new BadRequestException("Both address and postal code are required to set up your provider."); + } + var customer = await _providerBillingService.SetupCustomer(provider, taxInfo); + provider.GatewayCustomerId = customer.Id; + var subscription = await _providerBillingService.SetupSubscription(provider); + provider.GatewaySubscriptionId = subscription.Id; + provider.Status = ProviderStatusType.Billable; + await _providerRepository.UpsertAsync(provider); + } providerUser.Key = key; await _providerUserRepository.ReplaceAsync(providerUser); @@ -544,9 +563,9 @@ public class ProviderService : IProviderService await _providerOrganizationRepository.CreateAsync(providerOrganization); await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Created); - // If using Flexible Collections, give the owner Can Manage access over the default collection + // Give the owner Can Manage access over the default collection // The orgUser is not available when the org is created so we have to do it here as part of the invite - var defaultOwnerAccess = organization.FlexibleCollections && defaultCollection != null + var defaultOwnerAccess = defaultCollection != null ? [ new CollectionAccessSelection @@ -566,10 +585,6 @@ public class ProviderService : IProviderService new OrganizationUserInvite { Emails = new[] { clientOwnerEmail }, - - // If using Flexible Collections, AccessAll is deprecated and set to false. - // If not using Flexible Collections, set AccessAll to true (previous behavior) - AccessAll = !organization.FlexibleCollections, Type = OrganizationUserType.Owner, Permissions = null, Collections = defaultOwnerAccess, diff --git a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs index 2ee7f606d..c38c8fbb4 100644 --- a/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs +++ b/bitwarden_license/src/Commercial.Core/Billing/ProviderBillingService.cs @@ -9,11 +9,11 @@ using Bit.Core.Billing.Constants; using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; -using Bit.Core.Billing.Models; using Bit.Core.Billing.Repositories; using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; @@ -22,7 +22,6 @@ using Bit.Core.Utilities; using CsvHelper; using Microsoft.Extensions.Logging; using Stripe; -using static Bit.Core.Billing.Utilities; namespace Bit.Commercial.Core.Billing; @@ -69,67 +68,6 @@ public class ProviderBillingService( await organizationRepository.ReplaceAsync(organization); } - public async Task CreateCustomer( - Provider provider, - TaxInfo taxInfo) - { - ArgumentNullException.ThrowIfNull(provider); - ArgumentNullException.ThrowIfNull(taxInfo); - - if (string.IsNullOrEmpty(taxInfo.BillingAddressCountry) || - string.IsNullOrEmpty(taxInfo.BillingAddressPostalCode)) - { - logger.LogError("Cannot create customer for provider ({ProviderID}) without both a country and postal code", provider.Id); - - throw ContactSupport(); - } - - var providerDisplayName = provider.DisplayName(); - - var customerCreateOptions = new CustomerCreateOptions - { - Address = new AddressOptions - { - Country = taxInfo.BillingAddressCountry, - PostalCode = taxInfo.BillingAddressPostalCode, - Line1 = taxInfo.BillingAddressLine1, - Line2 = taxInfo.BillingAddressLine2, - City = taxInfo.BillingAddressCity, - State = taxInfo.BillingAddressState - }, - Description = provider.DisplayBusinessName(), - Email = provider.BillingEmail, - InvoiceSettings = new CustomerInvoiceSettingsOptions - { - CustomFields = - [ - new CustomerInvoiceSettingsCustomFieldOptions - { - Name = provider.SubscriberType(), - Value = providerDisplayName.Length <= 30 - ? providerDisplayName - : providerDisplayName[..30] - } - ] - }, - Metadata = new Dictionary - { - { "region", globalSettings.BaseServiceUri.CloudRegion } - }, - TaxIdData = taxInfo.HasTaxId ? - [ - new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber } - ] - : null - }; - - var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions); - - provider.GatewayCustomerId = customer.Id; - - await providerRepository.ReplaceAsync(provider); - } - public async Task CreateCustomerForClientOrganization( Provider provider, Organization organization) @@ -204,15 +142,14 @@ public class ProviderBillingService( public async Task GenerateClientInvoiceReport( string invoiceId) { - if (string.IsNullOrEmpty(invoiceId)) - { - throw new ArgumentNullException(nameof(invoiceId)); - } + ArgumentException.ThrowIfNullOrEmpty(invoiceId); var invoiceItems = await providerInvoiceItemRepository.GetByInvoiceId(invoiceId); if (invoiceItems.Count == 0) { + logger.LogError("No provider invoice item records were found for invoice ({InvoiceID})", invoiceId); + return null; } @@ -245,14 +182,14 @@ public class ProviderBillingService( "Could not find provider ({ID}) when retrieving assigned seat total", providerId); - throw ContactSupport(); + throw new BillingException(); } if (provider.Type == ProviderType.Reseller) { logger.LogError("Assigned seats cannot be retrieved for reseller-type provider ({ID})", providerId); - throw ContactSupport("Consolidated billing does not support reseller-type providers"); + throw new BillingException(); } var providerOrganizations = await providerOrganizationRepository.GetManyDetailsByProviderAsync(providerId); @@ -264,39 +201,6 @@ public class ProviderBillingService( .Sum(providerOrganization => providerOrganization.Seats ?? 0); } - public async Task GetConsolidatedBillingSubscription( - Provider provider) - { - ArgumentNullException.ThrowIfNull(provider); - - var subscription = await subscriberService.GetSubscription(provider, new SubscriptionGetOptions - { - Expand = ["customer", "test_clock"] - }); - - if (subscription == null) - { - return null; - } - - var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); - - var configuredProviderPlans = providerPlans - .Where(providerPlan => providerPlan.IsConfigured()) - .Select(ConfiguredProviderPlanDTO.From) - .ToList(); - - var taxInformation = await subscriberService.GetTaxInformation(provider); - - var suspension = await GetSuspensionAsync(stripeAdapter, subscription); - - return new ConsolidatedBillingSubscriptionDTO( - configuredProviderPlans, - subscription, - taxInformation, - suspension); - } - public async Task ScaleSeats( Provider provider, PlanType planType, @@ -308,14 +212,14 @@ public class ProviderBillingService( { logger.LogError("Non-MSP provider ({ProviderID}) cannot scale their seats", provider.Id); - throw ContactSupport(); + throw new BillingException(); } if (!planType.SupportsConsolidatedBilling()) { logger.LogError("Cannot scale provider ({ProviderID}) seats for plan type {PlanType} as it does not support consolidated billing", provider.Id, planType.ToString()); - throw ContactSupport(); + throw new BillingException(); } var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); @@ -326,7 +230,7 @@ public class ProviderBillingService( { logger.LogError("Cannot scale provider ({ProviderID}) seats for plan type {PlanType} when their matching provider plan is not configured", provider.Id, planType); - throw ContactSupport(); + throw new BillingException(); } var seatMinimum = providerPlan.SeatMinimum.GetValueOrDefault(0); @@ -362,7 +266,7 @@ public class ProviderBillingService( { logger.LogError("Service user for provider ({ProviderID}) cannot scale a provider's seat count over the seat minimum", provider.Id); - throw ContactSupport(); + throw new BillingException(); } await update( @@ -393,7 +297,71 @@ public class ProviderBillingService( } } - public async Task StartSubscription( + public async Task SetupCustomer( + Provider provider, + TaxInfo taxInfo) + { + ArgumentNullException.ThrowIfNull(provider); + ArgumentNullException.ThrowIfNull(taxInfo); + + if (string.IsNullOrEmpty(taxInfo.BillingAddressCountry) || + string.IsNullOrEmpty(taxInfo.BillingAddressPostalCode)) + { + logger.LogError("Cannot create customer for provider ({ProviderID}) without both a country and postal code", provider.Id); + + throw new BillingException(); + } + + var providerDisplayName = provider.DisplayName(); + + var customerCreateOptions = new CustomerCreateOptions + { + Address = new AddressOptions + { + Country = taxInfo.BillingAddressCountry, + PostalCode = taxInfo.BillingAddressPostalCode, + Line1 = taxInfo.BillingAddressLine1, + Line2 = taxInfo.BillingAddressLine2, + City = taxInfo.BillingAddressCity, + State = taxInfo.BillingAddressState + }, + Description = provider.DisplayBusinessName(), + Email = provider.BillingEmail, + InvoiceSettings = new CustomerInvoiceSettingsOptions + { + CustomFields = + [ + new CustomerInvoiceSettingsCustomFieldOptions + { + Name = provider.SubscriberType(), + Value = providerDisplayName?.Length <= 30 + ? providerDisplayName + : providerDisplayName?[..30] + } + ] + }, + Metadata = new Dictionary + { + { "region", globalSettings.BaseServiceUri.CloudRegion } + }, + TaxIdData = taxInfo.HasTaxId ? + [ + new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber } + ] + : null + }; + + try + { + return await stripeAdapter.CustomerCreateAsync(customerCreateOptions); + } + catch (StripeException stripeException) when (stripeException.StripeError?.Code == StripeConstants.ErrorCodes.TaxIdInvalid) + { + throw new BadRequestException("Your tax ID wasn't recognized for your selected country. Please ensure your country and tax ID are valid."); + } + } + + public async Task SetupSubscription( Provider provider) { ArgumentNullException.ThrowIfNull(provider); @@ -406,7 +374,7 @@ public class ProviderBillingService( { logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured plans", provider.Id); - throw ContactSupport(); + throw new BillingException(); } var subscriptionItemOptionsList = new List(); @@ -418,7 +386,7 @@ public class ProviderBillingService( { logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured Teams plan", provider.Id); - throw ContactSupport(); + throw new BillingException(); } var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); @@ -436,7 +404,7 @@ public class ProviderBillingService( { logger.LogError("Cannot start subscription for provider ({ProviderID}) that has no configured Enterprise plan", provider.Id); - throw ContactSupport(); + throw new BillingException(); } var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); @@ -465,22 +433,140 @@ public class ProviderBillingService( ProrationBehavior = StripeConstants.ProrationBehavior.CreateProrations }; - var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); - - provider.GatewaySubscriptionId = subscription.Id; - - if (subscription.Status == StripeConstants.SubscriptionStatus.Incomplete) + try { - await providerRepository.ReplaceAsync(provider); + var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions); - logger.LogError("Started incomplete provider ({ProviderID}) subscription ({SubscriptionID})", provider.Id, subscription.Id); + if (subscription.Status == StripeConstants.SubscriptionStatus.Active) + { + return subscription; + } - throw ContactSupport(); + logger.LogError( + "Newly created provider ({ProviderID}) subscription ({SubscriptionID}) has inactive status: {Status}", + provider.Id, + subscription.Id, + subscription.Status); + + throw new BillingException(); + } + catch (StripeException stripeException) when (stripeException.StripeError?.Code == StripeConstants.ErrorCodes.CustomerTaxLocationInvalid) + { + throw new BadRequestException("Your location wasn't recognized. Please ensure your country and postal code are valid."); + } + } + + public async Task UpdateSeatMinimums( + Provider provider, + int enterpriseSeatMinimum, + int teamsSeatMinimum) + { + ArgumentNullException.ThrowIfNull(provider); + + if (enterpriseSeatMinimum < 0 || teamsSeatMinimum < 0) + { + throw new BadRequestException("Provider seat minimums must be at least 0."); } - provider.Status = ProviderStatusType.Billable; + var subscription = await stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId); - await providerRepository.ReplaceAsync(provider); + var subscriptionItemOptionsList = new List(); + + var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); + + var enterpriseProviderPlan = + providerPlans.Single(providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly); + + if (enterpriseProviderPlan.SeatMinimum != enterpriseSeatMinimum) + { + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager + .StripeProviderPortalSeatPlanId; + + var enterpriseSubscriptionItem = subscription.Items.First(item => item.Price.Id == enterprisePriceId); + + if (enterpriseProviderPlan.PurchasedSeats == 0) + { + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = enterpriseSubscriptionItem.Id, + Price = enterprisePriceId, + Quantity = enterpriseSeatMinimum + }); + } + else + { + var totalEnterpriseSeats = enterpriseProviderPlan.SeatMinimum + enterpriseProviderPlan.PurchasedSeats; + + if (enterpriseSeatMinimum <= totalEnterpriseSeats) + { + enterpriseProviderPlan.PurchasedSeats = totalEnterpriseSeats - enterpriseSeatMinimum; + } + else + { + enterpriseProviderPlan.PurchasedSeats = 0; + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = enterpriseSubscriptionItem.Id, + Price = enterprisePriceId, + Quantity = enterpriseSeatMinimum + }); + } + } + + enterpriseProviderPlan.SeatMinimum = enterpriseSeatMinimum; + + await providerPlanRepository.ReplaceAsync(enterpriseProviderPlan); + } + + var teamsProviderPlan = + providerPlans.Single(providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly); + + if (teamsProviderPlan.SeatMinimum != teamsSeatMinimum) + { + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager + .StripeProviderPortalSeatPlanId; + + var teamsSubscriptionItem = subscription.Items.First(item => item.Price.Id == teamsPriceId); + + if (teamsProviderPlan.PurchasedSeats == 0) + { + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = teamsSubscriptionItem.Id, + Price = teamsPriceId, + Quantity = teamsSeatMinimum + }); + } + else + { + var totalTeamsSeats = teamsProviderPlan.SeatMinimum + teamsProviderPlan.PurchasedSeats; + + if (teamsSeatMinimum <= totalTeamsSeats) + { + teamsProviderPlan.PurchasedSeats = totalTeamsSeats - teamsSeatMinimum; + } + else + { + teamsProviderPlan.PurchasedSeats = 0; + subscriptionItemOptionsList.Add(new SubscriptionItemOptions + { + Id = teamsSubscriptionItem.Id, + Price = teamsPriceId, + Quantity = teamsSeatMinimum + }); + } + } + + teamsProviderPlan.SeatMinimum = teamsSeatMinimum; + + await providerPlanRepository.ReplaceAsync(teamsProviderPlan); + } + + if (subscriptionItemOptionsList.Count > 0) + { + await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, + new SubscriptionUpdateOptions { Items = subscriptionItemOptionsList }); + } } private Func CurrySeatScalingUpdate( diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs new file mode 100644 index 000000000..49d6e1fcc --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandler.cs @@ -0,0 +1,63 @@ +#nullable enable +using Bit.Core.Context; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Microsoft.AspNetCore.Authorization; + +namespace Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets; + +public class + BulkSecretAuthorizationHandler : AuthorizationHandler> +{ + private readonly IAccessClientQuery _accessClientQuery; + private readonly ICurrentContext _currentContext; + private readonly ISecretRepository _secretRepository; + + public BulkSecretAuthorizationHandler(ICurrentContext currentContext, IAccessClientQuery accessClientQuery, + ISecretRepository secretRepository) + { + _currentContext = currentContext; + _accessClientQuery = accessClientQuery; + _secretRepository = secretRepository; + } + + + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, + BulkSecretOperationRequirement requirement, + IReadOnlyList resources) + { + // Ensure all secrets belong to the same organization. + var organizationId = resources[0].OrganizationId; + if (resources.Any(secret => secret.OrganizationId != organizationId) || + !_currentContext.AccessSecretsManager(organizationId)) + { + return; + } + + switch (requirement) + { + case not null when requirement == BulkSecretOperations.ReadAll: + await CanReadAllAsync(context, requirement, resources, organizationId); + break; + default: + throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement)); + } + } + + private async Task CanReadAllAsync(AuthorizationHandlerContext context, + BulkSecretOperationRequirement requirement, IReadOnlyList resources, Guid organizationId) + { + var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, organizationId); + + var secretsAccess = + await _secretRepository.AccessToSecretsAsync(resources.Select(s => s.Id), userId, accessClient); + + if (secretsAccess.Count == resources.Count && + secretsAccess.All(a => a.Value.Read)) + { + context.Succeed(requirement); + } + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Requests/RequestSMAccessCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Requests/RequestSMAccessCommand.cs new file mode 100644 index 000000000..440c0dfee --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Requests/RequestSMAccessCommand.cs @@ -0,0 +1,34 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.SecretsManager.Commands.Requests.Interfaces; +using Bit.Core.Services; + +namespace Bit.Commercial.Core.SecretsManager.Commands.Requests; + +public class RequestSMAccessCommand : IRequestSMAccessCommand +{ + private readonly IMailService _mailService; + + public RequestSMAccessCommand( + IMailService mailService) + { + _mailService = mailService; + } + + public async Task SendRequestAccessToSM(Organization organization, ICollection orgUsers, User user, string emailContent) + { + var emailList = orgUsers.Where(o => o.Type <= OrganizationUserType.Admin) + .Select(a => a.Email).Distinct().ToList(); + + if (!emailList.Any()) + { + throw new BadRequestException("The organization is in an invalid state. Please contact Customer Support."); + } + + var userRequestingAccess = user.Name ?? user.Email; + await _mailService.SendRequestSMAccessToAdminEmailAsync(emailList, organization.Name, userRequestingAccess, emailContent); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index 970d874f8..8d2010028 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -6,6 +6,7 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens; using Bit.Commercial.Core.SecretsManager.Commands.Porting; using Bit.Commercial.Core.SecretsManager.Commands.Projects; +using Bit.Commercial.Core.SecretsManager.Commands.Requests; using Bit.Commercial.Core.SecretsManager.Commands.Secrets; using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; using Bit.Commercial.Core.SecretsManager.Commands.Trash; @@ -18,6 +19,7 @@ using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces; using Bit.Core.SecretsManager.Commands.Porting.Interfaces; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; +using Bit.Core.SecretsManager.Commands.Requests.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; using Bit.Core.SecretsManager.Commands.Trash.Interfaces; @@ -43,6 +45,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -55,6 +58,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index 55360a724..99d34e8cf 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -169,6 +169,58 @@ public class ProjectRepository : Repository pa.Id, pa => (pa.Read, pa.Write)); } + public async Task GetProjectCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Project.Where(p => p.OrganizationId == organizationId && p.DeletedDate == null); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToProject(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + return await query.CountAsync(); + } + + public async Task GetProjectCountsByIdAsync(Guid projectId, Guid userId, AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Project.Where(p => p.Id == projectId && p.DeletedDate == null); + + var queryReadAccess = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToProject(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var queryWriteAccess = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var secretsQuery = queryReadAccess.Select(project => project.Secrets.Count(s => s.DeletedDate == null)); + + var projectCountsQuery = queryWriteAccess.Select(project => new ProjectCounts + { + People = project.UserAccessPolicies.Count + project.GroupAccessPolicies.Count, + ServiceAccounts = project.ServiceAccountAccessPolicies.Count + }); + + var secrets = await secretsQuery.FirstOrDefaultAsync(); + var projectCounts = await projectCountsQuery.FirstOrDefaultAsync() ?? new ProjectCounts { Secrets = 0, People = 0, ServiceAccounts = 0 }; + projectCounts.Secrets = secrets; + + return projectCounts; + } + private record ProjectAccess(Guid Id, bool Read, bool Write); private static IQueryable BuildProjectAccessQuery(IQueryable projectQuery, Guid userId, diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index ae9a5032c..8b23e4cfd 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -299,6 +299,22 @@ public class SecretRepository : Repository> AccessToSecretsAsync( + IEnumerable ids, + Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + + var secrets = dbContext.Secret + .Where(s => ids.Contains(s.Id)); + + var accessQuery = BuildSecretAccessQuery(secrets, userId, accessType); + + return await accessQuery.ToDictionaryAsync(sa => sa.Id, sa => (sa.Read, sa.Write)); + } + public async Task EmptyTrash(DateTime currentDate, uint deleteAfterThisNumberOfDays) { using var scope = ServiceScopeFactory.CreateScope(); @@ -309,6 +325,23 @@ public class SecretRepository : Repository GetSecretsCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Secret.Where(s => s.OrganizationId == organizationId && s.DeletedDate == null); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + return await query.CountAsync(); + } + private IQueryable SecretToPermissionDetails(IQueryable query, Guid userId, AccessClientType accessType) { var secrets = accessType switch diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs index ffeb939e2..20c457730 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs @@ -125,6 +125,48 @@ public class ServiceAccountRepository : Repository GetServiceAccountCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ServiceAccount.Where(sa => sa.OrganizationId == organizationId); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToServiceAccount(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + return await query.CountAsync(); + } + + public async Task GetServiceAccountCountsByIdAsync(Guid serviceAccountId, Guid userId, + AccessClientType accessType) + { + await using var scope = ServiceScopeFactory.CreateAsyncScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ServiceAccount.Where(sa => sa.Id == serviceAccountId); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToServiceAccount(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var serviceAccountCountsQuery = query.Select(serviceAccount => new ServiceAccountCounts + { + Projects = serviceAccount.ProjectAccessPolicies.Count, + People = serviceAccount.UserAccessPolicies.Count + serviceAccount.GroupAccessPolicies.Count, + AccessTokens = serviceAccount.ApiKeys.Count + }); + + var serviceAccountCounts = await serviceAccountCountsQuery.FirstOrDefaultAsync(); + return serviceAccountCounts ?? new ServiceAccountCounts { Projects = 0, People = 0, AccessTokens = 0 }; + } + public async Task ServiceAccountsAreInOrganizationAsync(List serviceAccountIds, Guid organizationId) { await using var scope = ServiceScopeFactory.CreateAsyncScope(); diff --git a/bitwarden_license/src/Scim/Models/ScimUserRequestModel.cs b/bitwarden_license/src/Scim/Models/ScimUserRequestModel.cs index e95e197db..990ac27ec 100644 --- a/bitwarden_license/src/Scim/Models/ScimUserRequestModel.cs +++ b/bitwarden_license/src/Scim/Models/ScimUserRequestModel.cs @@ -20,7 +20,6 @@ public class ScimUserRequestModel : BaseScimUserModel // Permissions cannot be set via SCIM so we use default values Type = OrganizationUserType.User, - AccessAll = false, Collections = new List(), Groups = new List() }; diff --git a/bitwarden_license/src/Sso/Views/Shared/_Layout.cshtml b/bitwarden_license/src/Sso/Views/Shared/_Layout.cshtml index ae7511942..fc57d7e9b 100644 --- a/bitwarden_license/src/Sso/Views/Shared/_Layout.cshtml +++ b/bitwarden_license/src/Sso/Views/Shared/_Layout.cshtml @@ -7,15 +7,7 @@ @ViewData["Title"] - SSO - - - - - - - - - + @RenderSection("Head", required: false) @@ -43,18 +35,7 @@ - - - - - - - - - - - - + @RenderSection("Scripts", required: false) diff --git a/bitwarden_license/src/Sso/gulpfile.js b/bitwarden_license/src/Sso/gulpfile.js deleted file mode 100644 index fd1f74346..000000000 --- a/bitwarden_license/src/Sso/gulpfile.js +++ /dev/null @@ -1,71 +0,0 @@ -/// - -const gulp = require('gulp'); -const merge = require('merge-stream'); -const sass = require('gulp-sass')(require("sass")); -const del = require('del'); - -const paths = {}; -paths.webroot = './wwwroot/'; -paths.npmDir = './node_modules/'; -paths.sassDir = './Sass/'; -paths.libDir = paths.webroot + 'lib/'; -paths.cssDir = paths.webroot + 'css/'; -paths.jsDir = paths.webroot + 'js/'; - -paths.sass = paths.sassDir + '**/*.scss'; -paths.minCss = paths.cssDir + '**/*.min.css'; -paths.js = paths.jsDir + '**/*.js'; -paths.minJs = paths.jsDir + '**/*.min.js'; -paths.libJs = paths.libDir + '**/*.js'; -paths.libMinJs = paths.libDir + '**/*.min.js'; - -function clean() { - return del([paths.minJs, paths.cssDir, paths.libDir]); -} - -function lib() { - const libs = [ - { - src: paths.npmDir + 'bootstrap/dist/js/*', - dest: paths.libDir + 'bootstrap/js' - }, - { - src: paths.npmDir + 'popper.js/dist/umd/*', - dest: paths.libDir + 'popper' - }, - { - src: paths.npmDir + 'font-awesome/css/*', - dest: paths.libDir + 'font-awesome/css' - }, - { - src: paths.npmDir + 'font-awesome/fonts/*', - dest: paths.libDir + 'font-awesome/fonts' - }, - { - src: paths.npmDir + 'jquery/dist/jquery.slim*', - dest: paths.libDir + 'jquery' - }, - ]; - - const tasks = libs.map((lib) => { - return gulp.src(lib.src).pipe(gulp.dest(lib.dest)); - }); - return merge(tasks); -} - -function runSass() { - return gulp.src(paths.sass) - .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) - .pipe(gulp.dest(paths.cssDir)); -} - -function sassWatch() { - gulp.watch(paths.sass, runSass); -} - -exports.build = gulp.series(clean, gulp.parallel([lib, runSass])); -exports['sass:watch'] = sassWatch; -exports.sass = runSass; -exports.lib = lib; -exports.clean = clean; diff --git a/bitwarden_license/src/Sso/package-lock.json b/bitwarden_license/src/Sso/package-lock.json index cc291f4ba..cd1f00d29 100644 --- a/bitwarden_license/src/Sso/package-lock.json +++ b/bitwarden_license/src/Sso/package-lock.json @@ -8,586 +8,427 @@ "name": "bitwarden-sso", "version": "0.0.0", "license": "-", - "devDependencies": { + "dependencies": { "bootstrap": "4.6.2", - "del": "6.1.1", "font-awesome": "4.7.0", - "gulp": "4.0.2", - "gulp-sass": "5.1.0", "jquery": "3.7.1", - "merge-stream": "2.0.0", - "popper.js": "1.16.1", - "sass": "1.75.0" + "popper.js": "1.16.1" + }, + "devDependencies": { + "css-loader": "7.1.2", + "expose-loader": "5.0.0", + "mini-css-extract-plugin": "2.9.0", + "sass": "1.75.0", + "sass-loader": "16.0.0", + "webpack": "5.93.0", + "webpack-cli": "5.1.4" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">= 8" + "node": ">=6.0.0" } }, - "node_modules/@nodelib/fs.stat": { + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", + "dev": true, + "dependencies": { + "undici-types": "~6.11.1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">= 8" + "node": ">=0.4.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-wrap": "^0.1.0" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-wrap": "0.1.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.8.2" } }, "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "ISC", "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/anymatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bootstrap": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", - "dev": true, "funding": [ { "type": "github", @@ -598,3630 +439,86 @@ "url": "https://opencollective.com/bootstrap" } ], - "license": "MIT", "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "node_modules/caniuse-lite": { + "version": "1.0.30001644", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz", + "integrity": "sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==", "dev": true, - "license": "MIT", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "dev": true, - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/findup-sync/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/findup-sync/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "dev": true, - "license": "(OFL-1.1 AND MIT)", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true, - "license": "ISC" - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-stream/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-sass": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.1.0.tgz", - "integrity": "sha512-7VT0uaF+VZCmkNBglfe1b34bxn/AfcssquLKVDYnCDJ3xNBaW7cUuI3p3BQmoKcoKFrs9jdzUxyb+u+NGfL4OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "picocolors": "^1.0.0", - "plugin-error": "^1.0.1", - "replace-ext": "^2.0.0", - "strip-ansi": "^6.0.1", - "vinyl-sourcemaps-apply": "^0.2.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", - "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "license": "MIT", - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/matchdep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/nan": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", - "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/plugin-error/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/readdirp/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/readdirp/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true, - "license": "ISC" - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true, - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true, - "license": "MIT" - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass/node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sass/node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sass/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4241,12 +538,248 @@ "fsevents": "~2.3.2" } }, - "node_modules/sass/node_modules/fill-range": { + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.3.tgz", + "integrity": "sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expose-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz", + "integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4254,13 +787,42 @@ "node": ">=8" } }, - "node_modules/sass/node_modules/fsevents": { + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -4269,12 +831,111 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/sass/node_modules/is-binary-path": { + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4282,112 +943,56 @@ "node": ">=8" } }, - "node_modules/sass/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/sass/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/set-value/node_modules/is-plain-object": { + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -4395,110 +1000,618 @@ "node": ">=0.10.0" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { "node": ">=8" } }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "MIT", "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "mime-db": "1.52.0" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "p-try": "^2.0.0" }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/snapdragon-util": { + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sass": { + "version": "1.75.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", + "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, - "license": "MIT", "dependencies": { - "kind-of": "^3.2.0" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4508,243 +1621,33 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "MIT", "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT" - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "node": ">=10" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -4752,7 +1655,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4760,617 +1662,385 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, - "license": "MIT", - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "node_modules/terser": { + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", "dev": true, - "license": "MIT", "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, - "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, - "license": "MIT", "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.0" } }, - "node_modules/to-regex/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/undici-types": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "bin": { + "update-browserslist-db": "cli.js" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/to-regex/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true, - "license": "MIT" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "punycode": "^2.1.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, - "license": "MIT", "dependencies": { - "homedir-polyfill": "^1.0.1" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/webpack": { + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, - "license": "MIT", "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">= 0.10" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, - "license": "MIT", "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10.0.0" } }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { - "remove-trailing-separator": "^1.0.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "source-map": "^0.5.1" - } - }, - "node_modules/vinyl/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "node-which": "bin/node-which" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true } } } diff --git a/bitwarden_license/src/Sso/package.json b/bitwarden_license/src/Sso/package.json index 4106c031c..638a26b98 100644 --- a/bitwarden_license/src/Sso/package.json +++ b/bitwarden_license/src/Sso/package.json @@ -5,17 +5,21 @@ "repository": "https://github.com/bitwarden/enterprise", "license": "-", "scripts": { - "build": "gulp build" + "build": "webpack" + }, + "dependencies": { + "bootstrap": "4.6.2", + "font-awesome": "4.7.0", + "jquery": "3.7.1", + "popper.js": "1.16.1" }, "devDependencies": { - "bootstrap": "4.6.2", - "del": "6.1.1", - "font-awesome": "4.7.0", - "gulp": "4.0.2", - "gulp-sass": "5.1.0", - "jquery": "3.7.1", - "merge-stream": "2.0.0", - "popper.js": "1.16.1", - "sass": "1.75.0" + "css-loader": "7.1.2", + "expose-loader": "5.0.0", + "mini-css-extract-plugin": "2.9.0", + "sass": "1.75.0", + "sass-loader": "16.0.0", + "webpack": "5.93.0", + "webpack-cli": "5.1.4" } } diff --git a/bitwarden_license/src/Sso/webpack.config.js b/bitwarden_license/src/Sso/webpack.config.js new file mode 100644 index 000000000..37fb61832 --- /dev/null +++ b/bitwarden_license/src/Sso/webpack.config.js @@ -0,0 +1,57 @@ +const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + +const paths = { + assets: "./wwwroot/assets/", + sassDir: "./Sass/", +}; + +/** @type {import("webpack").Configuration} */ +module.exports = { + mode: "production", + devtool: "source-map", + entry: { + site: [ + path.resolve(__dirname, paths.sassDir, "site.scss"), + + "popper.js", + "bootstrap", + "jquery", + "font-awesome/css/font-awesome.css", + ], + }, + output: { + clean: true, + path: path.resolve(__dirname, paths.assets), + }, + module: { + rules: [ + { + test: /\.(sa|sc|c)ss$/, + use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], + }, + { + test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + exclude: /loading(|-white).svg/, + generator: { + filename: "fonts/[name].[contenthash][ext]", + }, + type: "asset/resource", + }, + + // Expose jquery globally so they can be used directly in asp.net + { + test: require.resolve("jquery"), + loader: "expose-loader", + options: { + exposes: ["$", "jQuery"], + }, + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + }), + ], +}; diff --git a/bitwarden_license/src/Sso/wwwroot/js/site.js b/bitwarden_license/src/Sso/wwwroot/js/site.js deleted file mode 100644 index ac49c1864..000000000 --- a/bitwarden_license/src/Sso/wwwroot/js/site.js +++ /dev/null @@ -1,4 +0,0 @@ -// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification -// for details on configuring this project to bundle and minify static web assets. - -// Write your JavaScript code. diff --git a/bitwarden_license/test/Bitwarden.License.Tests.proj b/bitwarden_license/test/Bitwarden.License.Tests.proj new file mode 100644 index 000000000..94da2116c --- /dev/null +++ b/bitwarden_license/test/Bitwarden.License.Tests.proj @@ -0,0 +1,5 @@ + + + + + diff --git a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs index 9a1c6c78d..4beda0060 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs @@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Models.Business.Provider; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -81,6 +82,51 @@ public class ProviderServiceTests .ReplaceAsync(Arg.Is(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id && pu.Key == key)); } + [Theory, BitAutoData] + public async Task CompleteSetupAsync_ConsolidatedBilling_Success(User user, Provider provider, string key, TaxInfo taxInfo, + [ProviderUser(ProviderUserStatusType.Confirmed, ProviderUserType.ProviderAdmin)] ProviderUser providerUser, + SutProvider sutProvider) + { + providerUser.ProviderId = provider.Id; + providerUser.UserId = user.Id; + var userService = sutProvider.GetDependency(); + userService.GetUserByIdAsync(user.Id).Returns(user); + + var providerUserRepository = sutProvider.GetDependency(); + providerUserRepository.GetByProviderUserAsync(provider.Id, user.Id).Returns(providerUser); + + var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName"); + var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector"); + sutProvider.GetDependency().CreateProtector("ProviderServiceDataProtector") + .Returns(protector); + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) + .Returns(true); + + var providerBillingService = sutProvider.GetDependency(); + + var customer = new Customer { Id = "customer_id" }; + providerBillingService.SetupCustomer(provider, taxInfo).Returns(customer); + + var subscription = new Subscription { Id = "subscription_id" }; + providerBillingService.SetupSubscription(provider).Returns(subscription); + + sutProvider.Create(); + + var token = protector.Protect($"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}"); + + await sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, key, taxInfo); + + await sutProvider.GetDependency().Received().UpsertAsync(Arg.Is( + p => + p.GatewayCustomerId == customer.Id && + p.GatewaySubscriptionId == subscription.Id && + p.Status == ProviderStatusType.Billable)); + + await sutProvider.GetDependency().Received() + .ReplaceAsync(Arg.Is(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id && pu.Key == key)); + } + [Theory, BitAutoData] public async Task UpdateAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider sutProvider) { @@ -613,7 +659,7 @@ public class ProviderServiceTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogProviderOrganizationEventsAsync(default); } - [Theory, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize, BitAutoData] public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, User user, SutProvider sutProvider) { @@ -637,12 +683,11 @@ public class ProviderServiceTests t.First().Item1.Emails.Count() == 1 && t.First().Item1.Emails.First() == clientOwnerEmail && t.First().Item1.Type == OrganizationUserType.Owner && - t.First().Item1.AccessAll && - !t.First().Item1.Collections.Any() && + t.First().Item1.Collections.Count() == 1 && t.First().Item2 == null)); } - [Theory, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize, BitAutoData] public async Task CreateOrganizationAsync_ConsolidatedBillingEnabled_InvalidPlanType_ThrowsBadRequestException( Provider provider, OrganizationSignup organizationSignup, @@ -671,7 +716,7 @@ public class ProviderServiceTests await providerOrganizationRepository.DidNotReceiveWithAnyArgs().CreateAsync(default); } - [Theory, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize, BitAutoData] public async Task CreateOrganizationAsync_ConsolidatedBillingEnabled_InvokeSignupClientAsync( Provider provider, OrganizationSignup organizationSignup, @@ -717,13 +762,12 @@ public class ProviderServiceTests t.First().Item1.Emails.Count() == 1 && t.First().Item1.Emails.First() == clientOwnerEmail && t.First().Item1.Type == OrganizationUserType.Owner && - t.First().Item1.AccessAll && - !t.First().Item1.Collections.Any() && + t.First().Item1.Collections.Count() == 1 && t.First().Item2 == null)); } - [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] - public async Task CreateOrganizationAsync_WithFlexibleCollections_SetsAccessAllToFalse + [Theory, OrganizationCustomize, BitAutoData] + public async Task CreateOrganizationAsync_SetsAccessAllToFalse (Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, User user, SutProvider sutProvider, Collection defaultCollection) { @@ -747,7 +791,6 @@ public class ProviderServiceTests t.First().Item1.Emails.Count() == 1 && t.First().Item1.Emails.First() == clientOwnerEmail && t.First().Item1.Type == OrganizationUserType.Owner && - t.First().Item1.AccessAll == false && t.First().Item1.Collections.Single().Id == defaultCollection.Id && !t.First().Item1.Collections.Single().HidePasswords && !t.First().Item1.Collections.Single().ReadOnly && diff --git a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs index a56a6f5ab..45a6fb983 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Billing/ProviderBillingServiceTests.cs @@ -11,12 +11,12 @@ using Bit.Core.Billing; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models; using Bit.Core.Billing.Repositories; using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; @@ -87,7 +87,7 @@ public class ProviderBillingServiceTests { organization.PlanType = PlanType.FamiliesAnnually; - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.AssignSeatsToClientOrganization(provider, organization, seats)); } @@ -105,7 +105,7 @@ public class ProviderBillingServiceTests new() { Id = Guid.NewGuid(), PlanType = PlanType.TeamsMonthly, ProviderId = provider.Id } }); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.AssignSeatsToClientOrganization(provider, organization, seats)); } @@ -247,7 +247,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().ProviderProviderAdmin(provider.Id).Returns(false); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.AssignSeatsToClientOrganization(provider, organization, seats)); } @@ -493,105 +493,6 @@ public class ProviderBillingServiceTests #endregion - #region CreateCustomer - - [Theory, BitAutoData] - public async Task CreateCustomer_NullProvider_ThrowsArgumentNullException( - SutProvider sutProvider, - TaxInfo taxInfo) => - await Assert.ThrowsAsync(() => sutProvider.Sut.CreateCustomer(null, taxInfo)); - - [Theory, BitAutoData] - public async Task CreateCustomer_NullTaxInfo_ThrowsArgumentNullException( - SutProvider sutProvider, - Provider provider) => - await Assert.ThrowsAsync(() => sutProvider.Sut.CreateCustomer(provider, null)); - - [Theory, BitAutoData] - public async Task CreateCustomer_MissingCountry_ContactSupport( - SutProvider sutProvider, - Provider provider, - TaxInfo taxInfo) - { - taxInfo.BillingAddressCountry = null; - - await ThrowsContactSupportAsync(() => sutProvider.Sut.CreateCustomer(provider, taxInfo)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .CustomerGetAsync(Arg.Any(), Arg.Any()); - } - - [Theory, BitAutoData] - public async Task CreateCustomer_MissingPostalCode_ContactSupport( - SutProvider sutProvider, - Provider provider, - TaxInfo taxInfo) - { - taxInfo.BillingAddressCountry = null; - - await ThrowsContactSupportAsync(() => sutProvider.Sut.CreateCustomer(provider, taxInfo)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .CustomerGetAsync(Arg.Any(), Arg.Any()); - } - - [Theory, BitAutoData] - public async Task CreateCustomer_Success( - SutProvider sutProvider, - Provider provider, - TaxInfo taxInfo) - { - provider.Name = "MSP"; - - taxInfo.BillingAddressCountry = "AD"; - - var stripeAdapter = sutProvider.GetDependency(); - - stripeAdapter.CustomerCreateAsync(Arg.Is(o => - o.Address.Country == taxInfo.BillingAddressCountry && - o.Address.PostalCode == taxInfo.BillingAddressPostalCode && - o.Address.Line1 == taxInfo.BillingAddressLine1 && - o.Address.Line2 == taxInfo.BillingAddressLine2 && - o.Address.City == taxInfo.BillingAddressCity && - o.Address.State == taxInfo.BillingAddressState && - o.Description == WebUtility.HtmlDecode(provider.BusinessName) && - o.Email == provider.BillingEmail && - o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" && - o.InvoiceSettings.CustomFields.FirstOrDefault().Value == "MSP" && - o.Metadata["region"] == "" && - o.TaxIdData.FirstOrDefault().Type == taxInfo.TaxIdType && - o.TaxIdData.FirstOrDefault().Value == taxInfo.TaxIdNumber)) - .Returns(new Customer - { - Id = "customer_id", - Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported } - }); - - await sutProvider.Sut.CreateCustomer(provider, taxInfo); - - await stripeAdapter.Received(1).CustomerCreateAsync(Arg.Is(o => - o.Address.Country == taxInfo.BillingAddressCountry && - o.Address.PostalCode == taxInfo.BillingAddressPostalCode && - o.Address.Line1 == taxInfo.BillingAddressLine1 && - o.Address.Line2 == taxInfo.BillingAddressLine2 && - o.Address.City == taxInfo.BillingAddressCity && - o.Address.State == taxInfo.BillingAddressState && - o.Description == WebUtility.HtmlDecode(provider.BusinessName) && - o.Email == provider.BillingEmail && - o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" && - o.InvoiceSettings.CustomFields.FirstOrDefault().Value == "MSP" && - o.Metadata["region"] == "" && - o.TaxIdData.FirstOrDefault().Type == taxInfo.TaxIdType && - o.TaxIdData.FirstOrDefault().Value == taxInfo.TaxIdNumber)); - - await sutProvider.GetDependency() - .ReplaceAsync(Arg.Is(p => p.GatewayCustomerId == "customer_id")); - } - - #endregion - #region CreateCustomerForClientOrganization [Theory, BitAutoData] @@ -777,7 +678,7 @@ public class ProviderBillingServiceTests public async Task GetAssignedSeatTotalForPlanOrThrow_NullProvider_ContactSupport( Guid providerId, SutProvider sutProvider) - => await ThrowsContactSupportAsync(() => + => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.GetAssignedSeatTotalForPlanOrThrow(providerId, PlanType.TeamsMonthly)); [Theory, BitAutoData] @@ -790,9 +691,8 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetByIdAsync(providerId).Returns(provider); - await ThrowsContactSupportAsync( - () => sutProvider.Sut.GetAssignedSeatTotalForPlanOrThrow(providerId, PlanType.TeamsMonthly), - internalMessage: "Consolidated billing does not support reseller-type providers"); + await ThrowsBillingExceptionAsync( + () => sutProvider.Sut.GetAssignedSeatTotalForPlanOrThrow(providerId, PlanType.TeamsMonthly)); } [Theory, BitAutoData] @@ -836,197 +736,100 @@ public class ProviderBillingServiceTests #endregion - #region GetConsolidatedBillingSubscription + #region SetupCustomer [Theory, BitAutoData] - public async Task GetConsolidatedBillingSubscription_NullProvider_ThrowsArgumentNullException( - SutProvider sutProvider) => - await Assert.ThrowsAsync(() => sutProvider.Sut.GetConsolidatedBillingSubscription(null)); - - [Theory, BitAutoData] - public async Task GetConsolidatedBillingSubscription_NullSubscription_ReturnsNull( + public async Task SetupCustomer_NullProvider_ThrowsArgumentNullException( SutProvider sutProvider, - Provider provider) + TaxInfo taxInfo) => + await Assert.ThrowsAsync(() => sutProvider.Sut.SetupCustomer(null, taxInfo)); + + [Theory, BitAutoData] + public async Task SetupCustomer_NullTaxInfo_ThrowsArgumentNullException( + SutProvider sutProvider, + Provider provider) => + await Assert.ThrowsAsync(() => sutProvider.Sut.SetupCustomer(provider, null)); + + [Theory, BitAutoData] + public async Task SetupCustomer_MissingCountry_ContactSupport( + SutProvider sutProvider, + Provider provider, + TaxInfo taxInfo) { - var consolidatedBillingSubscription = await sutProvider.Sut.GetConsolidatedBillingSubscription(provider); + taxInfo.BillingAddressCountry = null; - Assert.Null(consolidatedBillingSubscription); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupCustomer(provider, taxInfo)); - await sutProvider.GetDependency().Received(1).GetSubscription( - provider, - Arg.Is( - options => options.Expand.Count == 2 && options.Expand.First() == "customer" && options.Expand.Last() == "test_clock")); + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CustomerGetAsync(Arg.Any(), Arg.Any()); } [Theory, BitAutoData] - public async Task GetConsolidatedBillingSubscription_Active_NoSuspension_Success( + public async Task SetupCustomer_MissingPostalCode_ContactSupport( SutProvider sutProvider, - Provider provider) + Provider provider, + TaxInfo taxInfo) { - var subscriberService = sutProvider.GetDependency(); + taxInfo.BillingAddressCountry = null; - var subscription = new Subscription - { - Status = "active" - }; + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupCustomer(provider, taxInfo)); - subscriberService.GetSubscription(provider, Arg.Is( - options => options.Expand.Count == 2 && options.Expand.First() == "customer" && options.Expand.Last() == "test_clock")).Returns(subscription); - - var providerPlanRepository = sutProvider.GetDependency(); - - var enterprisePlan = new ProviderPlan - { - Id = Guid.NewGuid(), - ProviderId = provider.Id, - PlanType = PlanType.EnterpriseMonthly, - SeatMinimum = 100, - PurchasedSeats = 0, - AllocatedSeats = 0 - }; - - var teamsPlan = new ProviderPlan - { - Id = Guid.NewGuid(), - ProviderId = provider.Id, - PlanType = PlanType.TeamsMonthly, - SeatMinimum = 50, - PurchasedSeats = 10, - AllocatedSeats = 60 - }; - - var providerPlans = new List { enterprisePlan, teamsPlan, }; - - providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); - - var taxInformation = - new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY"); - - subscriberService.GetTaxInformation(provider).Returns(taxInformation); - - var (gotProviderPlans, gotSubscription, gotTaxInformation, gotSuspension) = await sutProvider.Sut.GetConsolidatedBillingSubscription(provider); - - Assert.Equal(2, gotProviderPlans.Count); - - var configuredEnterprisePlan = - gotProviderPlans.FirstOrDefault(configuredPlan => - configuredPlan.PlanType == PlanType.EnterpriseMonthly); - - var configuredTeamsPlan = - gotProviderPlans.FirstOrDefault(configuredPlan => - configuredPlan.PlanType == PlanType.TeamsMonthly); - - Compare(enterprisePlan, configuredEnterprisePlan); - - Compare(teamsPlan, configuredTeamsPlan); - - Assert.Equivalent(subscription, gotSubscription); - - Assert.Equivalent(taxInformation, gotTaxInformation); - - Assert.Null(gotSuspension); - - return; - - void Compare(ProviderPlan providerPlan, ConfiguredProviderPlanDTO configuredProviderPlan) - { - Assert.NotNull(configuredProviderPlan); - Assert.Equal(providerPlan.Id, configuredProviderPlan.Id); - Assert.Equal(providerPlan.ProviderId, configuredProviderPlan.ProviderId); - Assert.Equal(providerPlan.SeatMinimum!.Value, configuredProviderPlan.SeatMinimum); - Assert.Equal(providerPlan.PurchasedSeats!.Value, configuredProviderPlan.PurchasedSeats); - Assert.Equal(providerPlan.AllocatedSeats!.Value, configuredProviderPlan.AssignedSeats); - } + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .CustomerGetAsync(Arg.Any(), Arg.Any()); } [Theory, BitAutoData] - public async Task GetConsolidatedBillingSubscription_PastDue_HasSuspension_Success( + public async Task SetupCustomer_Success( SutProvider sutProvider, - Provider provider) + Provider provider, + TaxInfo taxInfo) { - var subscriberService = sutProvider.GetDependency(); + provider.Name = "MSP"; - var subscription = new Subscription - { - Id = "subscription_id", - Status = "past_due", - CollectionMethod = "send_invoice" - }; - - subscriberService.GetSubscription(provider, Arg.Is( - options => options.Expand.Count == 2 && options.Expand.First() == "customer" && options.Expand.Last() == "test_clock")).Returns(subscription); - - var providerPlanRepository = sutProvider.GetDependency(); - - var enterprisePlan = new ProviderPlan - { - Id = Guid.NewGuid(), - ProviderId = provider.Id, - PlanType = PlanType.EnterpriseMonthly, - SeatMinimum = 100, - PurchasedSeats = 0, - AllocatedSeats = 0 - }; - - var teamsPlan = new ProviderPlan - { - Id = Guid.NewGuid(), - ProviderId = provider.Id, - PlanType = PlanType.TeamsMonthly, - SeatMinimum = 50, - PurchasedSeats = 10, - AllocatedSeats = 60 - }; - - var providerPlans = new List { enterprisePlan, teamsPlan, }; - - providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); - - var taxInformation = - new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY"); - - subscriberService.GetTaxInformation(provider).Returns(taxInformation); + taxInfo.BillingAddressCountry = "AD"; var stripeAdapter = sutProvider.GetDependency(); - var openInvoice = new Invoice + var expected = new Customer { - Id = "invoice_id", - Status = "open", - DueDate = new DateTime(2024, 6, 1), - Created = new DateTime(2024, 5, 1), - PeriodEnd = new DateTime(2024, 6, 1) + Id = "customer_id", + Tax = new CustomerTax { AutomaticTax = StripeConstants.AutomaticTaxStatus.Supported } }; - stripeAdapter.InvoiceSearchAsync(Arg.Is(options => - options.Query == $"subscription:'{subscription.Id}' status:'open'")) - .Returns([openInvoice]); + stripeAdapter.CustomerCreateAsync(Arg.Is(o => + o.Address.Country == taxInfo.BillingAddressCountry && + o.Address.PostalCode == taxInfo.BillingAddressPostalCode && + o.Address.Line1 == taxInfo.BillingAddressLine1 && + o.Address.Line2 == taxInfo.BillingAddressLine2 && + o.Address.City == taxInfo.BillingAddressCity && + o.Address.State == taxInfo.BillingAddressState && + o.Description == WebUtility.HtmlDecode(provider.BusinessName) && + o.Email == provider.BillingEmail && + o.InvoiceSettings.CustomFields.FirstOrDefault().Name == "Provider" && + o.InvoiceSettings.CustomFields.FirstOrDefault().Value == "MSP" && + o.Metadata["region"] == "" && + o.TaxIdData.FirstOrDefault().Type == taxInfo.TaxIdType && + o.TaxIdData.FirstOrDefault().Value == taxInfo.TaxIdNumber)) + .Returns(expected); - var (gotProviderPlans, gotSubscription, gotTaxInformation, gotSuspension) = await sutProvider.Sut.GetConsolidatedBillingSubscription(provider); + var actual = await sutProvider.Sut.SetupCustomer(provider, taxInfo); - Assert.Equal(2, gotProviderPlans.Count); - - Assert.Equivalent(subscription, gotSubscription); - - Assert.Equivalent(taxInformation, gotTaxInformation); - - Assert.NotNull(gotSuspension); - Assert.Equal(openInvoice.DueDate.Value.AddDays(30), gotSuspension.SuspensionDate); - Assert.Equal(openInvoice.PeriodEnd, gotSuspension.UnpaidPeriodEndDate); - Assert.Equal(30, gotSuspension.GracePeriod); + Assert.Equivalent(expected, actual); } #endregion - #region StartSubscription + #region SetupSubscription [Theory, BitAutoData] - public async Task StartSubscription_NullProvider_ThrowsArgumentNullException( + public async Task SetupSubscription_NullProvider_ThrowsArgumentNullException( SutProvider sutProvider) => - await Assert.ThrowsAsync(() => sutProvider.Sut.StartSubscription(null)); + await Assert.ThrowsAsync(() => sutProvider.Sut.SetupSubscription(null)); [Theory, BitAutoData] - public async Task StartSubscription_NoProviderPlans_ContactSupport( + public async Task SetupSubscription_NoProviderPlans_ContactSupport( SutProvider sutProvider, Provider provider) { @@ -1041,7 +844,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetByProviderId(provider.Id) .Returns(new List()); - await ThrowsContactSupportAsync(() => sutProvider.Sut.StartSubscription(provider)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() @@ -1049,7 +852,7 @@ public class ProviderBillingServiceTests } [Theory, BitAutoData] - public async Task StartSubscription_NoProviderTeamsPlan_ContactSupport( + public async Task SetupSubscription_NoProviderTeamsPlan_ContactSupport( SutProvider sutProvider, Provider provider) { @@ -1066,7 +869,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetByProviderId(provider.Id) .Returns(providerPlans); - await ThrowsContactSupportAsync(() => sutProvider.Sut.StartSubscription(provider)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() @@ -1074,7 +877,7 @@ public class ProviderBillingServiceTests } [Theory, BitAutoData] - public async Task StartSubscription_NoProviderEnterprisePlan_ContactSupport( + public async Task SetupSubscription_NoProviderEnterprisePlan_ContactSupport( SutProvider sutProvider, Provider provider) { @@ -1091,7 +894,7 @@ public class ProviderBillingServiceTests sutProvider.GetDependency().GetByProviderId(provider.Id) .Returns(providerPlans); - await ThrowsContactSupportAsync(() => sutProvider.Sut.StartSubscription(provider)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() @@ -1099,7 +902,7 @@ public class ProviderBillingServiceTests } [Theory, BitAutoData] - public async Task StartSubscription_SubscriptionIncomplete_ThrowsBillingException( + public async Task SetupSubscription_SubscriptionIncomplete_ThrowsBillingException( SutProvider sutProvider, Provider provider) { @@ -1140,14 +943,11 @@ public class ProviderBillingServiceTests .Returns( new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Incomplete }); - await ThrowsContactSupportAsync(() => sutProvider.Sut.StartSubscription(provider)); - - await sutProvider.GetDependency().Received(1) - .ReplaceAsync(Arg.Is(p => p.GatewaySubscriptionId == "subscription_id")); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.SetupSubscription(provider)); } [Theory, BitAutoData] - public async Task StartSubscription_Succeeds( + public async Task SetupSubscription_Succeeds( SutProvider sutProvider, Provider provider) { @@ -1187,6 +987,8 @@ public class ProviderBillingServiceTests var teamsPlan = StaticStore.GetPlan(PlanType.TeamsMonthly); var enterprisePlan = StaticStore.GetPlan(PlanType.EnterpriseMonthly); + var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active }; + sutProvider.GetDependency().SubscriptionCreateAsync(Arg.Is( sub => sub.AutomaticTax.Enabled == true && @@ -1200,16 +1002,266 @@ public class ProviderBillingServiceTests sub.Items.ElementAt(1).Quantity == 100 && sub.Metadata["providerId"] == provider.Id.ToString() && sub.OffSession == true && - sub.ProrationBehavior == StripeConstants.ProrationBehavior.CreateProrations)).Returns(new Subscription - { - Id = "subscription_id", - Status = StripeConstants.SubscriptionStatus.Active - }); + sub.ProrationBehavior == StripeConstants.ProrationBehavior.CreateProrations)).Returns(expected); - await sutProvider.Sut.StartSubscription(provider); + var actual = await sutProvider.Sut.SetupSubscription(provider); - await sutProvider.GetDependency().Received(1) - .ReplaceAsync(Arg.Is(p => p.GatewaySubscriptionId == "subscription_id")); + Assert.Equivalent(expected, actual); + } + + #endregion + + #region UpdateSeatMinimums + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_NullProvider_ThrowsArgumentNullException( + SutProvider sutProvider) => + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSeatMinimums(null, 0, 0)); + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_NegativeSeatMinimum_ThrowsBadRequestException( + Provider provider, + SutProvider sutProvider) => + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateSeatMinimums(provider, -10, 100)); + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_NoPurchasedSeats_SyncsStripeWithNewSeatMinimum( + Provider provider, + SutProvider sutProvider) + { + var stripeAdapter = sutProvider.GetDependency(); + var providerPlanRepository = sutProvider.GetDependency(); + + const string enterpriseLineItemId = "enterprise_line_item_id"; + const string teamsLineItemId = "teams_line_item_id"; + + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Id = enterpriseLineItemId, + Price = new Price { Id = enterprisePriceId } + }, + new SubscriptionItem + { + Id = teamsLineItemId, + Price = new Price { Id = teamsPriceId } + } + ] + } + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId).Returns(subscription); + + var providerPlans = new List + { + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 0 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 30, PurchasedSeats = 0 } + }; + + providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); + + await sutProvider.Sut.UpdateSeatMinimums(provider, 70, 50); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly && providerPlan.SeatMinimum == 70)); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 50)); + + await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId, + Arg.Is( + options => + options.Items.Count == 2 && + options.Items.ElementAt(0).Id == enterpriseLineItemId && + options.Items.ElementAt(0).Quantity == 70 && + options.Items.ElementAt(1).Id == teamsLineItemId && + options.Items.ElementAt(1).Quantity == 50)); + } + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_PurchasedSeats_NewMinimumLessThanTotal_UpdatesPurchasedSeats( + Provider provider, + SutProvider sutProvider) + { + var stripeAdapter = sutProvider.GetDependency(); + var providerPlanRepository = sutProvider.GetDependency(); + + const string enterpriseLineItemId = "enterprise_line_item_id"; + const string teamsLineItemId = "teams_line_item_id"; + + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Id = enterpriseLineItemId, + Price = new Price { Id = enterprisePriceId } + }, + new SubscriptionItem + { + Id = teamsLineItemId, + Price = new Price { Id = teamsPriceId } + } + ] + } + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId).Returns(subscription); + + var providerPlans = new List + { + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 20 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 50, PurchasedSeats = 20 } + }; + + providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); + + await sutProvider.Sut.UpdateSeatMinimums(provider, 60, 60); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly && providerPlan.SeatMinimum == 60 && providerPlan.PurchasedSeats == 10)); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 60 && providerPlan.PurchasedSeats == 10)); + + await stripeAdapter.DidNotReceiveWithAnyArgs() + .SubscriptionUpdateAsync(Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_PurchasedSeats_NewMinimumGreaterThanTotal_ClearsPurchasedSeats_SyncsStripeWithNewSeatMinimum( + Provider provider, + SutProvider sutProvider) + { + var stripeAdapter = sutProvider.GetDependency(); + var providerPlanRepository = sutProvider.GetDependency(); + + const string enterpriseLineItemId = "enterprise_line_item_id"; + const string teamsLineItemId = "teams_line_item_id"; + + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Id = enterpriseLineItemId, + Price = new Price { Id = enterprisePriceId } + }, + new SubscriptionItem + { + Id = teamsLineItemId, + Price = new Price { Id = teamsPriceId } + } + ] + } + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId).Returns(subscription); + + var providerPlans = new List + { + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 20 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 50, PurchasedSeats = 20 } + }; + + providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); + + await sutProvider.Sut.UpdateSeatMinimums(provider, 80, 80); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly && providerPlan.SeatMinimum == 80 && providerPlan.PurchasedSeats == 0)); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 80 && providerPlan.PurchasedSeats == 0)); + + await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId, + Arg.Is( + options => + options.Items.Count == 2 && + options.Items.ElementAt(0).Id == enterpriseLineItemId && + options.Items.ElementAt(0).Quantity == 80 && + options.Items.ElementAt(1).Id == teamsLineItemId && + options.Items.ElementAt(1).Quantity == 80)); + } + + [Theory, BitAutoData] + public async Task UpdateSeatMinimums_SinglePlanTypeUpdate_Succeeds( + Provider provider, + SutProvider sutProvider) + { + var stripeAdapter = sutProvider.GetDependency(); + var providerPlanRepository = sutProvider.GetDependency(); + + const string enterpriseLineItemId = "enterprise_line_item_id"; + const string teamsLineItemId = "teams_line_item_id"; + + var enterprisePriceId = StaticStore.GetPlan(PlanType.EnterpriseMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + var teamsPriceId = StaticStore.GetPlan(PlanType.TeamsMonthly).PasswordManager.StripeProviderPortalSeatPlanId; + + var subscription = new Subscription + { + Items = new StripeList + { + Data = + [ + new SubscriptionItem + { + Id = enterpriseLineItemId, + Price = new Price { Id = enterprisePriceId } + }, + new SubscriptionItem + { + Id = teamsLineItemId, + Price = new Price { Id = teamsPriceId } + } + ] + } + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId).Returns(subscription); + + var providerPlans = new List + { + new() { PlanType = PlanType.EnterpriseMonthly, SeatMinimum = 50, PurchasedSeats = 0 }, + new() { PlanType = PlanType.TeamsMonthly, SeatMinimum = 30, PurchasedSeats = 0 } + }; + + providerPlanRepository.GetByProviderId(provider.Id).Returns(providerPlans); + + await sutProvider.Sut.UpdateSeatMinimums(provider, 70, 30); + + await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.EnterpriseMonthly && providerPlan.SeatMinimum == 70)); + + await providerPlanRepository.DidNotReceive().ReplaceAsync(Arg.Is( + providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly)); + + await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId, + Arg.Is( + options => + options.Items.Count == 1 && + options.Items.ElementAt(0).Id == enterpriseLineItemId && + options.Items.ElementAt(0).Quantity == 70)); } #endregion diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs new file mode 100644 index 000000000..d7dc11ba7 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/Secrets/BulkSecretAuthorizationHandlerTests.cs @@ -0,0 +1,224 @@ +#nullable enable +using System.Reflection; +using System.Security.Claims; +using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.Secrets; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.AuthorizationHandlers.Secrets; + +[SutProviderCustomize] +[ProjectCustomize] +public class BulkSecretAuthorizationHandlerTests +{ + [Fact] + public void BulkSecretOperations_OnlyPublicStatic() + { + var publicStaticFields = typeof(BulkSecretOperations).GetFields(BindingFlags.Public | BindingFlags.Static); + var allFields = typeof(BulkSecretOperations).GetFields(); + Assert.Equal(publicStaticFields.Length, allFields.Length); + } + + [Theory] + [BitAutoData] + public async Task Handler_MisMatchedOrganizations_DoesNotSucceed( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources[0].OrganizationId = Guid.NewGuid(); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()) + .ReturnsForAnyArgs(true); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task Handler_NoAccessToSecretsManager_DoesNotSucceed( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()) + .ReturnsForAnyArgs(false); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task Handler_UnsupportedSecretOperationRequirement_Throws( + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = new BulkSecretOperationRequirement(); + resources = SetSameOrganization(resources); + SetupUserSubstitutes(sutProvider, AccessClientType.User, resources.First().OrganizationId); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await Assert.ThrowsAsync(() => sutProvider.Sut.HandleAsync(authzContext)); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_NoAccessToSecrets_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(secretIds.ToDictionary(id => id, _ => (false, false))); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_HasAccessToSomeSecrets_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (false, false)); + accessResult[secretIds.First()] = (true, true); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_PartialAccessReturn_DoesNotSucceed( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (false, false)); + accessResult.Remove(secretIds.First()); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(AccessClientType.User)] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.ServiceAccount)] + public async Task Handler_HasAccessToAllSecrets_Success( + AccessClientType accessClientType, + SutProvider sutProvider, List resources, + ClaimsPrincipal claimsPrincipal) + { + var requirement = BulkSecretOperations.ReadAll; + resources = SetSameOrganization(resources); + var secretIds = + SetupSecretAccessRequest(sutProvider, resources, accessClientType, resources.First().OrganizationId); + + var accessResult = secretIds.ToDictionary(secretId => secretId, _ => (true, true)); + sutProvider.GetDependency() + .AccessToSecretsAsync(Arg.Any>(), Arg.Any(), Arg.Any()) + .Returns(accessResult); + + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, resources); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.True(authzContext.HasSucceeded); + } + + private static List SetSameOrganization(List secrets) + { + var organizationId = secrets.First().OrganizationId; + foreach (var secret in secrets) + { + secret.OrganizationId = organizationId; + } + + return secrets; + } + + private static void SetupUserSubstitutes( + SutProvider sutProvider, + AccessClientType accessClientType, + Guid organizationId, + Guid userId = new()) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId) + .Returns(true); + sutProvider.GetDependency().GetAccessClientAsync(default, organizationId) + .ReturnsForAnyArgs((accessClientType, userId)); + } + + private static List SetupSecretAccessRequest( + SutProvider sutProvider, + IEnumerable resources, + AccessClientType accessClientType, + Guid organizationId, + Guid userId = new()) + { + SetupUserSubstitutes(sutProvider, accessClientType, organizationId, userId); + return resources.Select(s => s.Id).ToList(); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Requests/RequestSMAccessCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Requests/RequestSMAccessCommandTests.cs new file mode 100644 index 000000000..e9387deec --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Commands/Requests/RequestSMAccessCommandTests.cs @@ -0,0 +1,96 @@ +using Bit.Commercial.Core.SecretsManager.Commands.Requests; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.Commands.Requests; + +[SutProviderCustomize] +public class RequestSMAccessCommandTests +{ + [Theory] + [BitAutoData] + public async Task SendRequestAccessToSM_Success( + User user, + Organization organization, + ICollection orgUsers, + string emailContent, + SutProvider sutProvider) + { + foreach (var userDetails in orgUsers) + { + userDetails.Type = OrganizationUserType.Admin; + } + + orgUsers.First().Type = OrganizationUserType.Owner; + + await sutProvider.Sut.SendRequestAccessToSM(organization, orgUsers, user, emailContent); + + var adminEmailList = orgUsers + .Where(o => o.Type <= OrganizationUserType.Admin) + .Select(a => a.Email) + .Distinct() + .ToList(); + + await sutProvider.GetDependency() + .Received(1) + .SendRequestSMAccessToAdminEmailAsync(Arg.Is(AssertHelper.AssertPropertyEqual(adminEmailList)), Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async Task SendRequestAccessToSM_NoAdmins_ThrowsBadRequestException( + User user, + Organization organization, + ICollection orgUsers, + string emailContent, + SutProvider sutProvider) + { + // Set OrgUsers so they are only users, no admins or owners + foreach (OrganizationUserUserDetails userDetails in orgUsers) + { + userDetails.Type = OrganizationUserType.User; + } + + await Assert.ThrowsAsync(() => sutProvider.Sut.SendRequestAccessToSM(organization, orgUsers, user, emailContent)); + } + + + [Theory] + [BitAutoData] + public async Task SendRequestAccessToSM_SomeAdmins_EmailListIsAsExpected( + User user, + Organization organization, + ICollection orgUsers, + string emailContent, + SutProvider sutProvider) + { + foreach (OrganizationUserUserDetails userDetails in orgUsers) + { + userDetails.Type = OrganizationUserType.User; + } + + // Make the first orgUser an admin so it's a mix of Admin + Users + orgUsers.First().Type = OrganizationUserType.Admin; + + var adminEmailList = orgUsers + .Where(o => o.Type == OrganizationUserType.Admin) // Filter by Admin type + .Select(a => a.Email) + .Distinct() + .ToList(); + + await sutProvider.Sut.SendRequestAccessToSM(organization, orgUsers, user, emailContent); + + await sutProvider.GetDependency() + .Received(1) + .SendRequestSMAccessToAdminEmailAsync(Arg.Is(AssertHelper.AssertPropertyEqual(adminEmailList)), Arg.Any(), Arg.Any(), Arg.Any()); + } +} diff --git a/bitwarden_license/test/Scim.Test/Users/PostUserCommandTests.cs b/bitwarden_license/test/Scim.Test/Users/PostUserCommandTests.cs index cf1c33702..71ad6361b 100644 --- a/bitwarden_license/test/Scim.Test/Users/PostUserCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Users/PostUserCommandTests.cs @@ -43,7 +43,6 @@ public class PostUserCommandTests Arg.Is(i => i.Emails.Single().Equals(scimUserRequestModel.PrimaryEmail.ToLowerInvariant()) && i.Type == OrganizationUserType.User && - !i.AccessAll && !i.Collections.Any() && !i.Groups.Any() && i.AccessSecretsManager), externalId) @@ -56,7 +55,6 @@ public class PostUserCommandTests Arg.Is(i => i.Emails.Single().Equals(scimUserRequestModel.PrimaryEmail.ToLowerInvariant()) && i.Type == OrganizationUserType.User && - !i.AccessAll && !i.Collections.Any() && !i.Groups.Any() && i.AccessSecretsManager), externalId); diff --git a/bitwarden_license/test/bitwarden_license.tests.sln b/bitwarden_license/test/bitwarden_license.tests.sln deleted file mode 100644 index c125c5bd1..000000000 --- a/bitwarden_license/test/bitwarden_license.tests.sln +++ /dev/null @@ -1,37 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 25.0.1703.8 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Commercial.Core.Test", "Commercial.Core.Test\Commercial.Core.Test.csproj", "{70F03E72-2F38-4497-BF31-EA19B13B2161}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scim.Test", "Scim.Test\Scim.Test.csproj", "{9BC8E2C9-400D-4FA7-86CA-3E1794E7CA4C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scim.IntegrationTest", "Scim.IntegrationTest\Scim.IntegrationTest.csproj", "{45CD3F1B-127E-44B7-B22B-28D677B621D9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {70F03E72-2F38-4497-BF31-EA19B13B2161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70F03E72-2F38-4497-BF31-EA19B13B2161}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70F03E72-2F38-4497-BF31-EA19B13B2161}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70F03E72-2F38-4497-BF31-EA19B13B2161}.Release|Any CPU.Build.0 = Release|Any CPU - {9BC8E2C9-400D-4FA7-86CA-3E1794E7CA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BC8E2C9-400D-4FA7-86CA-3E1794E7CA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BC8E2C9-400D-4FA7-86CA-3E1794E7CA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BC8E2C9-400D-4FA7-86CA-3E1794E7CA4C}.Release|Any CPU.Build.0 = Release|Any CPU - {45CD3F1B-127E-44B7-B22B-28D677B621D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45CD3F1B-127E-44B7-B22B-28D677B621D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45CD3F1B-127E-44B7-B22B-28D677B621D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45CD3F1B-127E-44B7-B22B-28D677B621D9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BAD5FA17-2653-401A-A1E5-A31C420B9DE8} - EndGlobalSection -EndGlobal diff --git a/dev/ef_migrate.ps1 b/dev/ef_migrate.ps1 index 14cf13088..999ce1a82 100644 --- a/dev/ef_migrate.ps1 +++ b/dev/ef_migrate.ps1 @@ -9,7 +9,11 @@ $service = "mysql" Write-Output "--- Attempting to start $service service ---" -docker-compose --profile $service up -d --no-recreate +# Attempt to start mysql but if docker-compose doesn't +# exist just trust that the user has it running some other way +if (command -v docker-compose) { + docker-compose --profile $service up -d --no-recreate +} dotnet tool restore diff --git a/dev/migrate.ps1 b/dev/migrate.ps1 index 03890b555..ee78e90d3 100755 --- a/dev/migrate.ps1 +++ b/dev/migrate.ps1 @@ -27,7 +27,9 @@ if ($all -or $postgres -or $mysql -or $sqlite) { if ($all -or $mssql) { function Get-UserSecrets { - return dotnet user-secrets list --json --project ../src/Api | ConvertFrom-Json + # The dotnet cli command sometimes adds //BEGIN and //END comments to the output, Where-Object removes comments + # to ensure a valid json + return dotnet user-secrets list --json --project ../src/Api | Where-Object { $_ -notmatch "^//" } | ConvertFrom-Json } if ($selfhost) { diff --git a/global.json b/global.json index 391ba3c2a..0c1d58f41 100644 --- a/global.json +++ b/global.json @@ -2,5 +2,8 @@ "sdk": { "version": "8.0.100", "rollForward": "latestFeature" + }, + "msbuild-sdks": { + "Microsoft.Build.Traversal": "4.1.0" } } diff --git a/src/Admin/AdminConsole/Controllers/ProvidersController.cs b/src/Admin/AdminConsole/Controllers/ProvidersController.cs index 8eb28e24a..cee87fbb7 100644 --- a/src/Admin/AdminConsole/Controllers/ProvidersController.cs +++ b/src/Admin/AdminConsole/Controllers/ProvidersController.cs @@ -13,6 +13,7 @@ using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; using Bit.Core.Billing.Repositories; +using Bit.Core.Billing.Services; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -40,6 +41,7 @@ public class ProvidersController : Controller private readonly ICreateProviderCommand _createProviderCommand; private readonly IFeatureService _featureService; private readonly IProviderPlanRepository _providerPlanRepository; + private readonly IProviderBillingService _providerBillingService; private readonly string _stripeUrl; private readonly string _braintreeMerchantUrl; private readonly string _braintreeMerchantId; @@ -57,6 +59,7 @@ public class ProvidersController : Controller ICreateProviderCommand createProviderCommand, IFeatureService featureService, IProviderPlanRepository providerPlanRepository, + IProviderBillingService providerBillingService, IWebHostEnvironment webHostEnvironment) { _organizationRepository = organizationRepository; @@ -71,6 +74,7 @@ public class ProvidersController : Controller _createProviderCommand = createProviderCommand; _featureService = featureService; _providerPlanRepository = providerPlanRepository; + _providerBillingService = providerBillingService; _stripeUrl = webHostEnvironment.GetStripeUrl(); _braintreeMerchantUrl = webHostEnvironment.GetBraintreeMerchantUrl(); _braintreeMerchantId = globalSettings.Braintree.MerchantId; @@ -223,19 +227,10 @@ public class ProvidersController : Controller } else { - foreach (var providerPlan in providerPlans) - { - if (providerPlan.PlanType == PlanType.EnterpriseMonthly) - { - providerPlan.SeatMinimum = model.EnterpriseMonthlySeatMinimum; - } - else if (providerPlan.PlanType == PlanType.TeamsMonthly) - { - providerPlan.SeatMinimum = model.TeamsMonthlySeatMinimum; - } - - await _providerPlanRepository.ReplaceAsync(providerPlan); - } + await _providerBillingService.UpdateSeatMinimums( + provider, + model.EnterpriseMonthlySeatMinimum, + model.TeamsMonthlySeatMinimum); } return RedirectToAction("Edit", new { id }); @@ -315,8 +310,7 @@ public class ProvidersController : Controller return RedirectToAction("Index"); } - var flexibleCollectionsV1Enabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - var organization = model.CreateOrganization(provider, flexibleCollectionsV1Enabled); + var organization = model.CreateOrganization(provider); await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted); await _providerService.AddOrganization(providerId, organization.Id, null); diff --git a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs index a582ac2a2..04079138d 100644 --- a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs @@ -179,30 +179,91 @@ public class OrganizationEditModel : OrganizationViewModel * This is mapped manually below to provide some type safety in case the plan objects change * Add mappings for individual properties as you need them */ - public IEnumerable> GetPlansHelper() => + public object GetPlansHelper() => StaticStore.Plans .Where(p => p.SupportsSecretsManager) - .Select(p => new Dictionary + .Select(p => { - { "type", p.Type }, - { "baseServiceAccount", p.SecretsManager.BaseServiceAccount } + var plan = new + { + Type = p.Type, + ProductTier = p.ProductTier, + Name = p.Name, + IsAnnual = p.IsAnnual, + NameLocalizationKey = p.NameLocalizationKey, + DescriptionLocalizationKey = p.DescriptionLocalizationKey, + CanBeUsedByBusiness = p.CanBeUsedByBusiness, + TrialPeriodDays = p.TrialPeriodDays, + HasSelfHost = p.HasSelfHost, + HasPolicies = p.HasPolicies, + HasGroups = p.HasGroups, + HasDirectory = p.HasDirectory, + HasEvents = p.HasEvents, + HasTotp = p.HasTotp, + Has2fa = p.Has2fa, + HasApi = p.HasApi, + HasSso = p.HasSso, + HasKeyConnector = p.HasKeyConnector, + HasScim = p.HasScim, + HasResetPassword = p.HasResetPassword, + UsersGetPremium = p.UsersGetPremium, + HasCustomPermissions = p.HasCustomPermissions, + UpgradeSortOrder = p.UpgradeSortOrder, + DisplaySortOrder = p.DisplaySortOrder, + LegacyYear = p.LegacyYear, + Disabled = p.Disabled, + SupportsSecretsManager = p.SupportsSecretsManager, + PasswordManager = + new + { + StripePlanId = p.PasswordManager?.StripePlanId, + StripeSeatPlanId = p.PasswordManager?.StripeSeatPlanId, + StripeProviderPortalSeatPlanId = p.PasswordManager?.StripeProviderPortalSeatPlanId, + BasePrice = p.PasswordManager?.BasePrice, + SeatPrice = p.PasswordManager?.SeatPrice, + ProviderPortalSeatPrice = p.PasswordManager?.ProviderPortalSeatPrice, + AllowSeatAutoscale = p.PasswordManager?.AllowSeatAutoscale, + HasAdditionalSeatsOption = p.PasswordManager?.HasAdditionalSeatsOption, + MaxAdditionalSeats = p.PasswordManager?.MaxAdditionalSeats, + BaseSeats = p.PasswordManager?.BaseSeats, + HasPremiumAccessOption = p.PasswordManager?.HasPremiumAccessOption, + StripePremiumAccessPlanId = p.PasswordManager?.StripePremiumAccessPlanId, + PremiumAccessOptionPrice = p.PasswordManager?.PremiumAccessOptionPrice, + MaxSeats = p.PasswordManager?.MaxSeats, + BaseStorageGb = p.PasswordManager?.BaseStorageGb, + HasAdditionalStorageOption = p.PasswordManager?.HasAdditionalStorageOption, + AdditionalStoragePricePerGb = p.PasswordManager?.AdditionalStoragePricePerGb, + StripeStoragePlanId = p.PasswordManager?.StripeStoragePlanId, + MaxAdditionalStorage = p.PasswordManager?.MaxAdditionalStorage, + MaxCollections = p.PasswordManager?.MaxCollections + }, + SecretsManager = new + { + MaxServiceAccounts = p.SecretsManager?.MaxServiceAccounts, + AllowServiceAccountsAutoscale = p.SecretsManager?.AllowServiceAccountsAutoscale, + StripeServiceAccountPlanId = p.SecretsManager?.StripeServiceAccountPlanId, + AdditionalPricePerServiceAccount = p.SecretsManager?.AdditionalPricePerServiceAccount, + BaseServiceAccount = p.SecretsManager?.BaseServiceAccount, + MaxAdditionalServiceAccount = p.SecretsManager?.MaxAdditionalServiceAccount, + HasAdditionalServiceAccountOption = p.SecretsManager?.HasAdditionalServiceAccountOption, + StripeSeatPlanId = p.SecretsManager?.StripeSeatPlanId, + HasAdditionalSeatsOption = p.SecretsManager?.HasAdditionalSeatsOption, + BasePrice = p.SecretsManager?.BasePrice, + SeatPrice = p.SecretsManager?.SeatPrice, + BaseSeats = p.SecretsManager?.BaseSeats, + MaxSeats = p.SecretsManager?.MaxSeats, + MaxAdditionalSeats = p.SecretsManager?.MaxAdditionalSeats, + AllowSeatAutoscale = p.SecretsManager?.AllowSeatAutoscale, + MaxProjects = p.SecretsManager?.MaxProjects + } + }; + return plan; }); - public Organization CreateOrganization(Provider provider, bool flexibleCollectionsV1Enabled) + public Organization CreateOrganization(Provider provider) { BillingEmail = provider.BillingEmail; - - var newOrg = new Organization - { - // Flexible Collections MVP is fully released and all organizations must always have this setting enabled. - // AC-1714 will remove this flag after all old code has been removed. - FlexibleCollections = true, - - // This is a transitional setting that defaults to ON until Flexible Collections v1 is released - // (to preserve existing behavior) and defaults to OFF after release (enabling new behavior) - AllowAdminAccessToAllCollectionItems = !flexibleCollectionsV1Enabled - }; - return ToOrganization(newOrg); + return ToOrganization(new Organization()); } public Organization ToOrganization(Organization existingOrganization) diff --git a/src/Admin/AdminConsole/Models/OrganizationViewModel.cs b/src/Admin/AdminConsole/Models/OrganizationViewModel.cs index 7706389d1..b58d3aa52 100644 --- a/src/Admin/AdminConsole/Models/OrganizationViewModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationViewModel.cs @@ -69,14 +69,4 @@ public class OrganizationViewModel public int ServiceAccountsCount { get; set; } public int OccupiedSmSeatsCount { get; set; } public bool UseSecretsManager => Organization.UseSecretsManager; - - public string GetCollectionManagementSetting(bool collectionManagementSetting) - { - if (!Organization.FlexibleCollections) - { - return "N/A"; - } - - return collectionManagementSetting ? "On" : "Off"; - } } diff --git a/src/Admin/AdminConsole/Views/Organizations/_ViewInformation.cshtml b/src/Admin/AdminConsole/Views/Organizations/_ViewInformation.cshtml index c78e61eb9..b017e7ccb 100644 --- a/src/Admin/AdminConsole/Views/Organizations/_ViewInformation.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/_ViewInformation.cshtml @@ -50,14 +50,11 @@
Collections
@Model.CollectionCount
-
Collection management enhancements
-
@(Model.Organization.FlexibleCollections ? "On" : "Off")
-
Administrators manage all collections
-
@(Model.GetCollectionManagementSetting(Model.Organization.AllowAdminAccessToAllCollectionItems))
+
@(Model.Organization.AllowAdminAccessToAllCollectionItems ? "On" : "Off")
Limit collection creation to administrators
-
@(Model.GetCollectionManagementSetting(Model.Organization.LimitCollectionCreationDeletion))
+
@(Model.Organization.LimitCollectionCreationDeletion ? "On" : "Off")

Secrets Manager

diff --git a/src/Admin/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml b/src/Admin/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml index 5e0b938da..98d4c0d90 100644 --- a/src/Admin/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml +++ b/src/Admin/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml @@ -2,6 +2,7 @@ @using Bit.Admin.Utilities @using Bit.Core.Billing.Enums @using Bit.Core.Enums +@using Bit.Core.Utilities @model OrganizationEditModel - - - - - - - - - - + @if (TempData["Error"] != null) { diff --git a/src/Admin/Views/Users/Index.cshtml b/src/Admin/Views/Users/Index.cshtml index 98847d509..7f941a819 100644 --- a/src/Admin/Views/Users/Index.cshtml +++ b/src/Admin/Views/Users/Index.cshtml @@ -1,5 +1,6 @@ @model UsersModel @inject Bit.Core.Services.IUserService userService +@inject Bit.Core.Services.IFeatureService featureService @{ ViewData["Title"] = "Users"; } @@ -69,13 +70,28 @@ { } - @if(await userService.TwoFactorIsEnabledAsync(user)) + @if (featureService.IsEnabled(Bit.Core.FeatureFlagKeys.MembersTwoFAQueryOptimization)) { - + var usersTwoFactorIsEnabled = TempData["UsersTwoFactorIsEnabled"] as IEnumerable<(Guid userId, bool twoFactorIsEnabled)>; + @if(usersTwoFactorIsEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled) + { + + } + else + { + + } } else { - + @if(await userService.TwoFactorIsEnabledAsync(user)) + { + + } + else + { + + } } diff --git a/src/Admin/Views/Users/_ViewInformation.cshtml b/src/Admin/Views/Users/_ViewInformation.cshtml index 72f84d3ce..b1e1c2fcf 100644 --- a/src/Admin/Views/Users/_ViewInformation.cshtml +++ b/src/Admin/Views/Users/_ViewInformation.cshtml @@ -29,15 +29,15 @@
@Model.User.RevisionDate.ToString()
Last Email Address Change
-
@(Model.User.LastEmailChangeDate.ToString() ?? "-")
+
@(Model.User.LastEmailChangeDate?.ToString() ?? "-")
Last KDF Change
-
@(Model.User.LastKdfChangeDate.ToString() ?? "-")
+
@(Model.User.LastKdfChangeDate?.ToString() ?? "-")
Last Key Rotation
-
@(Model.User.LastKeyRotationDate.ToString() ?? "-")
+
@(Model.User.LastKeyRotationDate?.ToString() ?? "-")
Last Password Change
-
@(Model.User.LastPasswordChangeDate.ToString() ?? "-")
+
@(Model.User.LastPasswordChangeDate?.ToString() ?? "-")
diff --git a/src/Admin/gulpfile.js b/src/Admin/gulpfile.js deleted file mode 100644 index f9f97592a..000000000 --- a/src/Admin/gulpfile.js +++ /dev/null @@ -1,79 +0,0 @@ -/// - -const gulp = require('gulp'); -const merge = require('merge-stream'); -const sass = require('gulp-sass')(require("sass")); -const del = require('del'); - -const paths = {}; -paths.webroot = './wwwroot/'; -paths.npmDir = './node_modules/'; -paths.sassDir = './Sass/'; -paths.libDir = paths.webroot + 'lib/'; -paths.cssDir = paths.webroot + 'css/'; -paths.jsDir = paths.webroot + 'js/'; - -paths.sass = paths.sassDir + '**/*.scss'; -paths.minCss = paths.cssDir + '**/*.min.css'; -paths.js = paths.jsDir + '**/*.js'; -paths.minJs = paths.jsDir + '**/*.min.js'; -paths.libJs = paths.libDir + '**/*.js'; -paths.libMinJs = paths.libDir + '**/*.min.js'; - -function clean() { - return del([paths.minJs, paths.cssDir, paths.libDir]); -} - -function lib() { - const libs = [ - { - src: paths.npmDir + 'bootstrap/dist/js/*', - dest: paths.libDir + 'bootstrap/js' - }, - { - src: paths.npmDir + 'popper.js/dist/umd/*', - dest: paths.libDir + 'popper' - }, - { - src: paths.npmDir + 'font-awesome/css/*', - dest: paths.libDir + 'font-awesome/css' - }, - { - src: paths.npmDir + 'font-awesome/fonts/*', - dest: paths.libDir + 'font-awesome/fonts' - }, - { - src: paths.npmDir + 'jquery/dist/jquery.*', - dest: paths.libDir + 'jquery' - }, - { - src: paths.npmDir + 'toastr/build/*', - dest: paths.libDir + 'toastr' - }, - { - src: paths.sassDir + 'webfonts/*', - dest: paths.cssDir + 'webfonts' - } - ]; - - const tasks = libs.map((lib) => { - return gulp.src(lib.src).pipe(gulp.dest(lib.dest)); - }); - return merge(tasks); -} - -function runSass() { - return gulp.src(paths.sass) - .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) - .pipe(gulp.dest(paths.cssDir)); -} - -function sassWatch() { - gulp.watch(paths.sass, runSass); -} - -exports.build = gulp.series(clean, gulp.parallel([lib, runSass])); -exports['sass:watch'] = sassWatch; -exports.sass = runSass; -exports.lib = lib; -exports.clean = clean; diff --git a/src/Admin/package-lock.json b/src/Admin/package-lock.json index ac3cd03c5..e3c9d4889 100644 --- a/src/Admin/package-lock.json +++ b/src/Admin/package-lock.json @@ -8,587 +8,428 @@ "name": "bitwarden-admin", "version": "0.0.0", "license": "GPL-3.0", - "devDependencies": { + "dependencies": { "bootstrap": "4.6.2", - "del": "6.1.1", "font-awesome": "4.7.0", - "gulp": "4.0.2", - "gulp-sass": "5.1.0", "jquery": "3.7.1", - "merge-stream": "2.0.0", "popper.js": "1.16.1", - "sass": "1.75.0", "toastr": "2.1.4" + }, + "devDependencies": { + "css-loader": "7.1.2", + "expose-loader": "5.0.0", + "mini-css-extract-plugin": "2.9.0", + "sass": "1.75.0", + "sass-loader": "16.0.0", + "webpack": "5.93.0", + "webpack-cli": "5.1.4" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, - "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">= 8" + "node": ">=6.0.0" } }, - "node_modules/@nodelib/fs.stat": { + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", + "dev": true, + "dependencies": { + "undici-types": "~6.11.1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">= 8" + "node": ">=0.4.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-wrap": "^0.1.0" + "ajv": "^8.0.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-wrap": "0.1.0" + "fast-deep-equal": "^3.1.3" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "ajv": "^8.8.2" } }, "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "ISC", "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/anymatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "license": "(MIT OR Apache-2.0)", - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-filter": "^1.1.1", - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "array-each": "^1.0.0", - "array-initial": "^1.0.0", - "array-last": "^1.1.1", - "async-done": "^1.2.2", - "async-settle": "^1.0.0", - "now-and-later": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bootstrap": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", - "dev": true, "funding": [ { "type": "github", @@ -599,3630 +440,86 @@ "url": "https://opencollective.com/bootstrap" } ], - "license": "MIT", "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "MIT", "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "node_modules/caniuse-lite": { + "version": "1.0.30001644", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001644.tgz", + "integrity": "sha512-YGvlOZB4QhZuiis+ETS0VXR+MExbFf4fZYYeMTEE0aTQd/RdIjkTyZjLrbYVKnHzppDvnOhritRVv+i7Go6mHw==", "dev": true, - "license": "MIT", - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] }, "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/chokidar/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "dev": true, - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dev": true, - "license": "MIT", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" - } - }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "dev": true, - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", - "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/findup-sync/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/findup-sync/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fined/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", - "dev": true, - "license": "(OFL-1.1 AND MIT)", - "engines": { - "node": ">=0.10.3" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", - "integrity": "sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true, - "license": "ISC" - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-stream/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob-watcher": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", - "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "^2.0.0", - "async-done": "^1.2.0", - "chokidar": "^2.0.0", - "is-negated-glob": "^1.0.0", - "just-debounce": "^1.0.0", - "normalize-path": "^3.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glogg": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", - "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sparkles": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/gulp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", - "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-watcher": "^5.0.3", - "gulp-cli": "^2.2.0", - "undertaker": "^1.2.1", - "vinyl-fs": "^3.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-cli": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", - "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "archy": "^1.0.0", - "array-sort": "^1.0.0", - "color-support": "^1.1.3", - "concat-stream": "^1.6.0", - "copy-props": "^2.0.1", - "fancy-log": "^1.3.2", - "gulplog": "^1.0.0", - "interpret": "^1.4.0", - "isobject": "^3.0.1", - "liftoff": "^3.1.0", - "matchdep": "^2.0.0", - "mute-stdout": "^1.0.0", - "pretty-hrtime": "^1.0.0", - "replace-homedir": "^1.0.0", - "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.2.0", - "yargs": "^7.1.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gulp-sass": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.1.0.tgz", - "integrity": "sha512-7VT0uaF+VZCmkNBglfe1b34bxn/AfcssquLKVDYnCDJ3xNBaW7cUuI3p3BQmoKcoKFrs9jdzUxyb+u+NGfL4OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "picocolors": "^1.0.0", - "plugin-error": "^1.0.1", - "replace-ext": "^2.0.0", - "strip-ansi": "^6.0.1", - "vinyl-sourcemaps-apply": "^0.2.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha512-hm6N8nrm3Y08jXie48jsC55eCZz9mnb4OirAStEk2deqeyhXU3C1otDVh+ccttMuc1sBi6RX6ZJ720hs9RCvgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glogg": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", - "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", - "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/just-debounce": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", - "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/last-run": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", - "integrity": "sha512-U/VxvpX4N/rFvPzr3qG5EtLKEnNI0emvIQB3/ecEwv+8GHaUKbIB8vxv1Oai5FAF0d0r7LXHhLLe5K/yChm5GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-resolution": "^2.0.0", - "es6-weak-map": "^2.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lead": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", - "integrity": "sha512-IpSVCk9AYvLHo5ctcIXxOBpMWUe+4TKN3VPWAKUbJikkmsGp0VrSM8IttVc32D6J4WUsiPE6aEFRNmIoF/gdow==", - "dev": true, - "license": "MIT", - "dependencies": { - "flush-write-stream": "^1.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/liftoff": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", - "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^3.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", - "integrity": "sha512-LFgVbaHIHMqCRuCZyfCtUOq9/Lnzhi7Z0KFUE2fhD54+JN2jLh3hC02RLkqauJ3U4soU6H1J3tfj/Byk7GoEjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "findup-sync": "^2.0.0", - "micromatch": "^3.0.4", - "resolve": "^1.4.0", - "stack-trace": "0.0.10" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/matchdep/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/matchdep/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/matchdep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/matchdep/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stdout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", - "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/nan": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", - "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", - "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.3.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.reduce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", - "integrity": "sha512-naLhxxpUESbNkRqc35oQ2scZSJueHGQNUfMW/0U37IgN6tE2dgDWg3whf+NEliy3F/QysrO48XKUz/nGPe+AQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/ordered-read-streams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", - "integrity": "sha512-Z87aSjx3r5c0ZB7bcJqIgIRX5bxR7A4aSzvIbaxd0oTkWBCOoKfuGHiKj60CHVUgg1Phm5yMZzBdt8XqRs73Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "lcid": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", - "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/plugin-error/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plugin-error/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/readdirp/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/readdirp/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", - "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-bom-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", - "integrity": "sha512-wigO8/O08XHb8YPzpDDT+QmRANfW6vLqxfaXm1YXhnFf3AkSLyjfG3GEFg4McZkmgL7KvCj5u2KczkvSP6NfHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true, - "license": "ISC" - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/replace-homedir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", - "integrity": "sha512-CHPV/GAglbIB1tnQgaiysb8H2yCy8WQ7lcEwQ/eT+kLj0QHV8LnJW0zpqpE7RSkrMSRoa+EBoag86clf7WAgSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1", - "is-absolute": "^1.0.0", - "remove-trailing-separator": "^1.1.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true, - "license": "ISC" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", - "integrity": "sha512-NYDgziiroVeDC29xq7bp/CacZERYsA9bXYd1ZmcJlF3BcrZv5pTb4NG7SjdyKDnXZ84aC4vo2u6sNKIA1LCu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^3.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true, - "license": "MIT" - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass/node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sass/node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sass/node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4242,12 +539,248 @@ "fsevents": "~2.3.2" } }, - "node_modules/sass/node_modules/fill-range": { + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.3.tgz", + "integrity": "sha512-QNdYSS5i8D9axWp/6XIezRObRHqaav/ur9z1VzCDUCH1XIFOr9WQk5xmgunhsTpjjgDy3oLxO/WMOVZlpUQrlA==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expose-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-5.0.0.tgz", + "integrity": "sha512-BtUqYRmvx1bEY5HN6eK2I9URUZgNmN0x5UANuocaNjXSgfoDlkXt+wyEMe7i5DzDNh2BKJHPc5F4rBwEdSQX6w==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4255,13 +788,42 @@ "node": ">=8" } }, - "node_modules/sass/node_modules/fsevents": { + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/font-awesome": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", + "integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==", + "engines": { + "node": ">=0.10.3" + } + }, + "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -4270,12 +832,111 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/sass/node_modules/is-binary-path": { + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "dev": true + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4283,112 +944,56 @@ "node": ">=8" } }, - "node_modules/sass/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/sass/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", - "integrity": "sha512-Ny/iyOzSSa8M5ML46IAx3iXc6tfOsYU2R4AXi2UpHk60Zrgyq6eqPj/xiOfS0rRl/iiQ/rdJkVjw/5cdUyCntQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "sver-compat": "^1.5.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/set-value/node_modules/is-plain-object": { + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -4396,110 +1001,618 @@ "node": ">=0.10.0" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, "engines": { "node": ">=8" } }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "MIT", "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "mime-db": "1.52.0" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "p-try": "^2.0.0" }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/postcss": { + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, - "license": "MIT", "dependencies": { - "is-descriptor": "^1.0.0" + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "license": "MIT", "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" + "resolve-from": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/snapdragon-util": { + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sass": { + "version": "1.75.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", + "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.0.tgz", + "integrity": "sha512-n13Z+3rU9A177dk4888czcVFiC8CL9dii4qpXWUg3YIIgZEvi9TCFKjOQcbK0kJM7DJu9VucrZFddvNfYCPwtw==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, - "license": "MIT", "dependencies": { - "kind-of": "^3.2.0" + "kind-of": "^6.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4509,243 +1622,33 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, - "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "license": "MIT", "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT" - }, - "node_modules/sparkles": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", - "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "node": ">=10" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -4753,7 +1656,6 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4761,626 +1663,393 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sver-compat": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", - "integrity": "sha512-aFTHfmjwizMNlNE6dsGmoAM4lHjL0CyiobWaFiXWSlD7cIxshW422Nb8KbXCmR6z+0ZEPY+daXJrDyh/vuwTyg==", + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, - "license": "MIT", - "dependencies": { - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2-filter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", - "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "~2.0.0", - "xtend": "~4.0.0" - } - }, - "node_modules/time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/to-absolute-glob": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", - "integrity": "sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==", + "node_modules/terser": { + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", "dev": true, - "license": "MIT", "dependencies": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, - "license": "MIT", "dependencies": { - "kind-of": "^3.0.2" + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { - "is-buffer": "^1.1.5" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, - "license": "MIT", "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "license": "MIT", "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-descriptor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", - "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-through": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", - "integrity": "sha512-+QIz37Ly7acM4EMdw2PRN389OneM5+d844tirkGp4dPKzI5OE72V9OsbFp+CIYJDahZ41ZV05hNtcPAQUAm9/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "through2": "^2.0.3" - }, - "engines": { - "node": ">= 0.10" + "node": ">=8.0" } }, "node_modules/toastr": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", "integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==", - "dev": true, "dependencies": { "jquery": ">=1.12.0" } }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "dev": true, - "license": "ISC" + "node_modules/undici-types": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", + "dev": true }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, - "license": "MIT" - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", - "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", - "dev": true, - "license": "MIT", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "arr-flatten": "^1.0.1", - "arr-map": "^2.0.0", - "bach": "^1.0.0", - "collection-map": "^1.0.0", - "es6-weak-map": "^2.0.1", - "fast-levenshtein": "^1.0.0", - "last-run": "^1.1.0", - "object.defaults": "^1.0.0", - "object.reduce": "^1.0.0", - "undertaker-registry": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" + "bin": { + "update-browserslist-db": "cli.js" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/unique-stream": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", - "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT", "dependencies": { - "json-stable-stringify-without-jsonify": "^1.0.1", - "through2-filter": "^3.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true, - "license": "MIT" - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "punycode": "^2.1.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" + "dev": true }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dev": true, - "license": "MIT", "dependencies": { - "homedir-polyfill": "^1.0.1" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/webpack": { + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-or-function": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", - "integrity": "sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" }, "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } } }, - "node_modules/vinyl-fs": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", - "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, - "license": "MIT", "dependencies": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">= 0.10" + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/vinyl-sourcemap": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", - "integrity": "sha512-NiibMgt6VJGJmyw7vtzhctDcfKch4e4n9TBeoWlirb7FMg9/1Ov9k+A5ZRAtywBpRPiyECvQRQllYM8dECegVA==", + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, - "license": "MIT", "dependencies": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10.0.0" } }, - "node_modules/vinyl-sourcemap/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { - "remove-trailing-separator": "^1.0.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-sourcemaps-apply": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz", - "integrity": "sha512-+oDh3KYZBoZC8hfocrbrxbLUeaYtQK7J5WU5Br9VqWqmCll3tFJqKp97GC9GmMsVIL0qnx2DgEDVxdo5EZ5sSw==", - "dev": true, - "license": "ISC", - "dependencies": { - "source-map": "^0.5.1" - } - }, - "node_modules/vinyl/node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "node-which": "bin/node-which" }, "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", - "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.1" - } - }, - "node_modules/yargs-parser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", - "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true } } } diff --git a/src/Admin/package.json b/src/Admin/package.json index 871371fb6..667cd367e 100644 --- a/src/Admin/package.json +++ b/src/Admin/package.json @@ -5,18 +5,22 @@ "repository": "https://github.com/bitwarden/server", "license": "GPL-3.0", "scripts": { - "build": "gulp build" + "build": "webpack" + }, + "dependencies": { + "bootstrap": "4.6.2", + "font-awesome": "4.7.0", + "jquery": "3.7.1", + "popper.js": "1.16.1", + "toastr": "2.1.4" }, "devDependencies": { - "bootstrap": "4.6.2", - "del": "6.1.1", - "font-awesome": "4.7.0", - "gulp": "4.0.2", - "gulp-sass": "5.1.0", - "jquery": "3.7.1", - "merge-stream": "2.0.0", - "popper.js": "1.16.1", + "css-loader": "7.1.2", + "expose-loader": "5.0.0", + "mini-css-extract-plugin": "2.9.0", "sass": "1.75.0", - "toastr": "2.1.4" + "sass-loader": "16.0.0", + "webpack": "5.93.0", + "webpack-cli": "5.1.4" } } diff --git a/src/Admin/webpack.config.js b/src/Admin/webpack.config.js new file mode 100644 index 000000000..32b0b4984 --- /dev/null +++ b/src/Admin/webpack.config.js @@ -0,0 +1,66 @@ +const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + +const paths = { + assets: "./wwwroot/assets/", + sassDir: "./Sass/", +}; + +/** @type {import("webpack").Configuration} */ +module.exports = { + mode: "production", + devtool: "source-map", + entry: { + site: [ + path.resolve(__dirname, paths.sassDir, "site.scss"), + + "popper.js", + "bootstrap", + "jquery", + "font-awesome/css/font-awesome.css", + "toastr", + "toastr/build/toastr.css", + ], + }, + output: { + clean: true, + path: path.resolve(__dirname, paths.assets), + }, + module: { + rules: [ + { + test: /\.(sa|sc|c)ss$/, + use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], + }, + { + test: /.(ttf|otf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, + exclude: /loading(|-white).svg/, + generator: { + filename: "fonts/[name].[contenthash][ext]", + }, + type: "asset/resource", + }, + + // Expose jquery and toastr globally so they can be used directly in asp.net + { + test: require.resolve("jquery"), + loader: "expose-loader", + options: { + exposes: ["$", "jQuery"], + }, + }, + { + test: require.resolve("toastr"), + loader: "expose-loader", + options: { + exposes: ["toastr"], + }, + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + }), + ], +}; diff --git a/src/Api/AdminConsole/Controllers/GroupsController.cs b/src/Api/AdminConsole/Controllers/GroupsController.cs index 974969158..b314155be 100644 --- a/src/Api/AdminConsole/Controllers/GroupsController.cs +++ b/src/Api/AdminConsole/Controllers/GroupsController.cs @@ -4,7 +4,6 @@ using Bit.Api.Models.Response; using Bit.Api.Utilities; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Api.Vault.AuthorizationHandlers.Groups; -using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; @@ -126,8 +125,8 @@ public class GroupsController : Controller throw new NotFoundException(); } - // Flexible Collections - check the user has permission to grant access to the collections for the new group - if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) && model.Collections?.Any() == true) + // Check the user has permission to grant access to the collections for the new group + if (model.Collections?.Any() == true) { var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Collections.Select(a => a.Id)); var authorized = @@ -135,7 +134,7 @@ public class GroupsController : Controller .Succeeded; if (!authorized) { - throw new NotFoundException("You are not authorized to grant access to these collections."); + throw new NotFoundException(); } } @@ -150,38 +149,20 @@ public class GroupsController : Controller [HttpPost("{id}")] public async Task Put(Guid orgId, Guid id, [FromBody] GroupRequestModel model) { - if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1)) - { - // Use new Flexible Collections v1 logic - return await Put_vNext(orgId, id, model); - } - - // Pre-Flexible Collections v1 logic follows - var group = await _groupRepository.GetByIdAsync(id); - if (group == null || !await _currentContext.ManageGroups(group.OrganizationId)) + if (!await _currentContext.ManageGroups(orgId)) { throw new NotFoundException(); } - var organization = await _organizationRepository.GetByIdAsync(orgId); - - await _updateGroupCommand.UpdateGroupAsync(model.ToGroup(group), organization, - model.Collections.Select(c => c.ToSelectionReadOnly()).ToList(), model.Users); - return new GroupResponseModel(group); - } - - /// - /// Put logic for Flexible Collections v1 - /// - private async Task Put_vNext(Guid orgId, Guid id, [FromBody] GroupRequestModel model) - { var (group, currentAccess) = await _groupRepository.GetByIdWithCollectionsAsync(id); - if (group == null || !await _currentContext.ManageGroups(group.OrganizationId)) + if (group == null || group.OrganizationId != orgId) { throw new NotFoundException(); } - // Check whether the user is permitted to add themselves to the group + // Authorization check: + // If admins are not allowed access to all collections, you cannot add yourself to a group. + // No error is thrown for this, we just don't update groups. var orgAbility = await _applicationCacheService.GetOrganizationAbilityAsync(orgId); if (!orgAbility.AllowAdminAccessToAllCollectionItems) { @@ -195,9 +176,23 @@ public class GroupsController : Controller } } + // Authorization check: + // You must have authorization to ModifyUserAccess for all collections being saved + var postedCollections = await _collectionRepository + .GetManyByManyIdsAsync(model.Collections.Select(c => c.Id)); + foreach (var collection in postedCollections) + { + if (!(await _authorizationService.AuthorizeAsync(User, collection, + BulkCollectionOperations.ModifyGroupAccess)) + .Succeeded) + { + throw new NotFoundException(); + } + } + // The client only sends collections that the saving user has permissions to edit. - // On the server side, we need to (1) confirm this and (2) concat these with the collections that the user - // can't edit before saving to the database. + // We need to combine these with collections that the user doesn't have permissions for, so that we don't + // accidentally overwrite those var currentCollections = await _collectionRepository .GetManyByManyIdsAsync(currentAccess.Select(cas => cas.Id)); @@ -211,11 +206,6 @@ public class GroupsController : Controller } } - if (model.Collections.Any(c => readonlyCollectionIds.Contains(c.Id))) - { - throw new BadRequestException("You must have Can Manage permissions to edit a collection's membership"); - } - var editedCollectionAccess = model.Collections .Select(c => c.ToSelectionReadOnly()); var readonlyCollectionAccess = currentAccess diff --git a/src/Api/AdminConsole/Controllers/OrganizationAuthRequestsController.cs b/src/Api/AdminConsole/Controllers/OrganizationAuthRequestsController.cs index dbb73f870..ea3de5a38 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationAuthRequestsController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationAuthRequestsController.cs @@ -1,14 +1,12 @@ using Bit.Api.AdminConsole.Models.Request; using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Models.Response; -using Bit.Core; using Bit.Core.AdminConsole.OrganizationAuth.Interfaces; using Bit.Core.Auth.Models.Api.Request.AuthRequest; using Bit.Core.Auth.Services; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; -using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -75,7 +73,6 @@ public class OrganizationAuthRequestsController : Controller } } - [RequireFeature(FeatureFlagKeys.BulkDeviceApproval)] [HttpPost("")] public async Task UpdateManyAuthRequests(Guid orgId, [FromBody] IEnumerable model) { diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index fd8149454..69d941a74 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -12,6 +12,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -22,7 +23,6 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -49,6 +49,7 @@ public class OrganizationUsersController : Controller private readonly IApplicationCacheService _applicationCacheService; private readonly IFeatureService _featureService; private readonly ISsoConfigRepository _ssoConfigRepository; + private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; public OrganizationUsersController( IOrganizationRepository organizationRepository, @@ -67,7 +68,8 @@ public class OrganizationUsersController : Controller IAuthorizationService authorizationService, IApplicationCacheService applicationCacheService, IFeatureService featureService, - ISsoConfigRepository ssoConfigRepository) + ISsoConfigRepository ssoConfigRepository, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -86,6 +88,7 @@ public class OrganizationUsersController : Controller _applicationCacheService = applicationCacheService; _featureService = featureService; _ssoConfigRepository = ssoConfigRepository; + _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; } [HttpGet("{id}")] @@ -127,8 +130,12 @@ public class OrganizationUsersController : Controller throw new NotFoundException(); } - var organizationUsers = await _organizationUserRepository - .GetManyDetailsByOrganizationAsync(orgId, includeGroups, includeCollections); + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + return await Get_vNext(orgId, includeGroups, includeCollections); + } + + var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgId, includeGroups, includeCollections); var responseTasks = organizationUsers .Select(async o => { @@ -201,7 +208,6 @@ public class OrganizationUsersController : Controller return new OrganizationUserResetPasswordDetailsResponseModel(new OrganizationUserResetPasswordDetails(organizationUser, user, org)); } - [RequireFeature(FeatureFlagKeys.BulkDeviceApproval)] [HttpPost("account-recovery-details")] public async Task> GetAccountRecoveryDetails(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model) { @@ -223,8 +229,8 @@ public class OrganizationUsersController : Controller throw new NotFoundException(); } - // Flexible Collections - check the user has permission to grant access to the collections for the new user - if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) && model.Collections?.Any() == true) + // Check the user has permission to grant access to the collections for the new user + if (model.Collections?.Any() == true) { var collections = await _collectionRepository.GetManyByManyIdsAsync(model.Collections.Select(a => a.Id)); var authorized = @@ -232,7 +238,7 @@ public class OrganizationUsersController : Controller .Succeeded; if (!authorized) { - throw new NotFoundException("You are not authorized to grant access to these collections."); + throw new NotFoundException(); } } @@ -334,7 +340,9 @@ public class OrganizationUsersController : Controller } var userId = _userService.GetProperUserId(User); - var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value, + var results = _featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization) + ? await _organizationService.ConfirmUsersAsync_vNext(orgGuidId, model.ToDictionary(), userId.Value) + : await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value, _userService); return new ListResponseModel(results.Select(r => @@ -358,35 +366,6 @@ public class OrganizationUsersController : Controller [HttpPut("{id}")] [HttpPost("{id}")] public async Task Put(Guid orgId, Guid id, [FromBody] OrganizationUserUpdateRequestModel model) - { - if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1)) - { - // Use new Flexible Collections v1 logic - await Put_vNext(orgId, id, model); - return; - } - - // Pre-Flexible Collections v1 code follows - if (!await _currentContext.ManageUsers(orgId)) - { - throw new NotFoundException(); - } - - var organizationUser = await _organizationUserRepository.GetByIdAsync(id); - if (organizationUser == null || organizationUser.OrganizationId != orgId) - { - throw new NotFoundException(); - } - - var userId = _userService.GetProperUserId(User); - await _updateOrganizationUserCommand.UpdateUserAsync(model.ToOrganizationUser(organizationUser), userId.Value, - model.Collections.Select(c => c.ToSelectionReadOnly()).ToList(), model.Groups); - } - - /// - /// Put logic for Flexible Collections v1 - /// - private async Task Put_vNext(Guid orgId, Guid id, [FromBody] OrganizationUserUpdateRequestModel model) { if (!await _currentContext.ManageUsers(orgId)) { @@ -402,13 +381,15 @@ public class OrganizationUsersController : Controller var userId = _userService.GetProperUserId(User).Value; var editingSelf = userId == organizationUser.UserId; + // Authorization check: // If admins are not allowed access to all collections, you cannot add yourself to a group. - // In this case we just don't update groups. + // No error is thrown for this, we just don't update groups. var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(orgId); var groupsToSave = editingSelf && !organizationAbility.AllowAdminAccessToAllCollectionItems ? null : model.Groups; + // Authorization check: // If admins are not allowed access to all collections, you cannot add yourself to collections. // This is not caught by the requirement below that you can ModifyUserAccess and must be checked separately var currentAccessIds = currentAccess.Select(c => c.Id).ToHashSet(); @@ -419,9 +400,23 @@ public class OrganizationUsersController : Controller throw new BadRequestException("You cannot add yourself to a collection."); } + // Authorization check: + // You must have authorization to ModifyUserAccess for all collections being saved + var postedCollections = await _collectionRepository + .GetManyByManyIdsAsync(model.Collections.Select(c => c.Id)); + foreach (var collection in postedCollections) + { + if (!(await _authorizationService.AuthorizeAsync(User, collection, + BulkCollectionOperations.ModifyUserAccess)) + .Succeeded) + { + throw new NotFoundException(); + } + } + // The client only sends collections that the saving user has permissions to edit. - // On the server side, we need to (1) make sure the user has permissions for these collections, and - // (2) concat these with the collections that the user can't edit before saving to the database. + // We need to combine these with collections that the user doesn't have permissions for, so that we don't + // accidentally overwrite those var currentCollections = await _collectionRepository .GetManyByManyIdsAsync(currentAccess.Select(cas => cas.Id)); @@ -435,11 +430,6 @@ public class OrganizationUsersController : Controller } } - if (model.Collections.Any(c => readonlyCollectionIds.Contains(c.Id))) - { - throw new BadRequestException("You must have Can Manage permissions to edit a collection's membership"); - } - var editedCollectionAccess = model.Collections .Select(c => c.ToSelectionReadOnly()); var readonlyCollectionAccess = currentAccess @@ -672,4 +662,32 @@ public class OrganizationUsersController : Controller return type; } + + private async Task> Get_vNext(Guid orgId, + bool includeGroups = false, bool includeCollections = false) + { + var organizationUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(orgId, includeGroups, includeCollections); + var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(organizationUsers); + var responseTasks = organizationUsers + .Select(async o => + { + var userTwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == o.Id).twoFactorIsEnabled; + var orgUser = new OrganizationUserUserDetailsResponseModel(o, userTwoFactorEnabled); + + // Downgrade Custom users with no other permissions than 'Edit/Delete Assigned Collections' to User + orgUser.Type = GetFlexibleCollectionsUserType(orgUser.Type, orgUser.Permissions); + + // Set 'Edit/Delete Assigned Collections' custom permissions to false + if (orgUser.Permissions is not null) + { + orgUser.Permissions.EditAssignedCollections = false; + orgUser.Permissions.DeleteAssignedCollections = false; + } + + return orgUser; + }); + var responses = await Task.WhenAll(responseTasks); + + return new ListResponseModel(responses); + } } diff --git a/src/Api/AdminConsole/Controllers/OrganizationsController.cs b/src/Api/AdminConsole/Controllers/OrganizationsController.cs index 0fdc03eed..e3af14e19 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationsController.cs @@ -539,14 +539,6 @@ public class OrganizationsController : Controller throw new NotFoundException(); } - var v1Enabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - - if (!v1Enabled) - { - // V1 is disabled, ensure V1 setting doesn't change - model.AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; - } - await _organizationService.UpdateAsync(model.ToOrganization(organization), eventType: EventType.Organization_CollectionManagement_Updated); return new OrganizationResponseModel(organization); } diff --git a/src/Api/AdminConsole/Controllers/ProvidersController.cs b/src/Api/AdminConsole/Controllers/ProvidersController.cs index 51cf4c7e3..be119744b 100644 --- a/src/Api/AdminConsole/Controllers/ProvidersController.cs +++ b/src/Api/AdminConsole/Controllers/ProvidersController.cs @@ -1,9 +1,7 @@ using Bit.Api.AdminConsole.Models.Request.Providers; using Bit.Api.AdminConsole.Models.Response.Providers; -using Bit.Core; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; -using Bit.Core.Billing.Services; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Business; @@ -23,23 +21,15 @@ public class ProvidersController : Controller private readonly IProviderService _providerService; private readonly ICurrentContext _currentContext; private readonly GlobalSettings _globalSettings; - private readonly IFeatureService _featureService; - private readonly ILogger _logger; - private readonly IProviderBillingService _providerBillingService; public ProvidersController(IUserService userService, IProviderRepository providerRepository, - IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings, - IFeatureService featureService, ILogger logger, - IProviderBillingService providerBillingService) + IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings) { _userService = userService; _providerRepository = providerRepository; _providerService = providerService; _currentContext = currentContext; _globalSettings = globalSettings; - _featureService = featureService; - _logger = logger; - _providerBillingService = providerBillingService; } [HttpGet("{id:guid}")] @@ -94,12 +84,8 @@ public class ProvidersController : Controller var userId = _userService.GetProperUserId(User).Value; - var response = - await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key); - - if (_featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)) - { - var taxInfo = new TaxInfo + var taxInfo = model.TaxInfo != null + ? new TaxInfo { BillingAddressCountry = model.TaxInfo.Country, BillingAddressPostalCode = model.TaxInfo.PostalCode, @@ -108,20 +94,12 @@ public class ProvidersController : Controller BillingAddressLine2 = model.TaxInfo.Line2, BillingAddressCity = model.TaxInfo.City, BillingAddressState = model.TaxInfo.State - }; - - try - { - await _providerBillingService.CreateCustomer(provider, taxInfo); - - await _providerBillingService.StartSubscription(provider); } - catch - { - // We don't want to trap the user on the setup page, so we'll let this go through but the provider will be in an un-billable state. - _logger.LogError("Failed to create subscription for provider with ID {ID} during setup", provider.Id); - } - } + : null; + + var response = + await _providerService.CompleteSetupAsync(model.ToProvider(provider), userId, model.Token, model.Key, + taxInfo); return new ProviderResponseModel(response); } diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs index 40aa62c9d..bbbb571f4 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs @@ -14,6 +14,7 @@ public class OrganizationUserInviteRequestModel [StrictEmailAddressList] public IEnumerable Emails { get; set; } [Required] + [EnumDataType(typeof(OrganizationUserType))] public OrganizationUserType? Type { get; set; } public bool AccessSecretsManager { get; set; } public Permissions Permissions { get; set; } @@ -83,6 +84,7 @@ public class OrganizationUserBulkConfirmRequestModel public class OrganizationUserUpdateRequestModel { [Required] + [EnumDataType(typeof(OrganizationUserType))] public OrganizationUserType? Type { get; set; } public bool AccessSecretsManager { get; set; } public Permissions Permissions { get; set; } diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs index 297ae247f..08b4e4b06 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs @@ -57,7 +57,6 @@ public class OrganizationResponseModel : ResponseModel MaxAutoscaleSmServiceAccounts = organization.MaxAutoscaleSmServiceAccounts; LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; - FlexibleCollections = organization.FlexibleCollections; } public Guid Id { get; set; } @@ -101,7 +100,6 @@ public class OrganizationResponseModel : ResponseModel public int? MaxAutoscaleSmServiceAccounts { get; set; } public bool LimitCollectionCreationDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } - public bool FlexibleCollections { get; set; } } public class OrganizationSubscriptionResponseModel : OrganizationResponseModel diff --git a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs index ae7f2cdff..65b7a38a8 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs @@ -64,7 +64,6 @@ public class ProfileOrganizationResponseModel : ResponseModel AccessSecretsManager = organization.AccessSecretsManager; LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; - FlexibleCollections = organization.FlexibleCollections; if (organization.SsoConfig != null) { @@ -73,39 +72,36 @@ public class ProfileOrganizationResponseModel : ResponseModel KeyConnectorUrl = ssoConfigData.KeyConnectorUrl; } - if (FlexibleCollections) + // Downgrade Custom users with no other permissions than 'Edit/Delete Assigned Collections' to User + if (Type == OrganizationUserType.Custom && Permissions is not null) { - // Downgrade Custom users with no other permissions than 'Edit/Delete Assigned Collections' to User - if (Type == OrganizationUserType.Custom && Permissions is not null) - { - if ((Permissions.EditAssignedCollections || Permissions.DeleteAssignedCollections) && - Permissions is - { - AccessEventLogs: false, - AccessImportExport: false, - AccessReports: false, - CreateNewCollections: false, - EditAnyCollection: false, - DeleteAnyCollection: false, - ManageGroups: false, - ManagePolicies: false, - ManageSso: false, - ManageUsers: false, - ManageResetPassword: false, - ManageScim: false - }) + if ((Permissions.EditAssignedCollections || Permissions.DeleteAssignedCollections) && + Permissions is { - organization.Type = OrganizationUserType.User; - } - } - - // Set 'Edit/Delete Assigned Collections' custom permissions to false - if (Permissions is not null) + AccessEventLogs: false, + AccessImportExport: false, + AccessReports: false, + CreateNewCollections: false, + EditAnyCollection: false, + DeleteAnyCollection: false, + ManageGroups: false, + ManagePolicies: false, + ManageSso: false, + ManageUsers: false, + ManageResetPassword: false, + ManageScim: false + }) { - Permissions.EditAssignedCollections = false; - Permissions.DeleteAssignedCollections = false; + organization.Type = OrganizationUserType.User; } } + + // Set 'Edit/Delete Assigned Collections' custom permissions to false + if (Permissions is not null) + { + Permissions.EditAssignedCollections = false; + Permissions.DeleteAssignedCollections = false; + } } public Guid Id { get; set; } @@ -157,5 +153,4 @@ public class ProfileOrganizationResponseModel : ResponseModel public bool AccessSecretsManager { get; set; } public bool LimitCollectionCreationDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } - public bool FlexibleCollections { get; set; } } diff --git a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs index a7dbd0209..46819f886 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -46,6 +46,5 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier; LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; - FlexibleCollections = organization.FlexibleCollections; } } diff --git a/src/Api/AdminConsole/Public/Controllers/MembersController.cs b/src/Api/AdminConsole/Public/Controllers/MembersController.cs index 1ecec686a..53ae317f6 100644 --- a/src/Api/AdminConsole/Public/Controllers/MembersController.cs +++ b/src/Api/AdminConsole/Public/Controllers/MembersController.cs @@ -2,9 +2,12 @@ using Bit.Api.AdminConsole.Public.Models.Request; using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; +using Bit.Core; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Context; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; @@ -24,6 +27,10 @@ public class MembersController : Controller private readonly IUpdateOrganizationUserCommand _updateOrganizationUserCommand; private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand; private readonly IApplicationCacheService _applicationCacheService; + private readonly IPaymentService _paymentService; + private readonly IOrganizationRepository _organizationRepository; + private readonly IFeatureService _featureService; + private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; public MembersController( IOrganizationUserRepository organizationUserRepository, @@ -33,7 +40,11 @@ public class MembersController : Controller ICurrentContext currentContext, IUpdateOrganizationUserCommand updateOrganizationUserCommand, IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand, - IApplicationCacheService applicationCacheService) + IApplicationCacheService applicationCacheService, + IPaymentService paymentService, + IOrganizationRepository organizationRepository, + IFeatureService featureService, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) { _organizationUserRepository = organizationUserRepository; _groupRepository = groupRepository; @@ -43,6 +54,10 @@ public class MembersController : Controller _updateOrganizationUserCommand = updateOrganizationUserCommand; _updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand; _applicationCacheService = applicationCacheService; + _paymentService = paymentService; + _organizationRepository = organizationRepository; + _featureService = featureService; + _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; } /// @@ -102,11 +117,18 @@ public class MembersController : Controller [ProducesResponseType(typeof(ListResponseModel), (int)HttpStatusCode.OK)] public async Task List() { - var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync( - _currentContext.OrganizationId.Value); + var organizationUserUserDetails = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_currentContext.OrganizationId.Value); // TODO: Get all CollectionUser associations for the organization and marry them up here for the response. - var memberResponsesTasks = users.Select(async u => new MemberResponseModel(u, - await _userService.TwoFactorIsEnabledAsync(u), null)); + + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + return await List_vNext(organizationUserUserDetails); + } + + var memberResponsesTasks = organizationUserUserDetails.Select(async u => + { + return new MemberResponseModel(u, await _userService.TwoFactorIsEnabledAsync(u), null); + }); var memberResponses = await Task.WhenAll(memberResponsesTasks); var response = new ListResponseModel(memberResponses); return new JsonResult(response); @@ -124,8 +146,19 @@ public class MembersController : Controller [ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)] public async Task Post([FromBody] MemberCreateRequestModel model) { + var hasStandaloneSecretsManager = false; + + var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId!.Value); + + if (organization != null) + { + hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization); + } + var invite = model.ToOrganizationUserInvite(); + invite.AccessSecretsManager = hasStandaloneSecretsManager; + var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null, systemUser: null, invite, model.ExternalId); var response = new MemberResponseModel(user, invite.Collections); @@ -235,4 +268,15 @@ public class MembersController : Controller await _organizationService.ResendInviteAsync(_currentContext.OrganizationId.Value, null, id); return new OkResult(); } + + private async Task List_vNext(ICollection organizationUserUserDetails) + { + var orgUsersTwoFactorIsEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(organizationUserUserDetails); + var memberResponses = organizationUserUserDetails.Select(u => + { + return new MemberResponseModel(u, orgUsersTwoFactorIsEnabled.FirstOrDefault(tuple => tuple.user == u).twoFactorIsEnabled, null); + }); + var response = new ListResponseModel(memberResponses); + return new JsonResult(response); + } } diff --git a/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs b/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs index 58f644409..79ec0ad78 100644 --- a/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs +++ b/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs @@ -45,10 +45,10 @@ public abstract class MemberBaseModel } /// - /// The member's type (or role) within the organization. If your organization has is using the latest collection enhancements, - /// you will not be allowed to assign the Manager role (OrganizationUserType = 3). + /// The member's type (or role) within the organization. /// [Required] + [EnumDataType(typeof(OrganizationUserType))] public OrganizationUserType? Type { get; set; } /// /// External identifier for reference or linking this member to another system, such as a user directory. diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index d8b2ab475..aae6582c4 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -34,7 +34,7 @@ - + diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index bb7a3d0a6..3370b8939 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -17,6 +17,7 @@ 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.UserKey; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Billing.Models; @@ -53,15 +54,13 @@ public class AccountsController : Controller private readonly IUserService _userService; private readonly IPolicyService _policyService; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; + private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand; private readonly IRotateUserKeyCommand _rotateUserKeyCommand; private readonly IFeatureService _featureService; private readonly ISubscriberService _subscriberService; private readonly IReferenceEventService _referenceEventService; private readonly ICurrentContext _currentContext; - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); - private readonly IRotationValidator, IEnumerable> _cipherValidator; private readonly IRotationValidator, IEnumerable> _folderValidator; private readonly IRotationValidator, IReadOnlyList> _sendValidator; @@ -83,6 +82,7 @@ public class AccountsController : Controller IUserService userService, IPolicyService policyService, ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand, + ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand, IRotateUserKeyCommand rotateUserKeyCommand, IFeatureService featureService, ISubscriberService subscriberService, @@ -106,6 +106,7 @@ public class AccountsController : Controller _userService = userService; _policyService = policyService; _setInitialMasterPasswordCommand = setInitialMasterPasswordCommand; + _tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand; _rotateUserKeyCommand = rotateUserKeyCommand; _featureService = featureService; _subscriberService = subscriberService; @@ -877,6 +878,29 @@ public class AccountsController : Controller throw new BadRequestException(ModelState); } + [HttpPut("update-tde-offboarding-password")] + public async Task PutUpdateTdePasswordAsync([FromBody] UpdateTdeOffboardingPasswordRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + var result = await _tdeOffboardingPasswordCommand.UpdateTdeOffboardingPasswordAsync(user, model.NewMasterPasswordHash, model.Key, model.MasterPasswordHint); + if (result.Succeeded) + { + return; + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + throw new BadRequestException(ModelState); + } + [HttpPost("request-otp")] public async Task PostRequestOTP() { diff --git a/src/Api/Auth/Controllers/TwoFactorController.cs b/src/Api/Auth/Controllers/TwoFactorController.cs index 1062ec4ac..0a50f9bc2 100644 --- a/src/Api/Auth/Controllers/TwoFactorController.cs +++ b/src/Api/Auth/Controllers/TwoFactorController.cs @@ -3,6 +3,7 @@ using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Models.Response.TwoFactor; using Bit.Api.Models.Request; using Bit.Api.Models.Response; +using Bit.Core; using Bit.Core.Auth.Enums; using Bit.Core.Auth.LoginFeatures.PasswordlessLogin.Interfaces; using Bit.Core.Auth.Models.Business.Tokenables; @@ -33,7 +34,10 @@ public class TwoFactorController : Controller private readonly UserManager _userManager; private readonly ICurrentContext _currentContext; private readonly IVerifyAuthRequestCommand _verifyAuthRequestCommand; - private readonly IDataProtectorTokenFactory _tokenDataFactory; + private readonly IFeatureService _featureService; + private readonly IDataProtectorTokenFactory _twoFactorAuthenticatorDataProtector; + private readonly IDataProtectorTokenFactory _ssoEmailTwoFactorSessionDataProtector; + private readonly bool _TwoFactorAuthenticatorTokenFeatureFlagEnabled; public TwoFactorController( IUserService userService, @@ -43,7 +47,9 @@ public class TwoFactorController : Controller UserManager userManager, ICurrentContext currentContext, IVerifyAuthRequestCommand verifyAuthRequestCommand, - IDataProtectorTokenFactory tokenDataFactory) + IFeatureService featureService, + IDataProtectorTokenFactory twoFactorAuthenticatorDataProtector, + IDataProtectorTokenFactory ssoEmailTwoFactorSessionDataProtector) { _userService = userService; _organizationRepository = organizationRepository; @@ -52,7 +58,10 @@ public class TwoFactorController : Controller _userManager = userManager; _currentContext = currentContext; _verifyAuthRequestCommand = verifyAuthRequestCommand; - _tokenDataFactory = tokenDataFactory; + _featureService = featureService; + _twoFactorAuthenticatorDataProtector = twoFactorAuthenticatorDataProtector; + _ssoEmailTwoFactorSessionDataProtector = ssoEmailTwoFactorSessionDataProtector; + _TwoFactorAuthenticatorTokenFeatureFlagEnabled = _featureService.IsEnabled(FeatureFlagKeys.AuthenticatorTwoFactorToken); } [HttpGet("")] @@ -93,8 +102,13 @@ public class TwoFactorController : Controller public async Task GetAuthenticator( [FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, false); + var user = _TwoFactorAuthenticatorTokenFeatureFlagEnabled ? await CheckAsync(model, false) : await CheckAsync(model, false, true); var response = new TwoFactorAuthenticatorResponseModel(user); + if (_TwoFactorAuthenticatorTokenFeatureFlagEnabled) + { + var tokenable = new TwoFactorAuthenticatorUserVerificationTokenable(user, response.Key); + response.UserVerificationToken = _twoFactorAuthenticatorDataProtector.Protect(tokenable); + } return response; } @@ -103,8 +117,21 @@ public class TwoFactorController : Controller public async Task PutAuthenticator( [FromBody] UpdateTwoFactorAuthenticatorRequestModel model) { - var user = await CheckAsync(model, false); - model.ToUser(user); + User user; + if (_TwoFactorAuthenticatorTokenFeatureFlagEnabled) + { + user = model.ToUser(await _userService.GetUserByPrincipalAsync(User)); + _twoFactorAuthenticatorDataProtector.TryUnprotect(model.UserVerificationToken, out var decryptedToken); + if (!decryptedToken.TokenIsValid(user, model.Key)) + { + throw new BadRequestException("UserVerificationToken", "User verification failed."); + } + } + else + { + user = await CheckAsync(model, false); + model.ToUser(user); // populates user obj with proper metadata for VerifyTwoFactorTokenAsync + } if (!await _userManager.VerifyTwoFactorTokenAsync(user, CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator), model.Token)) @@ -118,10 +145,26 @@ public class TwoFactorController : Controller return response; } + [RequireFeature(FeatureFlagKeys.AuthenticatorTwoFactorToken)] + [HttpDelete("authenticator")] + public async Task DisableAuthenticator( + [FromBody] TwoFactorAuthenticatorDisableRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + _twoFactorAuthenticatorDataProtector.TryUnprotect(model.UserVerificationToken, out var decryptedToken); + if (!decryptedToken.TokenIsValid(user, model.Key)) + { + throw new BadRequestException("UserVerificationToken", "User verification failed."); + } + + await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService); + return new TwoFactorProviderResponseModel(model.Type.Value, user); + } + [HttpPost("get-yubikey")] public async Task GetYubiKey([FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, true); + var user = await CheckAsync(model, true, true); var response = new TwoFactorYubiKeyResponseModel(user); return response; } @@ -147,7 +190,7 @@ public class TwoFactorController : Controller [HttpPost("get-duo")] public async Task GetDuo([FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, true); + var user = await CheckAsync(model, true, true); var response = new TwoFactorDuoResponseModel(user); return response; } @@ -187,7 +230,7 @@ public class TwoFactorController : Controller public async Task GetOrganizationDuo(string id, [FromBody] SecretVerificationRequestModel model) { - await CheckAsync(model, false); + await CheckAsync(model, false, true); var orgIdGuid = new Guid(id); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -244,7 +287,7 @@ public class TwoFactorController : Controller [HttpPost("get-webauthn")] public async Task GetWebAuthn([FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, false); + var user = await CheckAsync(model, false, true); var response = new TwoFactorWebAuthnResponseModel(user); return response; } @@ -253,7 +296,7 @@ public class TwoFactorController : Controller [ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly public async Task GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, false); + var user = await CheckAsync(model, false, true); var reg = await _userService.StartWebAuthnRegistrationAsync(user); return reg; } @@ -288,7 +331,7 @@ public class TwoFactorController : Controller [HttpPost("get-email")] public async Task GetEmail([FromBody] SecretVerificationRequestModel model) { - var user = await CheckAsync(model, false); + var user = await CheckAsync(model, false, true); var response = new TwoFactorEmailResponseModel(user); return response; } @@ -296,7 +339,7 @@ public class TwoFactorController : Controller [HttpPost("send-email")] public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model) { - var user = await CheckAsync(model, false); + var user = await CheckAsync(model, false, true); model.ToUser(user); await _userService.SendTwoFactorEmailAsync(user); } @@ -433,7 +476,8 @@ public class TwoFactorController : Controller return Task.FromResult(new DeviceVerificationResponseModel(false, false)); } - private async Task CheckAsync(SecretVerificationRequestModel model, bool premium) + private async Task CheckAsync(SecretVerificationRequestModel model, bool premium, + bool skipVerification = false) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -441,7 +485,7 @@ public class TwoFactorController : Controller throw new UnauthorizedAccessException(); } - if (!await _userService.VerifySecretAsync(user, model.Secret)) + if (!await _userService.VerifySecretAsync(user, model.Secret, skipVerification)) { await Task.Delay(2000); throw new BadRequestException(string.Empty, "User verification failed."); @@ -476,7 +520,7 @@ public class TwoFactorController : Controller private bool ValidateSsoEmail2FaToken(string ssoEmail2FaSessionToken, User user) { - return _tokenDataFactory.TryUnprotect(ssoEmail2FaSessionToken, out var decryptedToken) && + return _ssoEmailTwoFactorSessionDataProtector.TryUnprotect(ssoEmail2FaSessionToken, out var decryptedToken) && decryptedToken.Valid && decryptedToken.TokenIsValid(user); } diff --git a/src/Api/Auth/Models/Request/Accounts/UpdateTdeOffboardingPasswordRequestModel.cs b/src/Api/Auth/Models/Request/Accounts/UpdateTdeOffboardingPasswordRequestModel.cs new file mode 100644 index 000000000..e246a99c9 --- /dev/null +++ b/src/Api/Auth/Models/Request/Accounts/UpdateTdeOffboardingPasswordRequestModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Auth.Models.Request.Accounts; + +public class UpdateTdeOffboardingPasswordRequestModel +{ + [Required] + [StringLength(300)] + public string NewMasterPasswordHash { get; set; } + [Required] + public string Key { get; set; } + [StringLength(50)] + public string MasterPasswordHint { get; set; } +} diff --git a/src/Api/Auth/Models/Request/TwoFactorRequestModels.cs b/src/Api/Auth/Models/Request/TwoFactorRequestModels.cs index fc7129503..f2f01a237 100644 --- a/src/Api/Auth/Models/Request/TwoFactorRequestModels.cs +++ b/src/Api/Auth/Models/Request/TwoFactorRequestModels.cs @@ -17,7 +17,7 @@ public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationReques [Required] [StringLength(50)] public string Key { get; set; } - + public string UserVerificationToken { get; set; } public User ToUser(User existingUser) { var providers = existingUser.GetTwoFactorProviders(); @@ -323,3 +323,11 @@ public class TwoFactorRecoveryRequestModel : TwoFactorEmailRequestModel [StringLength(32)] public string RecoveryCode { get; set; } } + +public class TwoFactorAuthenticatorDisableRequestModel : TwoFactorProviderRequestModel +{ + [Required] + public string UserVerificationToken { get; set; } + [Required] + public string Key { get; set; } +} diff --git a/src/Api/Auth/Models/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs b/src/Api/Auth/Models/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs index a3fa1a8d2..f791c6fb1 100644 --- a/src/Api/Auth/Models/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs +++ b/src/Api/Auth/Models/Response/TwoFactor/TwoFactorAuthenticatorResponseModel.cs @@ -10,10 +10,7 @@ public class TwoFactorAuthenticatorResponseModel : ResponseModel public TwoFactorAuthenticatorResponseModel(User user) : base("twoFactorAuthenticator") { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } + ArgumentNullException.ThrowIfNull(user); var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); if (provider?.MetaData?.ContainsKey("Key") ?? false) @@ -31,4 +28,5 @@ public class TwoFactorAuthenticatorResponseModel : ResponseModel public bool Enabled { get; set; } public string Key { get; set; } + public string UserVerificationToken { get; set; } } diff --git a/src/Api/Auth/Models/Response/TwoFactor/TwoFactorDuoResponseModel.cs b/src/Api/Auth/Models/Response/TwoFactor/TwoFactorDuoResponseModel.cs index 2aaebf989..8b8c36d2e 100644 --- a/src/Api/Auth/Models/Response/TwoFactor/TwoFactorDuoResponseModel.cs +++ b/src/Api/Auth/Models/Response/TwoFactor/TwoFactorDuoResponseModel.cs @@ -59,8 +59,8 @@ public class TwoFactorDuoResponseModel : ResponseModel // check Skey and IKey first if they exist if (provider.MetaData.TryGetValue("SKey", out var sKey)) { - ClientSecret = (string)sKey; - SecretKey = (string)sKey; + ClientSecret = MaskKey((string)sKey); + SecretKey = MaskKey((string)sKey); } if (provider.MetaData.TryGetValue("IKey", out var iKey)) { @@ -73,8 +73,8 @@ public class TwoFactorDuoResponseModel : ResponseModel { if (!string.IsNullOrWhiteSpace((string)clientSecret)) { - ClientSecret = (string)clientSecret; - SecretKey = (string)clientSecret; + ClientSecret = MaskKey((string)clientSecret); + SecretKey = MaskKey((string)clientSecret); } } if (provider.MetaData.TryGetValue("ClientId", out var clientId)) @@ -114,4 +114,15 @@ public class TwoFactorDuoResponseModel : ResponseModel throw new InvalidDataException("Invalid Duo parameters."); } } + + private static string MaskKey(string key) + { + if (string.IsNullOrWhiteSpace(key) || key.Length <= 6) + { + return key; + } + + // Mask all but the first 6 characters. + return string.Concat(key.AsSpan(0, 6), new string('*', key.Length - 6)); + } } diff --git a/src/Api/Billing/Controllers/BaseProviderController.cs b/src/Api/Billing/Controllers/BaseProviderController.cs index 24fdf4864..37d804498 100644 --- a/src/Api/Billing/Controllers/BaseProviderController.cs +++ b/src/Api/Billing/Controllers/BaseProviderController.cs @@ -3,7 +3,9 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Extensions; using Bit.Core.Context; +using Bit.Core.Models.Api; using Bit.Core.Services; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.Billing.Controllers; @@ -11,8 +13,25 @@ namespace Bit.Api.Billing.Controllers; public abstract class BaseProviderController( ICurrentContext currentContext, IFeatureService featureService, - IProviderRepository providerRepository) : Controller + ILogger logger, + IProviderRepository providerRepository, + IUserService userService) : Controller { + protected readonly IUserService UserService = userService; + + protected static NotFound NotFoundResponse() => + TypedResults.NotFound(new ErrorResponseModel("Resource not found.")); + + protected static JsonHttpResult ServerErrorResponse(string errorMessage) => + TypedResults.Json( + new ErrorResponseModel(errorMessage), + statusCode: StatusCodes.Status500InternalServerError); + + protected static JsonHttpResult UnauthorizedResponse() => + TypedResults.Json( + new ErrorResponseModel("Unauthorized."), + statusCode: StatusCodes.Status401Unauthorized); + protected Task<(Provider, IResult)> TryGetBillableProviderForAdminOperation( Guid providerId) => TryGetBillableProviderAsync(providerId, currentContext.ProviderProviderAdmin); @@ -25,26 +44,53 @@ public abstract class BaseProviderController( { if (!featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling)) { - return (null, TypedResults.NotFound()); + logger.LogError( + "Cannot run Consolidated Billing operation for provider ({ProviderID}) while feature flag is disabled", + providerId); + + return (null, NotFoundResponse()); } var provider = await providerRepository.GetByIdAsync(providerId); if (provider == null) { - return (null, TypedResults.NotFound()); + logger.LogError( + "Cannot find provider ({ProviderID}) for Consolidated Billing operation", + providerId); + + return (null, NotFoundResponse()); } if (!checkAuthorization(providerId)) { - return (null, TypedResults.Unauthorized()); + var user = await UserService.GetUserByPrincipalAsync(User); + + logger.LogError( + "User ({UserID}) is not authorized to perform Consolidated Billing operation for provider ({ProviderID})", + user?.Id, providerId); + + return (null, UnauthorizedResponse()); } if (!provider.IsBillable()) { - return (null, TypedResults.Unauthorized()); + logger.LogError( + "Cannot run Consolidated Billing operation for provider ({ProviderID}) that is not billable", + providerId); + + return (null, UnauthorizedResponse()); } - return (provider, null); + if (provider.IsStripeEnabled()) + { + return (provider, null); + } + + logger.LogError( + "Cannot run Consolidated Billing operation for provider ({ProviderID}) that is missing Stripe configuration", + providerId); + + return (null, ServerErrorResponse("Something went wrong with your request. Please contact support.")); } } diff --git a/src/Api/Billing/Controllers/OrganizationBillingController.cs b/src/Api/Billing/Controllers/OrganizationBillingController.cs index 2f5b49356..47c4ef68f 100644 --- a/src/Api/Billing/Controllers/OrganizationBillingController.cs +++ b/src/Api/Billing/Controllers/OrganizationBillingController.cs @@ -20,6 +20,11 @@ public class OrganizationBillingController( [HttpGet("metadata")] public async Task GetMetadataAsync([FromRoute] Guid organizationId) { + if (!await currentContext.AccessMembersTab(organizationId)) + { + return TypedResults.Unauthorized(); + } + var metadata = await organizationBillingService.GetMetadata(organizationId); if (metadata == null) @@ -35,6 +40,11 @@ public class OrganizationBillingController( [HttpGet("history")] public async Task GetHistoryAsync([FromRoute] Guid organizationId) { + if (!await currentContext.ViewBillingHistory(organizationId)) + { + return TypedResults.Unauthorized(); + } + var organization = await organizationRepository.GetByIdAsync(organizationId); if (organization == null) diff --git a/src/Api/Billing/Controllers/OrganizationsController.cs b/src/Api/Billing/Controllers/OrganizationsController.cs index f3323ae80..d09246b18 100644 --- a/src/Api/Billing/Controllers/OrganizationsController.cs +++ b/src/Api/Billing/Controllers/OrganizationsController.cs @@ -162,13 +162,13 @@ public class OrganizationsController( [SelfHosted(NotSelfHostedOnly = true)] public async Task PostSmSubscription(Guid id, [FromBody] SecretsManagerSubscriptionUpdateRequestModel model) { - var organization = await organizationRepository.GetByIdAsync(id); - if (organization == null) + if (!await currentContext.EditSubscription(id)) { throw new NotFoundException(); } - if (!await currentContext.EditSubscription(id)) + var organization = await organizationRepository.GetByIdAsync(id); + if (organization == null) { throw new NotFoundException(); } @@ -195,13 +195,13 @@ public class OrganizationsController( [SelfHosted(NotSelfHostedOnly = true)] public async Task PostSubscribeSecretsManagerAsync(Guid id, [FromBody] SecretsManagerSubscribeRequestModel model) { - var organization = await organizationRepository.GetByIdAsync(id); - if (organization == null) + if (!await currentContext.EditSubscription(id)) { throw new NotFoundException(); } - if (!await currentContext.EditSubscription(id)) + var organization = await organizationRepository.GetByIdAsync(id); + if (organization == null) { throw new NotFoundException(); } diff --git a/src/Api/Billing/Controllers/ProviderBillingController.cs b/src/Api/Billing/Controllers/ProviderBillingController.cs index fda7eddd0..40a1ebdf2 100644 --- a/src/Api/Billing/Controllers/ProviderBillingController.cs +++ b/src/Api/Billing/Controllers/ProviderBillingController.cs @@ -1,15 +1,19 @@ using Bit.Api.Billing.Models.Requests; using Bit.Api.Billing.Models.Responses; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Billing.Constants; using Bit.Core.Billing.Models; +using Bit.Core.Billing.Repositories; using Bit.Core.Billing.Services; using Bit.Core.Context; +using Bit.Core.Models.Api; +using Bit.Core.Models.BitStripe; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Stripe; +using static Bit.Core.Billing.Utilities; + namespace Bit.Api.Billing.Controllers; [Route("providers/{providerId:guid}/billing")] @@ -17,10 +21,13 @@ namespace Bit.Api.Billing.Controllers; public class ProviderBillingController( ICurrentContext currentContext, IFeatureService featureService, + ILogger logger, IProviderBillingService providerBillingService, + IProviderPlanRepository providerPlanRepository, IProviderRepository providerRepository, + ISubscriberService subscriberService, IStripeAdapter stripeAdapter, - ISubscriberService subscriberService) : BaseProviderController(currentContext, featureService, providerRepository) + IUserService userService) : BaseProviderController(currentContext, featureService, logger, providerRepository, userService) { [HttpGet("invoices")] public async Task GetInvoicesAsync([FromRoute] Guid providerId) @@ -32,7 +39,10 @@ public class ProviderBillingController( return result; } - var invoices = await subscriberService.GetInvoices(provider); + var invoices = await stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions + { + Customer = provider.GatewayCustomerId + }); var response = InvoicesResponse.From(invoices); @@ -53,7 +63,7 @@ public class ProviderBillingController( if (reportContent == null) { - return TypedResults.NotFound(); + return ServerErrorResponse("We had a problem generating your invoice CSV. Please contact support."); } return TypedResults.File( @@ -61,95 +71,6 @@ public class ProviderBillingController( "text/csv"); } - [HttpGet("payment-information")] - public async Task GetPaymentInformationAsync([FromRoute] Guid providerId) - { - var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); - - if (provider == null) - { - return result; - } - - var paymentInformation = await subscriberService.GetPaymentInformation(provider); - - if (paymentInformation == null) - { - return TypedResults.NotFound(); - } - - var response = PaymentInformationResponse.From(paymentInformation); - - return TypedResults.Ok(response); - } - - [HttpGet("payment-method")] - public async Task GetPaymentMethodAsync([FromRoute] Guid providerId) - { - var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); - - if (provider == null) - { - return result; - } - - var maskedPaymentMethod = await subscriberService.GetPaymentMethod(provider); - - if (maskedPaymentMethod == null) - { - return TypedResults.NotFound(); - } - - var response = MaskedPaymentMethodResponse.From(maskedPaymentMethod); - - return TypedResults.Ok(response); - } - - [HttpPut("payment-method")] - public async Task UpdatePaymentMethodAsync( - [FromRoute] Guid providerId, - [FromBody] TokenizedPaymentMethodRequestBody requestBody) - { - var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); - - if (provider == null) - { - return result; - } - - var tokenizedPaymentMethod = new TokenizedPaymentMethodDTO( - requestBody.Type, - requestBody.Token); - - await subscriberService.UpdatePaymentMethod(provider, tokenizedPaymentMethod); - - await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, - new SubscriptionUpdateOptions - { - CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically - }); - - return TypedResults.Ok(); - } - - [HttpPost] - [Route("payment-method/verify-bank-account")] - public async Task VerifyBankAccountAsync( - [FromRoute] Guid providerId, - [FromBody] VerifyBankAccountRequestBody requestBody) - { - var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); - - if (provider == null) - { - return result; - } - - await subscriberService.VerifyBankAccount(provider, (requestBody.Amount1, requestBody.Amount2)); - - return TypedResults.Ok(); - } - [HttpGet("subscription")] public async Task GetSubscriptionAsync([FromRoute] Guid providerId) { @@ -160,36 +81,20 @@ public class ProviderBillingController( return result; } - var consolidatedBillingSubscription = await providerBillingService.GetConsolidatedBillingSubscription(provider); + var subscription = await stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, + new SubscriptionGetOptions { Expand = ["customer.tax_ids", "test_clock"] }); - if (consolidatedBillingSubscription == null) - { - return TypedResults.NotFound(); - } + var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id); - var response = ConsolidatedBillingSubscriptionResponse.From(consolidatedBillingSubscription); + var taxInformation = GetTaxInformation(subscription.Customer); - return TypedResults.Ok(response); - } + var subscriptionSuspension = await GetSubscriptionSuspensionAsync(stripeAdapter, subscription); - [HttpGet("tax-information")] - public async Task GetTaxInformationAsync([FromRoute] Guid providerId) - { - var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId); - - if (provider == null) - { - return result; - } - - var taxInformation = await subscriberService.GetTaxInformation(provider); - - if (taxInformation == null) - { - return TypedResults.NotFound(); - } - - var response = TaxInformationResponse.From(taxInformation); + var response = ProviderSubscriptionResponse.From( + subscription, + providerPlans, + taxInformation, + subscriptionSuspension); return TypedResults.Ok(response); } @@ -206,7 +111,13 @@ public class ProviderBillingController( return result; } - var taxInformation = new TaxInformationDTO( + if (requestBody is not { Country: not null, PostalCode: not null }) + { + return TypedResults.BadRequest( + new ErrorResponseModel("Country and postal code are required to update your tax information.")); + } + + var taxInformation = new TaxInformation( requestBody.Country, requestBody.PostalCode, requestBody.TaxId, diff --git a/src/Api/Billing/Controllers/ProviderClientsController.cs b/src/Api/Billing/Controllers/ProviderClientsController.cs index eaf5c054f..3fec4570f 100644 --- a/src/Api/Billing/Controllers/ProviderClientsController.cs +++ b/src/Api/Billing/Controllers/ProviderClientsController.cs @@ -15,13 +15,13 @@ namespace Bit.Api.Billing.Controllers; public class ProviderClientsController( ICurrentContext currentContext, IFeatureService featureService, - ILogger logger, + ILogger logger, IOrganizationRepository organizationRepository, IProviderBillingService providerBillingService, IProviderOrganizationRepository providerOrganizationRepository, IProviderRepository providerRepository, IProviderService providerService, - IUserService userService) : BaseProviderController(currentContext, featureService, providerRepository) + IUserService userService) : BaseProviderController(currentContext, featureService, logger, providerRepository, userService) { [HttpPost] public async Task CreateAsync( @@ -35,11 +35,11 @@ public class ProviderClientsController( return result; } - var user = await userService.GetUserByPrincipalAsync(User); + var user = await UserService.GetUserByPrincipalAsync(User); if (user == null) { - return TypedResults.Unauthorized(); + return UnauthorizedResponse(); } var organizationSignup = new OrganizationSignup @@ -63,13 +63,6 @@ public class ProviderClientsController( var clientOrganization = await organizationRepository.GetByIdAsync(providerOrganization.OrganizationId); - if (clientOrganization == null) - { - logger.LogError("Newly created client organization ({ID}) could not be found", providerOrganization.OrganizationId); - - return TypedResults.Problem(); - } - await providerBillingService.ScaleSeats( provider, requestBody.PlanType, @@ -103,18 +96,11 @@ public class ProviderClientsController( if (providerOrganization == null) { - return TypedResults.NotFound(); + return NotFoundResponse(); } var clientOrganization = await organizationRepository.GetByIdAsync(providerOrganization.OrganizationId); - if (clientOrganization == null) - { - logger.LogError("The client organization ({OrganizationID}) represented by provider organization ({ProviderOrganizationID}) could not be found.", providerOrganization.OrganizationId, providerOrganization.Id); - - return TypedResults.Problem(); - } - if (clientOrganization.Seats != requestBody.AssignedSeats) { await providerBillingService.AssignSeatsToClientOrganization( diff --git a/src/Api/Billing/Models/Responses/ConsolidatedBillingSubscriptionResponse.cs b/src/Api/Billing/Models/Responses/ConsolidatedBillingSubscriptionResponse.cs deleted file mode 100644 index 0e1656913..000000000 --- a/src/Api/Billing/Models/Responses/ConsolidatedBillingSubscriptionResponse.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Bit.Core.Billing.Models; -using Bit.Core.Utilities; - -namespace Bit.Api.Billing.Models.Responses; - -public record ConsolidatedBillingSubscriptionResponse( - string Status, - DateTime CurrentPeriodEndDate, - decimal? DiscountPercentage, - string CollectionMethod, - IEnumerable Plans, - long AccountCredit, - TaxInformationDTO TaxInformation, - DateTime? CancelAt, - SubscriptionSuspensionDTO Suspension) -{ - private const string _annualCadence = "Annual"; - private const string _monthlyCadence = "Monthly"; - - public static ConsolidatedBillingSubscriptionResponse From( - ConsolidatedBillingSubscriptionDTO consolidatedBillingSubscription) - { - var (providerPlans, subscription, taxInformation, suspension) = consolidatedBillingSubscription; - - var providerPlanResponses = providerPlans - .Select(providerPlan => - { - var plan = StaticStore.GetPlan(providerPlan.PlanType); - var cost = (providerPlan.SeatMinimum + providerPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice; - var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence; - return new ProviderPlanResponse( - plan.Name, - providerPlan.SeatMinimum, - providerPlan.PurchasedSeats, - providerPlan.AssignedSeats, - cost, - cadence); - }); - - return new ConsolidatedBillingSubscriptionResponse( - subscription.Status, - subscription.CurrentPeriodEnd, - subscription.Customer?.Discount?.Coupon?.PercentOff, - subscription.CollectionMethod, - providerPlanResponses, - subscription.Customer?.Balance ?? 0, - taxInformation, - subscription.CancelAt, - suspension); - } -} - -public record ProviderPlanResponse( - string PlanName, - int SeatMinimum, - int PurchasedSeats, - int AssignedSeats, - decimal Cost, - string Cadence); diff --git a/src/Api/Billing/Models/Responses/InvoicesResponse.cs b/src/Api/Billing/Models/Responses/InvoicesResponse.cs index 384b2fdd7..befbb4e53 100644 --- a/src/Api/Billing/Models/Responses/InvoicesResponse.cs +++ b/src/Api/Billing/Models/Responses/InvoicesResponse.cs @@ -3,16 +3,16 @@ namespace Bit.Api.Billing.Models.Responses; public record InvoicesResponse( - List Invoices) + List Invoices) { public static InvoicesResponse From(IEnumerable invoices) => new( invoices .Where(i => i.Status is "open" or "paid" or "uncollectible") .OrderByDescending(i => i.Created) - .Select(InvoiceDTO.From).ToList()); + .Select(InvoiceResponse.From).ToList()); } -public record InvoiceDTO( +public record InvoiceResponse( string Id, DateTime Date, string Number, @@ -21,7 +21,7 @@ public record InvoiceDTO( DateTime? DueDate, string Url) { - public static InvoiceDTO From(Invoice invoice) => new( + public static InvoiceResponse From(Invoice invoice) => new( invoice.Id, invoice.Created, invoice.Number, diff --git a/src/Api/Billing/Models/Responses/PaymentInformationResponse.cs b/src/Api/Billing/Models/Responses/PaymentInformationResponse.cs index 8e532d845..4ccb5889d 100644 --- a/src/Api/Billing/Models/Responses/PaymentInformationResponse.cs +++ b/src/Api/Billing/Models/Responses/PaymentInformationResponse.cs @@ -5,7 +5,7 @@ namespace Bit.Api.Billing.Models.Responses; public record PaymentInformationResponse( long AccountCredit, MaskedPaymentMethodDTO PaymentMethod, - TaxInformationDTO TaxInformation) + TaxInformation TaxInformation) { public static PaymentInformationResponse From(PaymentInformationDTO paymentInformation) => new( diff --git a/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs b/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs new file mode 100644 index 000000000..e9902f98b --- /dev/null +++ b/src/Api/Billing/Models/Responses/ProviderSubscriptionResponse.cs @@ -0,0 +1,66 @@ +using Bit.Core.Billing.Entities; +using Bit.Core.Billing.Models; +using Bit.Core.Utilities; +using Stripe; + +namespace Bit.Api.Billing.Models.Responses; + +public record ProviderSubscriptionResponse( + string Status, + DateTime CurrentPeriodEndDate, + decimal? DiscountPercentage, + string CollectionMethod, + IEnumerable Plans, + decimal AccountCredit, + TaxInformation TaxInformation, + DateTime? CancelAt, + SubscriptionSuspension Suspension) +{ + private const string _annualCadence = "Annual"; + private const string _monthlyCadence = "Monthly"; + + public static ProviderSubscriptionResponse From( + Subscription subscription, + ICollection providerPlans, + TaxInformation taxInformation, + SubscriptionSuspension subscriptionSuspension) + { + var providerPlanResponses = providerPlans + .Where(providerPlan => providerPlan.IsConfigured()) + .Select(ConfiguredProviderPlan.From) + .Select(configuredProviderPlan => + { + var plan = StaticStore.GetPlan(configuredProviderPlan.PlanType); + var cost = (configuredProviderPlan.SeatMinimum + configuredProviderPlan.PurchasedSeats) * plan.PasswordManager.ProviderPortalSeatPrice; + var cadence = plan.IsAnnual ? _annualCadence : _monthlyCadence; + return new ProviderPlanResponse( + plan.Name, + configuredProviderPlan.SeatMinimum, + configuredProviderPlan.PurchasedSeats, + configuredProviderPlan.AssignedSeats, + cost, + cadence); + }); + + var accountCredit = Convert.ToDecimal(subscription.Customer?.Balance) * -1 / 100; + + return new ProviderSubscriptionResponse( + subscription.Status, + subscription.CurrentPeriodEnd, + subscription.Customer?.Discount?.Coupon?.PercentOff, + subscription.CollectionMethod, + providerPlanResponses, + accountCredit, + taxInformation, + subscription.CancelAt, + subscriptionSuspension); + } +} + +public record ProviderPlanResponse( + string PlanName, + int SeatMinimum, + int PurchasedSeats, + int AssignedSeats, + decimal Cost, + string Cadence); diff --git a/src/Api/Billing/Models/Responses/TaxInformationResponse.cs b/src/Api/Billing/Models/Responses/TaxInformationResponse.cs index 53e2de19d..02349d74f 100644 --- a/src/Api/Billing/Models/Responses/TaxInformationResponse.cs +++ b/src/Api/Billing/Models/Responses/TaxInformationResponse.cs @@ -11,7 +11,7 @@ public record TaxInformationResponse( string City, string State) { - public static TaxInformationResponse From(TaxInformationDTO taxInformation) + public static TaxInformationResponse From(TaxInformation taxInformation) => new( taxInformation.Country, taxInformation.PostalCode, diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index b4b1681dd..37b4fe266 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -107,7 +107,7 @@ public class CollectionsController : Controller } else { - var assignedCollections = await _collectionRepository.GetManyByUserIdAsync(_currentContext.UserId.Value, false); + var assignedCollections = await _collectionRepository.GetManyByUserIdAsync(_currentContext.UserId.Value); orgCollections = assignedCollections.Where(c => c.OrganizationId == orgId && c.Manage).ToList(); } @@ -119,7 +119,7 @@ public class CollectionsController : Controller public async Task> GetUser() { var collections = await _collectionRepository.GetManyByUserIdAsync( - _userService.GetProperUserId(User).Value, false); + _userService.GetProperUserId(User).Value); var responses = collections.Select(c => new CollectionDetailsResponseModel(c)); return new ListResponseModel(responses); } diff --git a/src/Api/Controllers/DevicesController.cs b/src/Api/Controllers/DevicesController.cs index 46e312bc0..389d2c965 100644 --- a/src/Api/Controllers/DevicesController.cs +++ b/src/Api/Controllers/DevicesController.cs @@ -3,6 +3,7 @@ using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Models.Request; using Bit.Api.Models.Response; +using Bit.Core; using Bit.Core.Auth.Models.Api.Request; using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Context; @@ -25,19 +26,22 @@ public class DevicesController : Controller private readonly IUserService _userService; private readonly IUserRepository _userRepository; private readonly ICurrentContext _currentContext; + private readonly ILogger _logger; public DevicesController( IDeviceRepository deviceRepository, IDeviceService deviceService, IUserService userService, IUserRepository userRepository, - ICurrentContext currentContext) + ICurrentContext currentContext, + ILogger logger) { _deviceRepository = deviceRepository; _deviceService = deviceService; _userService = userService; _userRepository = userRepository; _currentContext = currentContext; + _logger = logger; } [HttpGet("{id}")] @@ -231,4 +235,25 @@ public class DevicesController : Controller var device = await _deviceRepository.GetByIdentifierAsync(identifier, user.Id); return device != null; } + + [RequireFeature(FeatureFlagKeys.DeviceTrustLogging)] + [HttpPost("lost-trust")] + public void PostLostTrust() + { + var userId = _currentContext.UserId.GetValueOrDefault(); + if (userId == default) + { + throw new UnauthorizedAccessException(); + } + + var deviceId = _currentContext.DeviceIdentifier; + if (deviceId == null) + { + throw new BadRequestException("Please provide a device identifier"); + } + + _logger.LogError("User {id} has a device key, but didn't receive decryption keys for device {device}", userId, + deviceId); + } + } diff --git a/src/Api/SecretsManager/Controllers/CountsController.cs b/src/Api/SecretsManager/Controllers/CountsController.cs new file mode 100644 index 000000000..a37708d9a --- /dev/null +++ b/src/Api/SecretsManager/Controllers/CountsController.cs @@ -0,0 +1,119 @@ +#nullable enable +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.SecretsManager.Controllers; + +[Authorize("secrets")] +public class CountsController : Controller +{ + private readonly ICurrentContext _currentContext; + private readonly IAccessClientQuery _accessClientQuery; + private readonly IProjectRepository _projectRepository; + private readonly ISecretRepository _secretRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; + + public CountsController( + ICurrentContext currentContext, + IAccessClientQuery accessClientQuery, + IProjectRepository projectRepository, + ISecretRepository secretRepository, + IServiceAccountRepository serviceAccountRepository) + { + _currentContext = currentContext; + _accessClientQuery = accessClientQuery; + _projectRepository = projectRepository; + _secretRepository = secretRepository; + _serviceAccountRepository = serviceAccountRepository; + } + + [HttpGet("organizations/{organizationId}/sm-counts")] + public async Task GetByOrganizationAsync([FromRoute] Guid organizationId) + { + var (accessType, userId) = await GetAccessClientAsync(organizationId); + + var projectsCountTask = _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId, + userId, accessType); + + var secretsCountTask = _secretRepository.GetSecretsCountByOrganizationIdAsync(organizationId, + userId, accessType); + + var serviceAccountsCountsTask = _serviceAccountRepository.GetServiceAccountCountByOrganizationIdAsync( + organizationId, userId, accessType); + + var counts = await Task.WhenAll(projectsCountTask, secretsCountTask, serviceAccountsCountsTask); + + return new OrganizationCountsResponseModel + { + Projects = counts[0], + Secrets = counts[1], + ServiceAccounts = counts[2] + }; + } + + + [HttpGet("projects/{projectId}/sm-counts")] + public async Task GetByProjectAsync([FromRoute] Guid projectId) + { + var project = await _projectRepository.GetByIdAsync(projectId); + if (project == null) + { + throw new NotFoundException(); + } + + var (accessType, userId) = await GetAccessClientAsync(project.OrganizationId); + + var projectsCounts = await _projectRepository.GetProjectCountsByIdAsync(projectId, userId, accessType); + + return new ProjectCountsResponseModel + { + Secrets = projectsCounts.Secrets, + People = projectsCounts.People, + ServiceAccounts = projectsCounts.ServiceAccounts + }; + } + + [HttpGet("service-accounts/{serviceAccountId}/sm-counts")] + public async Task GetByServiceAccountAsync([FromRoute] Guid serviceAccountId) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId); + if (serviceAccount == null) + { + throw new NotFoundException(); + } + + var (accessType, userId) = await GetAccessClientAsync(serviceAccount.OrganizationId); + + var serviceAccountCounts = + await _serviceAccountRepository.GetServiceAccountCountsByIdAsync(serviceAccountId, userId, accessType); + + return new ServiceAccountCountsResponseModel + { + Projects = serviceAccountCounts.Projects, + People = serviceAccountCounts.People, + AccessTokens = serviceAccountCounts.AccessTokens + }; + } + + private async Task<(AccessClientType, Guid)> GetAccessClientAsync(Guid organizationId) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + var (accessType, userId) = await _accessClientQuery.GetAccessClientAsync(User, organizationId); + if (accessType == AccessClientType.ServiceAccount) + { + throw new NotFoundException(); + } + + return (accessType, userId); + } +} diff --git a/src/Api/SecretsManager/Controllers/RequestSMAccessController.cs b/src/Api/SecretsManager/Controllers/RequestSMAccessController.cs new file mode 100644 index 000000000..c9b393bb2 --- /dev/null +++ b/src/Api/SecretsManager/Controllers/RequestSMAccessController.cs @@ -0,0 +1,55 @@ +using Bit.Api.SecretsManager.Models.Request; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Commands.Requests.Interfaces; +using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.SecretsManager.Controllers; + +[Route("request-access")] +[Authorize("Web")] +public class RequestSMAccessController : Controller +{ + private readonly IRequestSMAccessCommand _requestSMAccessCommand; + private readonly IUserService _userService; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ICurrentContext _currentContext; + + public RequestSMAccessController( + IRequestSMAccessCommand requestSMAccessCommand, IUserService userService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICurrentContext currentContext) + { + _requestSMAccessCommand = requestSMAccessCommand; + _userService = userService; + _organizationRepository = organizationRepository; + _organizationUserRepository = organizationUserRepository; + _currentContext = currentContext; + } + + [HttpPost("request-sm-access")] + public async Task RequestSMAccessFromAdmins([FromBody] RequestSMAccessRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + if (!await _currentContext.OrganizationUser(model.OrganizationId)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(model.OrganizationId); + if (organization == null) + { + throw new NotFoundException(); + } + + var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organization.Id); + await _requestSMAccessCommand.SendRequestAccessToSM(organization, orgUsers, user, model.EmailContent); + } +} diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 34c6a9723..8e93f3d79 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -260,25 +260,13 @@ public class SecretsController : Controller throw new NotFoundException(); } - // Ensure all secrets belong to the same organization. - var organizationId = secrets.First().OrganizationId; - if (secrets.Any(secret => secret.OrganizationId != organizationId) || - !_currentContext.AccessSecretsManager(organizationId)) + var authorizationResult = await _authorizationService.AuthorizeAsync(User, secrets, BulkSecretOperations.ReadAll); + if (!authorizationResult.Succeeded) { throw new NotFoundException(); } - - foreach (var secret in secrets) - { - var authorizationResult = await _authorizationService.AuthorizeAsync(User, secret, SecretOperations.Read); - if (!authorizationResult.Succeeded) - { - throw new NotFoundException(); - } - } - - await LogSecretsRetrievalAsync(organizationId, secrets); + await LogSecretsRetrievalAsync(secrets.First().OrganizationId, secrets); var responses = secrets.Select(s => new BaseSecretResponseModel(s)); return new ListResponseModel(responses); diff --git a/src/Api/SecretsManager/Models/Request/RequestSMAccessRequestModel.cs b/src/Api/SecretsManager/Models/Request/RequestSMAccessRequestModel.cs new file mode 100644 index 000000000..1f05bad93 --- /dev/null +++ b/src/Api/SecretsManager/Models/Request/RequestSMAccessRequestModel.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.SecretsManager.Models.Request; + +public class RequestSMAccessRequestModel +{ + [Required] + public Guid OrganizationId { get; set; } + [Required(ErrorMessage = "Add a note is a required field")] + public string EmailContent { get; set; } +} diff --git a/src/Api/SecretsManager/Models/Response/OrganizationCountsResponseModel.cs b/src/Api/SecretsManager/Models/Response/OrganizationCountsResponseModel.cs new file mode 100644 index 000000000..bb3f0013b --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/OrganizationCountsResponseModel.cs @@ -0,0 +1,15 @@ +#nullable enable +using Bit.Core.Models.Api; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class OrganizationCountsResponseModel() : ResponseModel(_objectName) +{ + private const string _objectName = "organizationCounts"; + + public int Projects { get; set; } + + public int Secrets { get; set; } + + public int ServiceAccounts { get; set; } +} diff --git a/src/Api/SecretsManager/Models/Response/ProjectCountsResponseModel.cs b/src/Api/SecretsManager/Models/Response/ProjectCountsResponseModel.cs new file mode 100644 index 000000000..df921a7b1 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/ProjectCountsResponseModel.cs @@ -0,0 +1,15 @@ +#nullable enable +using Bit.Core.Models.Api; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class ProjectCountsResponseModel() : ResponseModel(_objectName) +{ + private const string _objectName = "projectCounts"; + + public int Secrets { get; set; } + + public int People { get; set; } + + public int ServiceAccounts { get; set; } +} diff --git a/src/Api/SecretsManager/Models/Response/ServiceAccountCountsResponseModel.cs b/src/Api/SecretsManager/Models/Response/ServiceAccountCountsResponseModel.cs new file mode 100644 index 000000000..ac457d253 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/ServiceAccountCountsResponseModel.cs @@ -0,0 +1,15 @@ +#nullable enable +using Bit.Core.Models.Api; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class ServiceAccountCountsResponseModel() : ResponseModel(_objectName) +{ + private const string _objectName = "serviceAccountCounts"; + + public int Projects { get; set; } + + public int People { get; set; } + + public int AccessTokens { get; set; } +} diff --git a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs index 4dadedeb3..15e8bb295 100644 --- a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs +++ b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs @@ -1,4 +1,6 @@ -using Bit.Api.Models.Public.Response; +using System.Text; +using Bit.Api.Models.Public.Response; +using Bit.Core.Billing; using Bit.Core.Exceptions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -49,18 +51,18 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute errorMessage = badRequestException.Message; } } - else if (exception is StripeException stripeException && stripeException?.StripeError?.Type == "card_error") + else if (exception is StripeException { StripeError.Type: "card_error" } stripeCardErrorException) { context.HttpContext.Response.StatusCode = 400; if (_publicApi) { - publicErrorModel = new ErrorResponseModel(stripeException.StripeError.Param, - stripeException.Message); + publicErrorModel = new ErrorResponseModel(stripeCardErrorException.StripeError.Param, + stripeCardErrorException.Message); } else { - internalErrorModel = new InternalApi.ErrorResponseModel(stripeException.StripeError.Param, - stripeException.Message); + internalErrorModel = new InternalApi.ErrorResponseModel(stripeCardErrorException.StripeError.Param, + stripeCardErrorException.Message); } } else if (exception is GatewayException) @@ -68,6 +70,40 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute errorMessage = exception.Message; context.HttpContext.Response.StatusCode = 400; } + else if (exception is BillingException billingException) + { + errorMessage = billingException.Response; + context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + } + else if (exception is StripeException stripeException) + { + var logger = context.HttpContext.RequestServices.GetRequiredService>(); + + var error = stripeException.Message; + + if (stripeException.StripeError != null) + { + var stringBuilder = new StringBuilder(); + + if (!string.IsNullOrEmpty(stripeException.StripeError.Code)) + { + stringBuilder.Append($"{stripeException.StripeError.Code} | "); + } + + stringBuilder.Append(stripeException.StripeError.Message); + + if (!string.IsNullOrEmpty(stripeException.StripeError.DocUrl)) + { + stringBuilder.Append($" > {stripeException.StripeError.DocUrl}"); + } + + error = stringBuilder.ToString(); + } + + logger.LogError("An unhandled error occurred while communicating with Stripe: {Error}", error); + errorMessage = "Something went wrong with your request. Please contact support."; + context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + } else if (exception is NotSupportedException && !string.IsNullOrWhiteSpace(exception.Message)) { errorMessage = exception.Message; diff --git a/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs b/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs index d836b18e3..add5b75a3 100644 --- a/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs +++ b/src/Api/Vault/AuthorizationHandlers/Collections/BulkCollectionAuthorizationHandler.cs @@ -1,5 +1,4 @@ #nullable enable -using Bit.Core; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -191,11 +190,8 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler c.Manage) @@ -326,8 +320,6 @@ public class BulkCollectionAuthorizationHandler : BulkAuthorizationHandler AllowAdminAccessToAllCollectionItems(CurrentContextOrganization? org) { - var organizationAbility = await GetOrganizationAbilityAsync(org); - return !_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) || - organizationAbility is { AllowAdminAccessToAllCollectionItems: true }; + return await GetOrganizationAbilityAsync(org) is { AllowAdminAccessToAllCollectionItems: true }; } } diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 13e0546a2..1e608155c 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -41,15 +41,11 @@ public class CiphersController : Controller private readonly ICurrentContext _currentContext; private readonly ILogger _logger; private readonly GlobalSettings _globalSettings; - private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion); private readonly IFeatureService _featureService; private readonly IOrganizationCiphersQuery _organizationCiphersQuery; private readonly IApplicationCacheService _applicationCacheService; private readonly ICollectionRepository _collectionRepository; - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); - public CiphersController( ICipherRepository cipherRepository, ICollectionCipherRepository collectionCipherRepository, @@ -127,7 +123,7 @@ public class CiphersController : Controller var userId = _userService.GetProperUserId(User).Value; var hasOrgs = _currentContext.Organizations?.Any() ?? false; // TODO: Use hasOrgs proper for cipher listing here? - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: true || hasOrgs); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: true || hasOrgs); Dictionary> collectionCiphersGroupDict = null; if (hasOrgs) { @@ -199,7 +195,6 @@ public class CiphersController : Controller throw new NotFoundException(); } - ValidateClientVersionForItemLevelEncryptionSupport(cipher); ValidateClientVersionForFido2CredentialSupport(cipher); var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList(); @@ -224,7 +219,6 @@ public class CiphersController : Controller var userId = _userService.GetProperUserId(User).Value; var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id); - ValidateClientVersionForItemLevelEncryptionSupport(cipher); ValidateClientVersionForFido2CredentialSupport(cipher); if (cipher == null || !cipher.OrganizationId.HasValue || @@ -245,37 +239,24 @@ public class CiphersController : Controller [HttpGet("organization-details")] public async Task> GetOrganizationCiphers(Guid organizationId) { - // Flexible Collections V1 Logic - if (UseFlexibleCollectionsV1()) + if (!await CanAccessAllCiphersAsync(organizationId)) { - return await GetAllOrganizationCiphersAsync(organizationId); + throw new NotFoundException(); } - // Pre-Flexible Collections V1 Logic - var userId = _userService.GetProperUserId(User).Value; + var allOrganizationCiphers = await _organizationCiphersQuery.GetAllOrganizationCiphers(organizationId); - (IEnumerable orgCiphers, Dictionary> collectionCiphersGroupDict) = await _cipherService.GetOrganizationCiphers(userId, organizationId); + var allOrganizationCipherResponses = + allOrganizationCiphers.Select(c => + new CipherMiniDetailsResponseModel(c, _globalSettings, c.OrganizationUseTotp) + ); - var responses = orgCiphers.Select(c => new CipherMiniDetailsResponseModel(c, _globalSettings, - collectionCiphersGroupDict, c.OrganizationUseTotp)); - - var providerId = await _currentContext.ProviderIdForOrg(organizationId); - if (providerId.HasValue) - { - await _providerService.LogProviderAccessToOrganizationAsync(organizationId); - } - - return new ListResponseModel(responses); + return new ListResponseModel(allOrganizationCipherResponses); } [HttpGet("organization-details/assigned")] public async Task> GetAssignedOrganizationCiphers(Guid organizationId) { - if (!UseFlexibleCollectionsV1()) - { - throw new FeatureUnavailableException(); - } - if (!await CanAccessOrganizationCiphersAsync(organizationId) || !_currentContext.UserId.HasValue) { throw new NotFoundException(); @@ -299,27 +280,6 @@ public class CiphersController : Controller return new ListResponseModel(responses); } - /// - /// Returns all ciphers belonging to the organization if the user has access to All ciphers. - /// - /// - private async Task> GetAllOrganizationCiphersAsync(Guid organizationId) - { - if (!await CanAccessAllCiphersAsync(organizationId)) - { - throw new NotFoundException(); - } - - var allOrganizationCiphers = await _organizationCiphersQuery.GetAllOrganizationCiphers(organizationId); - - var allOrganizationCipherResponses = - allOrganizationCiphers.Select(c => - new CipherMiniDetailsResponseModel(c, _globalSettings, c.OrganizationUseTotp) - ); - - return new ListResponseModel(allOrganizationCipherResponses); - } - /// /// Permission helper to determine if the current user can use the "/admin" variants of the cipher endpoints. /// Allowed for custom users with EditAnyCollection, providers, unrestricted owners and admins (allowAdminAccess setting is ON). @@ -328,12 +288,6 @@ public class CiphersController : Controller /// private async Task CanEditCipherAsAdminAsync(Guid organizationId, IEnumerable cipherIds) { - // Pre-Flexible collections V1 only needs to check EditAnyCollection - if (!UseFlexibleCollectionsV1()) - { - return await _currentContext.EditAnyCollection(organizationId); - } - var org = _currentContext.GetOrganization(organizationId); // If we're not an "admin", we don't need to check the ciphers @@ -396,14 +350,6 @@ public class CiphersController : Controller { var org = _currentContext.GetOrganization(organizationId); - // If not using V1, owners, admins, and users with EditAnyCollection permissions, and providers can always edit all ciphers - if (!UseFlexibleCollectionsV1()) - { - return org is { Type: OrganizationUserType.Owner or OrganizationUserType.Admin } or - { Permissions.EditAnyCollection: true } || - await _currentContext.ProviderUserForOrgAsync(organizationId); - } - // Custom users with EditAnyCollection permissions can always edit all ciphers if (org is { Type: OrganizationUserType.Custom, Permissions.EditAnyCollection: true }) { @@ -553,7 +499,7 @@ public class CiphersController : Controller } var userId = _userService.GetProperUserId(User).Value; - var editableCollections = (await _collectionRepository.GetManyByUserIdAsync(userId, true)) + var editableCollections = (await _collectionRepository.GetManyByUserIdAsync(userId)) .Where(c => c.OrganizationId == organizationId && !c.ReadOnly) .ToDictionary(c => c.Id); @@ -590,7 +536,6 @@ public class CiphersController : Controller throw new NotFoundException(); } - ValidateClientVersionForItemLevelEncryptionSupport(cipher); ValidateClientVersionForFido2CredentialSupport(cipher); var original = cipher.Clone(); @@ -669,7 +614,7 @@ public class CiphersController : Controller // In V1, we still need to check if the user can edit the collections they're submitting // This should only happen for unassigned ciphers (otherwise restricted admins would use the normal collections endpoint) - if (UseFlexibleCollectionsV1() && !await CanEditItemsInCollections(cipher.OrganizationId.Value, collectionIds)) + if (!await CanEditItemsInCollections(cipher.OrganizationId.Value, collectionIds)) { throw new NotFoundException(); } @@ -926,7 +871,7 @@ public class CiphersController : Controller } var userId = _userService.GetProperUserId(User).Value; - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: false); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: false); var ciphersDict = ciphers.ToDictionary(c => c.Id); var shareCiphers = new List<(Cipher, DateTime?)>(); @@ -939,7 +884,6 @@ public class CiphersController : Controller var existingCipher = ciphersDict[cipher.Id.Value]; - ValidateClientVersionForItemLevelEncryptionSupport(existingCipher); ValidateClientVersionForFido2CredentialSupport(existingCipher); shareCiphers.Add((cipher.ToCipher(existingCipher), cipher.LastKnownRevisionDate)); @@ -994,8 +938,6 @@ public class CiphersController : Controller throw new NotFoundException(); } - ValidateClientVersionForItemLevelEncryptionSupport(cipher); - if (request.FileSize > CipherService.MAX_FILE_SIZE) { throw new BadRequestException($"Max file size is {CipherService.MAX_FILE_SIZE_READABLE}."); @@ -1205,32 +1147,6 @@ public class CiphersController : Controller }); } - /// - /// Returns true if the user is an admin or owner of an organization with unassigned ciphers (i.e. ciphers that - /// are not assigned to a collection). - /// - /// - [HttpGet("has-unassigned-ciphers")] - public async Task HasUnassignedCiphers() - { - // We don't filter for organization.FlexibleCollections here, it's shown for all orgs, and the client determines - // whether the message is shown in future tense (not yet migrated) or present tense (already migrated) - var adminOrganizations = _currentContext.Organizations - .Where(o => o.Type is OrganizationUserType.Admin or OrganizationUserType.Owner); - - foreach (var org in adminOrganizations) - { - var unassignedCiphers = await _cipherRepository.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(org.Id); - // We only care about non-deleted ciphers - if (unassignedCiphers.Any(c => c.DeletedDate == null)) - { - return true; - } - } - - return false; - } - private void ValidateAttachment() { if (!Request?.ContentType.Contains("multipart/") ?? true) @@ -1239,14 +1155,6 @@ public class CiphersController : Controller } } - private void ValidateClientVersionForItemLevelEncryptionSupport(Cipher cipher) - { - if (cipher.Key != null && _currentContext.ClientVersion < _cipherKeyEncryptionMinimumVersion) - { - throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again."); - } - } - private void ValidateClientVersionForFido2CredentialSupport(Cipher cipher) { if (cipher.Type == Core.Vault.Enums.CipherType.Login) @@ -1263,9 +1171,4 @@ public class CiphersController : Controller { return await _cipherRepository.GetByIdAsync(cipherId, userId); } - - private bool UseFlexibleCollectionsV1() - { - return _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - } } diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index 82ed82a2b..0381bdca6 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -1,5 +1,4 @@ using Bit.Api.Vault.Models.Response; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; @@ -31,10 +30,6 @@ public class SyncController : Controller private readonly IPolicyRepository _policyRepository; private readonly ISendRepository _sendRepository; private readonly GlobalSettings _globalSettings; - private readonly IFeatureService _featureService; - - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); public SyncController( IUserService userService, @@ -46,8 +41,7 @@ public class SyncController : Controller IProviderUserRepository providerUserRepository, IPolicyRepository policyRepository, ISendRepository sendRepository, - GlobalSettings globalSettings, - IFeatureService featureService) + GlobalSettings globalSettings) { _userService = userService; _folderRepository = folderRepository; @@ -59,7 +53,6 @@ public class SyncController : Controller _policyRepository = policyRepository; _sendRepository = sendRepository; _globalSettings = globalSettings; - _featureService = featureService; } [HttpGet("")] @@ -81,7 +74,7 @@ public class SyncController : Controller var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled); var folders = await _folderRepository.GetManyByUserIdAsync(user.Id); - var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: UseFlexibleCollections, withOrganizations: hasEnabledOrgs); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: hasEnabledOrgs); var sends = await _sendRepository.GetManyByUserIdAsync(user.Id); IEnumerable collections = null; @@ -90,7 +83,7 @@ public class SyncController : Controller if (hasEnabledOrgs) { - collections = await _collectionRepository.GetManyByUserIdAsync(user.Id, UseFlexibleCollections); + collections = await _collectionRepository.GetManyByUserIdAsync(user.Id); var collectionCiphers = await _collectionCipherRepository.GetManyByUserIdAsync(user.Id); collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key); } diff --git a/src/Api/Vault/Validators/CipherRotationValidator.cs b/src/Api/Vault/Validators/CipherRotationValidator.cs index d6c12b96e..77e437017 100644 --- a/src/Api/Vault/Validators/CipherRotationValidator.cs +++ b/src/Api/Vault/Validators/CipherRotationValidator.cs @@ -1,9 +1,7 @@ using Bit.Api.Auth.Validators; using Bit.Api.Vault.Models.Request; -using Bit.Core; using Bit.Core.Entities; using Bit.Core.Exceptions; -using Bit.Core.Services; using Bit.Core.Vault.Entities; using Bit.Core.Vault.Repositories; @@ -12,28 +10,29 @@ namespace Bit.Api.Vault.Validators; public class CipherRotationValidator : IRotationValidator, IEnumerable> { private readonly ICipherRepository _cipherRepository; - private readonly IFeatureService _featureService; - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); - - public CipherRotationValidator(ICipherRepository cipherRepository, IFeatureService featureService) + public CipherRotationValidator(ICipherRepository cipherRepository) { _cipherRepository = cipherRepository; - _featureService = featureService; } public async Task> ValidateAsync(User user, IEnumerable ciphers) { var result = new List(); - var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, UseFlexibleCollections); - if (existingCiphers == null || existingCiphers.Count == 0) + var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id); + if (existingCiphers == null) { return result; } - foreach (var existing in existingCiphers) + var existingUserCiphers = existingCiphers.Where(c => c.OrganizationId == null); + if (existingUserCiphers.Count() == 0) + { + return result; + } + + foreach (var existing in existingUserCiphers) { var cipher = ciphers.FirstOrDefault(c => c.Id == existing.Id); if (cipher == null) diff --git a/src/Billing/Services/Implementations/ProviderEventService.cs b/src/Billing/Services/Implementations/ProviderEventService.cs index b741e834f..e716f8f16 100644 --- a/src/Billing/Services/Implementations/ProviderEventService.cs +++ b/src/Billing/Services/Implementations/ProviderEventService.cs @@ -76,7 +76,7 @@ public class ProviderEventService( ProviderId = parsedProviderId, InvoiceId = invoice.Id, InvoiceNumber = invoice.Number, - ClientId = client.Id, + ClientId = client.OrganizationId, ClientName = client.OrganizationName, PlanName = client.Plan, AssignedSeats = client.Seats ?? 0, diff --git a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs index 48b691097..aaa06b9f4 100644 --- a/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs +++ b/src/Billing/Services/Implementations/UpcomingInvoiceHandler.cs @@ -1,5 +1,4 @@ using Bit.Billing.Constants; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Constants; @@ -9,7 +8,6 @@ using Bit.Core.Services; using Bit.Core.Utilities; using Stripe; using Event = Stripe.Event; -using TaxRate = Bit.Core.Entities.TaxRate; namespace Bit.Billing.Services.Implementations; @@ -19,38 +17,32 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler private readonly IStripeEventService _stripeEventService; private readonly IUserService _userService; private readonly IStripeFacade _stripeFacade; - private readonly IFeatureService _featureService; private readonly IMailService _mailService; private readonly IProviderRepository _providerRepository; private readonly IValidateSponsorshipCommand _validateSponsorshipCommand; private readonly IOrganizationRepository _organizationRepository; private readonly IStripeEventUtilityService _stripeEventUtilityService; - private readonly ITaxRateRepository _taxRateRepository; public UpcomingInvoiceHandler( ILogger logger, IStripeEventService stripeEventService, IUserService userService, IStripeFacade stripeFacade, - IFeatureService featureService, IMailService mailService, IProviderRepository providerRepository, IValidateSponsorshipCommand validateSponsorshipCommand, IOrganizationRepository organizationRepository, - IStripeEventUtilityService stripeEventUtilityService, - ITaxRateRepository taxRateRepository) + IStripeEventUtilityService stripeEventUtilityService) { _logger = logger; _stripeEventService = stripeEventService; _userService = userService; _stripeFacade = stripeFacade; - _featureService = featureService; _mailService = mailService; _providerRepository = providerRepository; _validateSponsorshipCommand = validateSponsorshipCommand; _organizationRepository = organizationRepository; _stripeEventUtilityService = stripeEventUtilityService; - _taxRateRepository = taxRateRepository; } /// @@ -75,27 +67,7 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler $"Received null Subscription from Stripe for ID '{invoice.SubscriptionId}' while processing Event with ID '{parsedEvent.Id}'"); } - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - if (pm5766AutomaticTaxIsEnabled) - { - var customerGetOptions = new CustomerGetOptions(); - customerGetOptions.AddExpand("tax"); - var customer = await _stripeFacade.GetCustomer(subscription.CustomerId, customerGetOptions); - if (!subscription.AutomaticTax.Enabled && - customer.Tax?.AutomaticTax == StripeConstants.AutomaticTaxStatus.Supported) - { - subscription = await _stripeFacade.UpdateSubscription(subscription.Id, - new SubscriptionUpdateOptions - { - DefaultTaxRates = [], - AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } - }); - } - } - - var updatedSubscription = pm5766AutomaticTaxIsEnabled - ? subscription - : await VerifyCorrectTaxRateForChargeAsync(invoice, subscription); + var updatedSubscription = await TryEnableAutomaticTaxAsync(subscription); var (organizationId, userId, providerId) = _stripeEventUtilityService.GetIdsFromMetadata(updatedSubscription.Metadata); @@ -105,7 +77,18 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler { if (_stripeEventUtilityService.IsSponsoredSubscription(updatedSubscription)) { - await _validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value); + var sponsorshipIsValid = + await _validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value); + if (!sponsorshipIsValid) + { + // If the sponsorship is invalid, then the subscription was updated to use the regular families plan + // price. Given that this is the case, we need the new invoice amount + subscription = await _stripeFacade.GetSubscription(subscription.Id, + new SubscriptionGetOptions { Expand = ["latest_invoice"] }); + + invoice = subscription.LatestInvoice; + invoiceLineItemDescriptions = invoice.Lines.Select(i => i.Description).ToList(); + } } var organization = await _organizationRepository.GetByIdAsync(organizationId.Value); @@ -176,39 +159,24 @@ public class UpcomingInvoiceHandler : IUpcomingInvoiceHandler } } - private async Task VerifyCorrectTaxRateForChargeAsync(Invoice invoice, Stripe.Subscription subscription) + private async Task TryEnableAutomaticTaxAsync(Subscription subscription) { - if (string.IsNullOrWhiteSpace(invoice?.CustomerAddress?.Country) || - string.IsNullOrWhiteSpace(invoice?.CustomerAddress?.PostalCode)) + var customerGetOptions = new CustomerGetOptions { Expand = ["tax"] }; + var customer = await _stripeFacade.GetCustomer(subscription.CustomerId, customerGetOptions); + + if (subscription.AutomaticTax.Enabled || + customer.Tax?.AutomaticTax != StripeConstants.AutomaticTaxStatus.Supported) { return subscription; } - var localBitwardenTaxRates = await _taxRateRepository.GetByLocationAsync( - new TaxRate() - { - Country = invoice.CustomerAddress.Country, - PostalCode = invoice.CustomerAddress.PostalCode - } - ); - - if (!localBitwardenTaxRates.Any()) + var subscriptionUpdateOptions = new SubscriptionUpdateOptions { - return subscription; - } + DefaultTaxRates = [], + AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true } + }; - var stripeTaxRate = await _stripeFacade.GetTaxRate(localBitwardenTaxRates.First().Id); - if (stripeTaxRate == null || subscription.DefaultTaxRates.Any(x => x == stripeTaxRate)) - { - return subscription; - } - - subscription.DefaultTaxRates = [stripeTaxRate]; - - var subscriptionOptions = new SubscriptionUpdateOptions { DefaultTaxRates = [stripeTaxRate.Id] }; - subscription = await _stripeFacade.UpdateSubscription(subscription.Id, subscriptionOptions); - - return subscription; + return await _stripeFacade.UpdateSubscription(subscription.Id, subscriptionUpdateOptions); } private static bool OrgPlanForInvoiceNotifications(Organization org) => StaticStore.GetPlan(org.PlanType).IsAnnual; diff --git a/src/Core/AdminConsole/Entities/Organization.cs b/src/Core/AdminConsole/Entities/Organization.cs index 42086a67f..1f60a554c 100644 --- a/src/Core/AdminConsole/Entities/Organization.cs +++ b/src/Core/AdminConsole/Entities/Organization.cs @@ -99,12 +99,6 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, /// If set to false, users generally need collection-level permissions to read/write a collection or its items. /// public bool AllowAdminAccessToAllCollectionItems { get; set; } - /// - /// This is an organization-level feature flag (not controlled via LaunchDarkly) to onboard organizations to the - /// Flexible Collections MVP changes. This has been fully released and must always be set to TRUE for all organizations. - /// AC-1714 will remove this flag after all old code has been removed. - /// - public bool FlexibleCollections { get; set; } public void SetNewId() { @@ -275,7 +269,6 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, // The following properties are intentionally excluded from being updated: // - Id - self-hosted org will have its own unique Guid // - MaxStorageGb - not enforced for self-hosted because we're not providing the storage - // - FlexibleCollections - the self-hosted organization must do its own data migration to set this property, it cannot be updated from cloud Name = license.Name; BusinessName = license.BusinessName; diff --git a/src/Core/AdminConsole/Entities/OrganizationUser.cs b/src/Core/AdminConsole/Entities/OrganizationUser.cs index afc3dc490..5340e2255 100644 --- a/src/Core/AdminConsole/Entities/OrganizationUser.cs +++ b/src/Core/AdminConsole/Entities/OrganizationUser.cs @@ -19,7 +19,11 @@ public class OrganizationUser : ITableObject, IExternal public string? ResetPasswordKey { get; set; } public OrganizationUserStatusType Status { get; set; } public OrganizationUserType Type { get; set; } - public bool AccessAll { get; set; } + + /// + /// AccessAll is deprecated and should always be left as false. Scheduled for removal. + /// + public bool AccessAll { get; set; } = false; [MaxLength(300)] public string? ExternalId { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/AdminConsole/Enums/OrganizationUserType.cs b/src/Core/AdminConsole/Enums/OrganizationUserType.cs index 620eaeb33..be5986a65 100644 --- a/src/Core/AdminConsole/Enums/OrganizationUserType.cs +++ b/src/Core/AdminConsole/Enums/OrganizationUserType.cs @@ -5,6 +5,6 @@ public enum OrganizationUserType : byte Owner = 0, Admin = 1, User = 2, - Manager = 3, + // Manager = 3 has been intentionally permanently deleted Custom = 4, } diff --git a/src/Core/AdminConsole/Models/Business/OrganizationUserInvite.cs b/src/Core/AdminConsole/Models/Business/OrganizationUserInvite.cs index 7102a5f9e..e177a5047 100644 --- a/src/Core/AdminConsole/Models/Business/OrganizationUserInvite.cs +++ b/src/Core/AdminConsole/Models/Business/OrganizationUserInvite.cs @@ -7,7 +7,6 @@ public class OrganizationUserInvite { public IEnumerable Emails { get; set; } public Enums.OrganizationUserType? Type { get; set; } - public bool AccessAll { get; set; } public bool AccessSecretsManager { get; set; } public Permissions Permissions { get; set; } public IEnumerable Collections { get; set; } @@ -19,7 +18,6 @@ public class OrganizationUserInvite { Emails = requestModel.Emails; Type = requestModel.Type; - AccessAll = requestModel.AccessAll; AccessSecretsManager = requestModel.AccessSecretsManager; Collections = requestModel.Collections; Groups = requestModel.Groups; diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs index f867d1881..07db80d43 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationAbility.cs @@ -23,7 +23,6 @@ public class OrganizationAbility UsePolicies = organization.UsePolicies; LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion; AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems; - FlexibleCollections = organization.FlexibleCollections; } public Guid Id { get; set; } @@ -40,5 +39,4 @@ public class OrganizationAbility public bool UsePolicies { get; set; } public bool LimitCollectionCreationDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } - public bool FlexibleCollections { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserInviteData.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserInviteData.cs index f8789fe5d..a48ee3a6c 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserInviteData.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserInviteData.cs @@ -6,7 +6,6 @@ public class OrganizationUserInviteData { public IEnumerable Emails { get; set; } public OrganizationUserType? Type { get; set; } - public bool AccessAll { get; set; } public bool AccessSecretsManager { get; set; } public IEnumerable Collections { get; set; } public IEnumerable Groups { get; set; } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs index 7f0a20762..cdd73cba7 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs @@ -56,5 +56,4 @@ public class OrganizationUserOrganizationDetails public int? SmServiceAccounts { get; set; } public bool LimitCollectionCreationDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } - public bool FlexibleCollections { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs index 80a16e495..d21ba9183 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/SelfHostedOrganizationDetails.cs @@ -146,7 +146,6 @@ public class SelfHostedOrganizationDetails : Organization OwnersNotifiedOfAutoscaling = OwnersNotifiedOfAutoscaling, LimitCollectionCreationDeletion = LimitCollectionCreationDeletion, AllowAdminAccessToAllCollectionItems = AllowAdminAccessToAllCollectionItems, - FlexibleCollections = FlexibleCollections, Status = Status }; } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs index ed68f957a..d3d831f51 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -42,5 +42,4 @@ public class ProviderUserOrganizationDetails public PlanType PlanType { get; set; } public bool LimitCollectionCreationDeletion { get; set; } public bool AllowAdminAccessToAllCollectionItems { get; set; } - public bool FlexibleCollections { get; set; } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs index af95f9fb3..83d076475 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs @@ -115,18 +115,15 @@ public class CreateGroupCommand : ICreateGroupCommand throw new BadRequestException("This organization cannot use groups."); } - if (organization.FlexibleCollections) + if (group.AccessAll) { - if (group.AccessAll) - { - throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); - } + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); + } - var invalidAssociations = 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."); - } + var invalidAssociations = 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."); } } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs index ba2aa2b8b..b471b1d88 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Entities; +#nullable enable + +using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; @@ -14,48 +16,53 @@ public class UpdateGroupCommand : IUpdateGroupCommand private readonly IEventService _eventService; private readonly IGroupRepository _groupRepository; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ICollectionRepository _collectionRepository; public UpdateGroupCommand( IEventService eventService, IGroupRepository groupRepository, - IOrganizationUserRepository organizationUserRepository) + IOrganizationUserRepository organizationUserRepository, + ICollectionRepository collectionRepository) { _eventService = eventService; _groupRepository = groupRepository; _organizationUserRepository = organizationUserRepository; + _collectionRepository = collectionRepository; } public async Task UpdateGroupAsync(Group group, Organization organization, - ICollection collections = null, - IEnumerable userIds = null) + ICollection? collections = null, + IEnumerable? userIds = null) { - Validate(organization, group, collections); - await GroupRepositoryUpdateGroupAsync(group, collections); + await ValidateAsync(organization, group, collections, userIds); + + await SaveGroupWithCollectionsAsync(group, collections); if (userIds != null) { - await GroupRepositoryUpdateUsersAsync(group, userIds); + await SaveGroupUsersAsync(group, userIds); } await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Updated); } public async Task UpdateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, - ICollection collections = null, - IEnumerable userIds = null) + ICollection? collections = null, + IEnumerable? userIds = null) { - Validate(organization, group, collections); - await GroupRepositoryUpdateGroupAsync(group, collections); + await ValidateAsync(organization, group, collections, userIds); + + await SaveGroupWithCollectionsAsync(group, collections); if (userIds != null) { - await GroupRepositoryUpdateUsersAsync(group, userIds, systemUser); + await SaveGroupUsersAsync(group, userIds, systemUser); } await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Updated, systemUser); } - private async Task GroupRepositoryUpdateGroupAsync(Group group, IEnumerable collections = null) + private async Task SaveGroupWithCollectionsAsync(Group group, IEnumerable? collections = null) { group.RevisionDate = DateTime.UtcNow; @@ -69,7 +76,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand } } - private async Task GroupRepositoryUpdateUsersAsync(Group group, IEnumerable userIds, EventSystemUser? systemUser = null) + private async Task SaveGroupUsersAsync(Group group, IEnumerable userIds, EventSystemUser? systemUser = null) { var newUserIds = userIds as Guid[] ?? userIds.ToArray(); var originalUserIds = await _groupRepository.GetManyUserIdsByIdAsync(group.Id); @@ -97,11 +104,15 @@ public class UpdateGroupCommand : IUpdateGroupCommand } } - private static void Validate(Organization organization, Group group, IEnumerable collections) + private async Task ValidateAsync(Organization organization, Group group, ICollection? collectionAccess, + IEnumerable? memberAccess) { - if (organization == null) + // Avoid multiple enumeration + memberAccess = memberAccess?.ToList(); + + if (organization == null || organization.Id != group.OrganizationId) { - throw new BadRequestException("Organization not found"); + throw new NotFoundException(); } if (!organization.UseGroups) @@ -109,18 +120,73 @@ public class UpdateGroupCommand : IUpdateGroupCommand throw new BadRequestException("This organization cannot use groups."); } - if (organization.FlexibleCollections) + var originalGroup = await _groupRepository.GetByIdAsync(group.Id); + if (originalGroup == null || originalGroup.OrganizationId != group.OrganizationId) { - if (group.AccessAll) - { - throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); - } + throw new NotFoundException(); + } - var invalidAssociations = 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."); - } + if (collectionAccess?.Any() == true) + { + await ValidateCollectionAccessAsync(originalGroup, collectionAccess); + } + + if (memberAccess?.Any() == true) + { + await ValidateMemberAccessAsync(originalGroup, memberAccess.ToList()); + } + + if (group.AccessAll) + { + throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the group to collections instead."); + } + + var invalidAssociations = collectionAccess?.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."); + } + } + + private async Task ValidateCollectionAccessAsync(Group originalGroup, + ICollection collectionAccess) + { + var collections = await _collectionRepository + .GetManyByManyIdsAsync(collectionAccess.Select(c => c.Id)); + var collectionIds = collections.Select(c => c.Id); + + var missingCollection = collectionAccess + .FirstOrDefault(cas => !collectionIds.Contains(cas.Id)); + if (missingCollection != default) + { + throw new NotFoundException(); + } + + var invalidCollection = collections.FirstOrDefault(c => c.OrganizationId != originalGroup.OrganizationId); + if (invalidCollection != default) + { + // Use generic error message to avoid enumeration + throw new NotFoundException(); + } + } + + private async Task ValidateMemberAccessAsync(Group originalGroup, + ICollection memberAccess) + { + var members = await _organizationUserRepository.GetManyAsync(memberAccess); + var memberIds = members.Select(g => g.Id); + + var missingMemberId = memberAccess.FirstOrDefault(mId => !memberIds.Contains(mId)); + if (missingMemberId != default) + { + throw new NotFoundException(); + } + + var invalidMember = members.FirstOrDefault(m => m.OrganizationId != originalGroup.OrganizationId); + if (invalidMember != default) + { + // Use generic error message to avoid enumeration + throw new NotFoundException(); } } } diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs index 254a97b75..c7298e1cd 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/Interfaces/IUpdateOrganizationUserCommand.cs @@ -6,5 +6,6 @@ namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interface public interface IUpdateOrganizationUserCommand { - Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable collections, IEnumerable? groups); + Task UpdateUserAsync(OrganizationUser user, 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 9d25e6442..a37d94fb6 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommand.cs @@ -1,5 +1,6 @@ #nullable enable using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -19,6 +20,8 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ICountNewSmSeatsRequiredQuery _countNewSmSeatsRequiredQuery; private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand; + private readonly ICollectionRepository _collectionRepository; + private readonly IGroupRepository _groupRepository; public UpdateOrganizationUserCommand( IEventService eventService, @@ -26,7 +29,9 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICountNewSmSeatsRequiredQuery countNewSmSeatsRequiredQuery, - IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand) + IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand, + ICollectionRepository collectionRepository, + IGroupRepository groupRepository) { _eventService = eventService; _organizationService = organizationService; @@ -34,17 +39,45 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand _organizationUserRepository = organizationUserRepository; _countNewSmSeatsRequiredQuery = countNewSmSeatsRequiredQuery; _updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand; + _collectionRepository = collectionRepository; + _groupRepository = groupRepository; } + /// + /// Update an organization user. + /// + /// The modified user to save. + /// The userId of the currently logged in user who is making the change. + /// The user's updated collection access. If set to null, this removes all collection access. + /// The user's updated group access. If set to null, groups are not updated. + /// public async Task UpdateUserAsync(OrganizationUser user, Guid? savingUserId, - IEnumerable collections, IEnumerable? groups) + List? collectionAccess, IEnumerable? groupAccess) { + // Avoid multiple enumeration + collectionAccess = collectionAccess?.ToList(); + groupAccess = groupAccess?.ToList(); + if (user.Id.Equals(default(Guid))) { throw new BadRequestException("Invite the user first."); } var originalUser = await _organizationUserRepository.GetByIdAsync(user.Id); + if (originalUser == null || user.OrganizationId != originalUser.OrganizationId) + { + throw new NotFoundException(); + } + + if (collectionAccess?.Any() == true) + { + await ValidateCollectionAccessAsync(originalUser, collectionAccess.ToList()); + } + + if (groupAccess?.Any() == true) + { + await ValidateGroupAccessAsync(originalUser, groupAccess.ToList()); + } if (savingUserId.HasValue) { @@ -59,27 +92,19 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand throw new BadRequestException("Organization must have at least one confirmed owner."); } - // If the organization is using Flexible Collections, prevent use of any deprecated permissions - var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId); - if (organization.FlexibleCollections && user.Type == OrganizationUserType.Manager) - { - throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead."); - } - - if (organization.FlexibleCollections && user.AccessAll) + if (user.AccessAll) { throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead."); } - if (organization.FlexibleCollections && collections?.Any() == true) + if (collectionAccess?.Count > 0) { - var invalidAssociations = collections.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); + var invalidAssociations = collectionAccess.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); if (invalidAssociations.Any()) { throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); } } - // End Flexible Collections // Only autoscale (if required) after all validation has passed so that we know it's a valid request before // updating Stripe @@ -88,24 +113,62 @@ public class UpdateOrganizationUserCommand : IUpdateOrganizationUserCommand var additionalSmSeatsRequired = await _countNewSmSeatsRequiredQuery.CountNewSmSeatsRequiredAsync(user.OrganizationId, 1); if (additionalSmSeatsRequired > 0) { + var organization = await _organizationRepository.GetByIdAsync(user.OrganizationId); var update = new SecretsManagerSubscriptionUpdate(organization, true) .AdjustSeats(additionalSmSeatsRequired); await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(update); } } - if (user.AccessAll) - { - // We don't need any collections if we're flagged to have all access. - collections = new List(); - } - await _organizationUserRepository.ReplaceAsync(user, collections); + await _organizationUserRepository.ReplaceAsync(user, collectionAccess); - if (groups != null) + if (groupAccess != null) { - await _organizationUserRepository.UpdateGroupsAsync(user.Id, groups); + await _organizationUserRepository.UpdateGroupsAsync(user.Id, groupAccess); } await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated); } + + private async Task ValidateCollectionAccessAsync(OrganizationUser originalUser, + ICollection collectionAccess) + { + var collections = await _collectionRepository + .GetManyByManyIdsAsync(collectionAccess.Select(c => c.Id)); + var collectionIds = collections.Select(c => c.Id); + + var missingCollection = collectionAccess + .FirstOrDefault(cas => !collectionIds.Contains(cas.Id)); + if (missingCollection != default) + { + throw new NotFoundException(); + } + + var invalidCollection = collections.FirstOrDefault(c => c.OrganizationId != originalUser.OrganizationId); + if (invalidCollection != default) + { + // Use generic error message to avoid enumeration + throw new NotFoundException(); + } + } + + private async Task ValidateGroupAccessAsync(OrganizationUser originalUser, + ICollection groupAccess) + { + var groups = await _groupRepository.GetManyByManyIds(groupAccess); + var groupIds = groups.Select(g => g.Id); + + var missingGroupId = groupAccess.FirstOrDefault(gId => !groupIds.Contains(gId)); + if (missingGroupId != default) + { + throw new NotFoundException(); + } + + var invalidGroup = groups.FirstOrDefault(g => g.OrganizationId != originalUser.OrganizationId); + if (invalidGroup != default) + { + // Use generic error message to avoid enumeration + throw new NotFoundException(); + } + } } diff --git a/src/Core/AdminConsole/Services/IOrganizationService.cs b/src/Core/AdminConsole/Services/IOrganizationService.cs index d26ed901b..fac18ca40 100644 --- a/src/Core/AdminConsole/Services/IOrganizationService.cs +++ b/src/Core/AdminConsole/Services/IOrganizationService.cs @@ -53,6 +53,8 @@ public interface IOrganizationService Guid confirmingUserId, IUserService userService); Task>> ConfirmUsersAsync(Guid organizationId, Dictionary keys, Guid confirmingUserId, IUserService userService); + Task>> ConfirmUsersAsync_vNext(Guid organizationId, Dictionary keys, + Guid confirmingUserId); [Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")] Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId); [Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")] diff --git a/src/Core/AdminConsole/Services/IProviderService.cs b/src/Core/AdminConsole/Services/IProviderService.cs index c12bda37d..8999b3cb8 100644 --- a/src/Core/AdminConsole/Services/IProviderService.cs +++ b/src/Core/AdminConsole/Services/IProviderService.cs @@ -7,7 +7,7 @@ namespace Bit.Core.AdminConsole.Services; public interface IProviderService { - Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key); + Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null); Task UpdateAsync(Provider provider, bool updateBilling = false); Task> InviteUserAsync(ProviderUserInvite invite); diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 9bcefa3c7..37f376e50 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -13,6 +13,7 @@ using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Business; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Enums; using Bit.Core.Context; using Bit.Core.Entities; @@ -67,6 +68,7 @@ public class OrganizationService : IOrganizationService private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory; private readonly IDataProtectorTokenFactory _orgUserInviteTokenDataFactory; private readonly IFeatureService _featureService; + private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; public OrganizationService( IOrganizationRepository organizationRepository, @@ -99,7 +101,8 @@ public class OrganizationService : IOrganizationService IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand, IDataProtectorTokenFactory orgDeleteTokenDataFactory, IProviderRepository providerRepository, - IFeatureService featureService) + IFeatureService featureService, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -132,6 +135,7 @@ public class OrganizationService : IOrganizationService _orgUserInviteTokenableFactory = orgUserInviteTokenableFactory; _orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory; _featureService = featureService; + _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; } public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, @@ -148,9 +152,7 @@ public class OrganizationService : IOrganizationService organization, paymentMethodType, paymentToken, - _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax) - ? taxInfo - : null); + taxInfo); if (updated) { await ReplaceAndUpdateCacheAsync(organization); @@ -439,20 +441,16 @@ public class OrganizationService : IOrganizationService ValidatePlan(plan, signup.AdditionalSeats, "Password Manager"); - var flexibleCollectionsV1Enabled = - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - var organization = new Organization { - // Pre-generate the org id so that we can save it with the Stripe subscription.. + // Pre-generate the org id so that we can save it with the Stripe subscription. Id = CoreHelpers.GenerateComb(), Name = signup.Name, BillingEmail = signup.BillingEmail, PlanType = plan!.Type, Seats = signup.AdditionalSeats, MaxCollections = plan.PasswordManager.MaxCollections, - // Extra storage not available for purchase with Consolidated Billing. - MaxStorageGb = 0, + MaxStorageGb = 1, UsePolicies = plan.HasPolicies, UseSso = plan.HasSso, UseGroups = plan.HasGroups, @@ -479,14 +477,6 @@ public class OrganizationService : IOrganizationService UsePasswordManager = true, // Secrets Manager not available for purchase with Consolidated Billing. UseSecretsManager = false, - - // Flexible Collections MVP is fully released and all organizations must always have this setting enabled. - // AC-1714 will remove this flag after all old code has been removed. - FlexibleCollections = true, - - // This is a transitional setting that defaults to ON until Flexible Collections v1 is released - // (to preserve existing behavior) and defaults to OFF after release (enabling new behavior) - AllowAdminAccessToAllCollectionItems = !flexibleCollectionsV1Enabled }; var returnValue = await SignUpAsync(organization, default, signup.OwnerKey, signup.CollectionName, false); @@ -529,9 +519,6 @@ public class OrganizationService : IOrganizationService await ValidateSignUpPoliciesAsync(signup.Owner.Id); } - var flexibleCollectionsV1IsEnabled = - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - var organization = new Organization { // Pre-generate the org id so that we can save it with the Stripe subscription.. @@ -568,15 +555,7 @@ public class OrganizationService : IOrganizationService RevisionDate = DateTime.UtcNow, Status = OrganizationStatusType.Created, UsePasswordManager = true, - UseSecretsManager = signup.UseSecretsManager, - - // Flexible Collections MVP is fully released and all organizations must always have this setting enabled. - // AC-1714 will remove this flag after all old code has been removed. - FlexibleCollections = true, - - // This is a transitional setting that defaults to ON until Flexible Collections v1 is released - // (to preserve existing behavior) and defaults to OFF after release (enabling new behavior) - AllowAdminAccessToAllCollectionItems = !flexibleCollectionsV1IsEnabled + UseSecretsManager = signup.UseSecretsManager }; if (signup.UseSecretsManager) @@ -698,10 +677,6 @@ public class OrganizationService : IOrganizationService SmServiceAccounts = license.SmServiceAccounts, LimitCollectionCreationDeletion = license.LimitCollectionCreationDeletion, AllowAdminAccessToAllCollectionItems = license.AllowAdminAccessToAllCollectionItems, - - // This feature flag indicates that new organizations should be automatically onboarded to - // Flexible Collections enhancements - FlexibleCollections = true, }; var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false); @@ -745,10 +720,6 @@ public class OrganizationService : IOrganizationService AccessSecretsManager = organization.UseSecretsManager, Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Confirmed, - - // If using Flexible Collections, AccessAll is deprecated and set to false. - // If not using Flexible Collections, set AccessAll to true (previous behavior) - AccessAll = !organization.FlexibleCollections, CreationDate = organization.CreationDate, RevisionDate = organization.CreationDate }; @@ -773,9 +744,9 @@ public class OrganizationService : IOrganizationService RevisionDate = organization.CreationDate }; - // If using Flexible Collections, give the owner Can Manage access over the default collection + // Give the owner Can Manage access over the default collection List defaultOwnerAccess = null; - if (orgUser != null && organization.FlexibleCollections) + if (orgUser != null) { defaultOwnerAccess = [new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }]; @@ -967,15 +938,11 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("This method can only be used to invite a single user."); } - // Validate Collection associations if org is using latest collection enhancements - var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(organizationId); - if (organizationAbility?.FlexibleCollections ?? false) + // Validate Collection associations + var invalidAssociations = invite.Collections?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); + if (invalidAssociations?.Any() ?? false) { - 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, @@ -1040,18 +1007,6 @@ public class OrganizationService : IOrganizationService throw new NotFoundException(); } - // If the organization is using Flexible Collections, prevent use of any deprecated permissions - if (organization.FlexibleCollections && invites.Any(i => i.invite.Type is OrganizationUserType.Manager)) - { - throw new BadRequestException("The Manager role has been deprecated by collection enhancements. Use the collection Can Manage permission instead."); - } - - if (organization.FlexibleCollections && invites.Any(i => i.invite.AccessAll)) - { - throw new BadRequestException("The AccessAll property has been deprecated by collection enhancements. Assign the user to collections instead."); - } - // End Flexible Collections - var existingEmails = new HashSet(await _organizationUserRepository.SelectKnownEmailsAsync( organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); @@ -1094,8 +1049,8 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Organization must have at least one confirmed owner."); } - var orgUsers = new List(); - var limitedCollectionOrgUsers = new List<(OrganizationUser, IEnumerable)>(); + var orgUsersWithoutCollections = new List(); + var orgUsersWithCollections = new List<(OrganizationUser, IEnumerable)>(); var orgUserGroups = new List<(OrganizationUser, IEnumerable)>(); var orgUserInvitedCount = 0; var exceptions = new List(); @@ -1121,7 +1076,6 @@ public class OrganizationService : IOrganizationService Key = null, Type = invite.Type.Value, Status = OrganizationUserStatusType.Invited, - AccessAll = invite.AccessAll, AccessSecretsManager = invite.AccessSecretsManager, ExternalId = externalId, CreationDate = DateTime.UtcNow, @@ -1133,13 +1087,13 @@ public class OrganizationService : IOrganizationService orgUser.SetPermissions(invite.Permissions ?? new Permissions()); } - if (!orgUser.AccessAll && invite.Collections.Any()) + if (invite.Collections.Any()) { - limitedCollectionOrgUsers.Add((orgUser, invite.Collections)); + orgUsersWithCollections.Add((orgUser, invite.Collections)); } else { - orgUsers.Add(orgUser); + orgUsersWithoutCollections.Add(orgUser); } if (invite.Groups != null && invite.Groups.Any()) @@ -1162,10 +1116,14 @@ public class OrganizationService : IOrganizationService throw new AggregateException("One or more errors occurred while inviting users.", exceptions); } + var allOrgUsers = orgUsersWithoutCollections + .Concat(orgUsersWithCollections.Select(u => u.Item1)) + .ToList(); + try { - await _organizationUserRepository.CreateManyAsync(orgUsers); - foreach (var (orgUser, collections) in limitedCollectionOrgUsers) + await _organizationUserRepository.CreateManyAsync(orgUsersWithoutCollections); + foreach (var (orgUser, collections) in orgUsersWithCollections) { await _organizationUserRepository.CreateAsync(orgUser, collections); } @@ -1187,7 +1145,7 @@ public class OrganizationService : IOrganizationService await _updateSecretsManagerSubscriptionCommand.UpdateSubscriptionAsync(smSubscriptionUpdate); } - await SendInvitesAsync(orgUsers.Concat(limitedCollectionOrgUsers.Select(u => u.Item1)), organization); + await SendInvitesAsync(allOrgUsers, organization); await _referenceEventService.RaiseEventAsync( new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext) @@ -1198,7 +1156,7 @@ public class OrganizationService : IOrganizationService catch (Exception e) { // Revert any added users. - var invitedOrgUserIds = orgUsers.Select(u => u.Id).Concat(limitedCollectionOrgUsers.Select(u => u.Item1.Id)); + var invitedOrgUserIds = allOrgUsers.Select(ou => ou.Id); await _organizationUserRepository.DeleteManyAsync(invitedOrgUserIds); var currentOrganization = await _organizationRepository.GetByIdAsync(organization.Id); @@ -1227,7 +1185,7 @@ public class OrganizationService : IOrganizationService throw new AggregateException("One or more errors occurred while inviting users.", exceptions); } - return (orgUsers, events); + return (allOrgUsers, events); } public async Task>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, @@ -1337,7 +1295,10 @@ public class OrganizationService : IOrganizationService public async Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId, IUserService userService) { - var result = await ConfirmUsersAsync(organizationId, new Dictionary() { { organizationUserId, key } }, + var result = _featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization) + ? await ConfirmUsersAsync_vNext(organizationId, new Dictionary() { { organizationUserId, key } }, + confirmingUserId) + : await ConfirmUsersAsync(organizationId, new Dictionary() { { organizationUserId, key } }, confirmingUserId, userService); if (!result.Any()) @@ -1422,6 +1383,77 @@ public class OrganizationService : IOrganizationService return result; } + public async Task>> ConfirmUsersAsync_vNext(Guid organizationId, Dictionary keys, + Guid confirmingUserId) + { + var selectedOrganizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys); + var validSelectedOrganizationUsers = selectedOrganizationUsers + .Where(u => u.Status == OrganizationUserStatusType.Accepted && u.OrganizationId == organizationId && u.UserId != null) + .ToList(); + + if (!validSelectedOrganizationUsers.Any()) + { + return new List>(); + } + + var validSelectedUserIds = validSelectedOrganizationUsers.Select(u => u.UserId.Value).ToList(); + + var organization = await GetOrgById(organizationId); + var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds); + var users = await _userRepository.GetManyWithCalculatedPremiumAsync(validSelectedUserIds); + var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds); + + var keyedFilteredUsers = validSelectedOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u); + var keyedOrganizationUsers = allUsersOrgs.GroupBy(u => u.UserId.Value) + .ToDictionary(u => u.Key, u => u.ToList()); + + var succeededUsers = new List(); + var result = new List>(); + + foreach (var user in users) + { + if (!keyedFilteredUsers.ContainsKey(user.Id)) + { + continue; + } + var orgUser = keyedFilteredUsers[user.Id]; + var orgUsers = keyedOrganizationUsers.GetValueOrDefault(user.Id, new List()); + try + { + if (organization.PlanType == PlanType.Free && (orgUser.Type == OrganizationUserType.Admin + || orgUser.Type == 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(user.Id); + if (adminCount > 0) + { + throw new BadRequestException("User can only be an admin of one free organization."); + } + } + + var twoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled; + await CheckPolicies_vNext(organizationId, user, orgUsers, twoFactorEnabled); + orgUser.Status = OrganizationUserStatusType.Confirmed; + orgUser.Key = keys[orgUser.Id]; + orgUser.Email = null; + + await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); + await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), user.Email, orgUser.AccessSecretsManager); + await DeleteAndPushUserRegistrationAsync(organizationId, user.Id); + succeededUsers.Add(orgUser); + result.Add(Tuple.Create(orgUser, "")); + } + catch (BadRequestException e) + { + result.Add(Tuple.Create(orgUser, e.Message)); + } + } + + await _organizationUserRepository.ReplaceManyAsync(succeededUsers); + + return result; + } + internal async Task<(bool canScale, string failureReason)> CanScaleAsync( Organization organization, int seatsToAdd) @@ -1440,9 +1472,20 @@ public class OrganizationService : IOrganizationService var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id); - if (provider is { Enabled: true, Type: ProviderType.Reseller }) + if (provider is { Enabled: true }) { - return (false, "Seat limit has been reached. Contact your provider to purchase additional seats."); + var consolidatedBillingEnabled = _featureService.IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling); + + if (consolidatedBillingEnabled && provider.Type == ProviderType.Msp && + provider.Status == ProviderStatusType.Billable) + { + return (false, "Seat limit has been reached. Please contact your provider to add more seats."); + } + + if (provider.Type == ProviderType.Reseller) + { + return (false, "Seat limit has been reached. Contact your provider to purchase additional seats."); + } } if (organization.Seats.HasValue && @@ -1520,6 +1563,33 @@ public class OrganizationService : IOrganizationService } } + private async Task CheckPolicies_vNext(Guid organizationId, UserWithCalculatedPremium user, + ICollection userOrgs, bool twoFactorEnabled) + { + // Enforce Two Factor Authentication Policy for this organization + var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)) + .Any(p => p.OrganizationId == organizationId); + if (orgRequiresTwoFactor && !twoFactorEnabled) + { + throw new BadRequestException("User does not have two-step login enabled."); + } + + var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId); + var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg); + var otherSingleOrgPolicies = + singleOrgPolicies.Where(p => p.OrganizationId != organizationId); + // Enforce Single Organization Policy for this organization + if (hasOtherOrgs && singleOrgPolicies.Any(p => p.OrganizationId == organizationId)) + { + throw new BadRequestException("Cannot confirm this member to the organization until they leave or remove all other organizations."); + } + // Enforce Single Organization Policy of other organizations user is a member of + if (otherSingleOrgPolicies.Any()) + { + throw new BadRequestException("Cannot confirm this member to the organization because they are in another organization which forbids it."); + } + } + [Obsolete("IDeleteOrganizationUserCommand should be used instead. To be removed by EC-607.")] public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId) { @@ -1807,7 +1877,6 @@ public class OrganizationService : IOrganizationService { Emails = new List { user.Email }, Type = OrganizationUserType.User, - AccessAll = false, Collections = new List(), AccessSecretsManager = hasStandaloneSecretsManager }; @@ -2178,21 +2247,11 @@ public class OrganizationService : IOrganizationService return false; } - if (permissions.DeleteAssignedCollections && !await _currentContext.DeleteAssignedCollections(organizationId)) - { - return false; - } - if (permissions.EditAnyCollection && !await _currentContext.EditAnyCollection(organizationId)) { return false; } - if (permissions.EditAssignedCollections && !await _currentContext.EditAssignedCollections(organizationId)) - { - return false; - } - if (permissions.ManageResetPassword && !await _currentContext.ManageResetPassword(organizationId)) { return false; @@ -2365,7 +2424,21 @@ public class OrganizationService : IOrganizationService await AutoAddSeatsAsync(organization, 1); } - await CheckPoliciesBeforeRestoreAsync(organizationUser, userService); + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + var userTwoFactorIsEnabled = false; + // Only check Two Factor Authentication status if the user is linked to a user account + if (organizationUser.UserId.HasValue) + { + userTwoFactorIsEnabled = (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(new[] { organizationUser.UserId.Value })).FirstOrDefault().twoFactorIsEnabled; + } + + await CheckPoliciesBeforeRestoreAsync_vNext(organizationUser, userTwoFactorIsEnabled); + } + else + { + await CheckPoliciesBeforeRestoreAsync(organizationUser, userService); + } var status = GetPriorActiveOrganizationUserStatusType(organizationUser); @@ -2397,6 +2470,14 @@ public class OrganizationService : IOrganizationService deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId); } + // Query Two Factor Authentication status for all users in the organization + // This is an optimization to avoid querying the Two Factor Authentication status for each user individually + IEnumerable<(Guid userId, bool twoFactorIsEnabled)> organizationUsersTwoFactorEnabled = null; + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(filteredUsers.Select(ou => ou.UserId.Value)); + } + var result = new List>(); foreach (var organizationUser in filteredUsers) @@ -2418,7 +2499,15 @@ public class OrganizationService : IOrganizationService throw new BadRequestException("Only owners can restore other owners."); } - await CheckPoliciesBeforeRestoreAsync(organizationUser, userService); + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + var twoFactorIsEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(ou => ou.userId == organizationUser.UserId.Value).twoFactorIsEnabled; + await CheckPoliciesBeforeRestoreAsync_vNext(organizationUser, twoFactorIsEnabled); + } + else + { + await CheckPoliciesBeforeRestoreAsync(organizationUser, userService); + } var status = GetPriorActiveOrganizationUserStatusType(organizationUser); @@ -2484,6 +2573,52 @@ public class OrganizationService : IOrganizationService } } + private async Task CheckPoliciesBeforeRestoreAsync_vNext(OrganizationUser orgUser, bool userHasTwoFactorEnabled) + { + // An invited OrganizationUser isn't linked with a user account yet, so these checks are irrelevant + // The user will be subject to the same checks when they try to accept the invite + if (GetPriorActiveOrganizationUserStatusType(orgUser) == OrganizationUserStatusType.Invited) + { + return; + } + + var userId = orgUser.UserId.Value; + + // Enforce Single Organization Policy of organization user is being restored to + var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId); + var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId); + var singleOrgPoliciesApplyingToRevokedUsers = await _policyService.GetPoliciesApplicableToUserAsync(userId, + PolicyType.SingleOrg, OrganizationUserStatusType.Revoked); + var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId); + + if (hasOtherOrgs && singleOrgPolicyApplies) + { + throw new BadRequestException("You cannot restore this user until " + + "they leave or remove all other organizations."); + } + + // Enforce Single Organization Policy of other organizations user is a member of + var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId, + PolicyType.SingleOrg); + if (anySingleOrgPolicies) + { + throw new BadRequestException("You cannot restore this user because they are a member of " + + "another organization which forbids it"); + } + + // Enforce Two Factor Authentication Policy of organization user is trying to join + if (!userHasTwoFactorEnabled) + { + var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId, + PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited); + if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId)) + { + throw new BadRequestException("You cannot restore this user until they enable " + + "two-step login on their user account."); + } + } + } + static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusType(OrganizationUser organizationUser) { // Determine status to revert back to @@ -2525,10 +2660,6 @@ public class OrganizationService : IOrganizationService Key = null, Type = OrganizationUserType.Owner, Status = OrganizationUserStatusType.Invited, - - // If using Flexible Collections, AccessAll is deprecated and set to false. - // If not using Flexible Collections, set AccessAll to true (previous behavior) - AccessAll = !organization.FlexibleCollections, }; await _organizationUserRepository.CreateAsync(ownerOrganizationUser); @@ -2577,13 +2708,9 @@ public class OrganizationService : IOrganizationService if (!string.IsNullOrWhiteSpace(collectionName)) { - // If using Flexible Collections, give the owner Can Manage access over the default collection - List defaultOwnerAccess = null; - if (org.FlexibleCollections) - { - defaultOwnerAccess = - [new CollectionAccessSelection { Id = organizationUserId, HidePasswords = false, ReadOnly = false, Manage = true }]; - } + // give the owner Can Manage access over the default collection + List defaultOwnerAccess = + [new CollectionAccessSelection { Id = organizationUserId, HidePasswords = false, ReadOnly = false, Manage = true }]; var defaultCollection = new Collection { diff --git a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs index 38cd4f838..6d67cba70 100644 --- a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs +++ b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs @@ -4,6 +4,7 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -24,6 +25,8 @@ public class PolicyService : IPolicyService private readonly ISsoConfigRepository _ssoConfigRepository; private readonly IMailService _mailService; private readonly GlobalSettings _globalSettings; + private readonly IFeatureService _featureService; + private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; public PolicyService( IApplicationCacheService applicationCacheService, @@ -33,7 +36,9 @@ public class PolicyService : IPolicyService IPolicyRepository policyRepository, ISsoConfigRepository ssoConfigRepository, IMailService mailService, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + IFeatureService featureService, + ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) { _applicationCacheService = applicationCacheService; _eventService = eventService; @@ -43,6 +48,8 @@ public class PolicyService : IPolicyService _ssoConfigRepository = ssoConfigRepository; _mailService = mailService; _globalSettings = globalSettings; + _featureService = featureService; + _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; } public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService, @@ -81,6 +88,12 @@ public class PolicyService : IPolicyService return; } + if (_featureService.IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization)) + { + await EnablePolicy_vNext(policy, org, organizationService, savingUserId); + return; + } + await EnablePolicy(policy, org, userService, organizationService, savingUserId); return; } @@ -261,8 +274,7 @@ public class PolicyService : IPolicyService var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id); if (!currentPolicy?.Enabled ?? true) { - var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync( - policy.OrganizationId); + var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(policy.OrganizationId); var removableOrgUsers = orgUsers.Where(ou => ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked && ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin && @@ -311,4 +323,61 @@ public class PolicyService : IPolicyService await SetPolicyConfiguration(policy); } + + private async Task EnablePolicy_vNext(Policy policy, Organization org, IOrganizationService organizationService, Guid? savingUserId) + { + var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id); + if (!currentPolicy?.Enabled ?? true) + { + var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(policy.OrganizationId); + var organizationUsersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUsers); + var removableOrgUsers = orgUsers.Where(ou => + ou.Status != OrganizationUserStatusType.Invited && ou.Status != OrganizationUserStatusType.Revoked && + ou.Type != OrganizationUserType.Owner && ou.Type != OrganizationUserType.Admin && + ou.UserId != savingUserId); + switch (policy.Type) + { + case PolicyType.TwoFactorAuthentication: + // Reorder by HasMasterPassword to prioritize checking users without a master if they have 2FA enabled + foreach (var orgUser in removableOrgUsers.OrderBy(ou => ou.HasMasterPassword)) + { + var userTwoFactorEnabled = organizationUsersTwoFactorEnabled.FirstOrDefault(u => u.user.Id == orgUser.Id).twoFactorIsEnabled; + if (!userTwoFactorEnabled) + { + if (!orgUser.HasMasterPassword) + { + throw new BadRequestException( + "Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page."); + } + + await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id, + savingUserId); + await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync( + org.DisplayName(), orgUser.Email); + } + } + break; + case PolicyType.SingleOrg: + var userOrgs = await _organizationUserRepository.GetManyByManyUsersAsync( + removableOrgUsers.Select(ou => ou.UserId.Value)); + foreach (var orgUser in removableOrgUsers) + { + if (userOrgs.Any(ou => ou.UserId == orgUser.UserId + && ou.OrganizationId != org.Id + && ou.Status != OrganizationUserStatusType.Invited)) + { + await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id, + savingUserId); + await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync( + org.DisplayName(), orgUser.Email); + } + } + break; + default: + break; + } + } + + await SetPolicyConfiguration(policy); + } } diff --git a/src/Core/AdminConsole/Services/NoopImplementations/NoopProviderService.cs b/src/Core/AdminConsole/Services/NoopImplementations/NoopProviderService.cs index 26d8dae03..bd3a75766 100644 --- a/src/Core/AdminConsole/Services/NoopImplementations/NoopProviderService.cs +++ b/src/Core/AdminConsole/Services/NoopImplementations/NoopProviderService.cs @@ -7,7 +7,7 @@ namespace Bit.Core.AdminConsole.Services.NoopImplementations; public class NoopProviderService : IProviderService { - public Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) => throw new NotImplementedException(); + public Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TaxInfo taxInfo = null) => throw new NotImplementedException(); public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException(); diff --git a/src/Core/Auth/Exceptions/DuplicateAuthRequestException.cs b/src/Core/Auth/Exceptions/DuplicateAuthRequestException.cs index c3c674e02..1a0970362 100644 --- a/src/Core/Auth/Exceptions/DuplicateAuthRequestException.cs +++ b/src/Core/Auth/Exceptions/DuplicateAuthRequestException.cs @@ -1,6 +1,8 @@ -namespace Bit.Core.Auth.Exceptions; +using Bit.Core.Exceptions; -public class DuplicateAuthRequestException : Exception +namespace Bit.Core.Auth.Exceptions; + +public class DuplicateAuthRequestException : BadRequestException { public DuplicateAuthRequestException() : base("An authentication request with the same device already exists.") diff --git a/src/Core/Auth/Identity/EmailTokenProvider.cs b/src/Core/Auth/Identity/EmailTokenProvider.cs index 6ef473c4b..1db9e13ee 100644 --- a/src/Core/Auth/Identity/EmailTokenProvider.cs +++ b/src/Core/Auth/Identity/EmailTokenProvider.cs @@ -1,7 +1,6 @@ -using Bit.Core.Auth.Enums; -using Bit.Core.Auth.Models; +using System.Text; using Bit.Core.Entities; -using Bit.Core.Services; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; @@ -10,100 +9,55 @@ namespace Bit.Core.Auth.Identity; public class EmailTokenProvider : IUserTwoFactorTokenProvider { - private const string CacheKeyFormat = "Email_TOTP_{0}_{1}"; + private const string CacheKeyFormat = "EmailToken_{0}_{1}_{2}"; - private readonly IServiceProvider _serviceProvider; private readonly IDistributedCache _distributedCache; private readonly DistributedCacheEntryOptions _distributedCacheEntryOptions; public EmailTokenProvider( - IServiceProvider serviceProvider, [FromKeyedServices("persistent")] IDistributedCache distributedCache) { - _serviceProvider = serviceProvider; _distributedCache = distributedCache; _distributedCacheEntryOptions = new DistributedCacheEntryOptions { - AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(20) + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) }; } - public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) - { - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); - if (!HasProperMetaData(provider)) - { - return false; - } + public int TokenLength { get; protected set; } = 8; + public bool TokenAlpha { get; protected set; } = false; + public bool TokenNumeric { get; protected set; } = true; - return await _serviceProvider.GetRequiredService(). - TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Email, user); + public virtual Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + { + return Task.FromResult(!string.IsNullOrEmpty(user.Email)); } - public Task GenerateAsync(string purpose, UserManager manager, User user) + public virtual async Task GenerateAsync(string purpose, UserManager manager, User user) { - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); - if (!HasProperMetaData(provider)) - { - return null; - } - - return Task.FromResult(RedactEmail((string)provider.MetaData["Email"])); + var code = CoreHelpers.SecureRandomString(TokenLength, TokenAlpha, true, false, TokenNumeric, false); + var cacheKey = string.Format(CacheKeyFormat, user.Id, user.SecurityStamp, purpose); + await _distributedCache.SetAsync(cacheKey, Encoding.UTF8.GetBytes(code), _distributedCacheEntryOptions); + return code; } public async Task ValidateAsync(string purpose, string token, UserManager manager, User user) { - var cacheKey = string.Format(CacheKeyFormat, user.Id, token); + var cacheKey = string.Format(CacheKeyFormat, user.Id, user.SecurityStamp, purpose); var cachedValue = await _distributedCache.GetAsync(cacheKey); - if (cachedValue != null) + if (cachedValue == null) { return false; } - var valid = await _serviceProvider.GetRequiredService().VerifyTwoFactorEmailAsync(user, token); + var code = Encoding.UTF8.GetString(cachedValue); + var valid = string.Equals(token, code); if (valid) { - await _distributedCache.SetAsync(cacheKey, [1], _distributedCacheEntryOptions); + await _distributedCache.RemoveAsync(cacheKey); } return valid; } - - private bool HasProperMetaData(TwoFactorProvider provider) - { - return provider?.MetaData != null && provider.MetaData.ContainsKey("Email") && - !string.IsNullOrWhiteSpace((string)provider.MetaData["Email"]); - } - - private static string RedactEmail(string email) - { - var emailParts = email.Split('@'); - - string shownPart = null; - if (emailParts[0].Length > 2 && emailParts[0].Length <= 4) - { - shownPart = emailParts[0].Substring(0, 1); - } - else if (emailParts[0].Length > 4) - { - shownPart = emailParts[0].Substring(0, 2); - } - else - { - shownPart = string.Empty; - } - - string redactedPart = null; - if (emailParts[0].Length > 4) - { - redactedPart = new string('*', emailParts[0].Length - 2); - } - else - { - redactedPart = new string('*', emailParts[0].Length - shownPart.Length); - } - - return $"{shownPart}{redactedPart}@{emailParts[1]}"; - } } diff --git a/src/Core/Auth/Identity/EmailTwoFactorTokenProvider.cs b/src/Core/Auth/Identity/EmailTwoFactorTokenProvider.cs new file mode 100644 index 000000000..607d86a13 --- /dev/null +++ b/src/Core/Auth/Identity/EmailTwoFactorTokenProvider.cs @@ -0,0 +1,56 @@ +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models; +using Bit.Core.Entities; +using Bit.Core.Services; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Auth.Identity; + +public class EmailTwoFactorTokenProvider : EmailTokenProvider +{ + private readonly IServiceProvider _serviceProvider; + + public EmailTwoFactorTokenProvider( + IServiceProvider serviceProvider, + [FromKeyedServices("persistent")] + IDistributedCache distributedCache) : + base(distributedCache) + { + _serviceProvider = serviceProvider; + + TokenAlpha = false; + TokenNumeric = true; + TokenLength = 6; + } + + public override async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + { + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); + if (!HasProperMetaData(provider)) + { + return false; + } + + return await _serviceProvider.GetRequiredService(). + TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Email, user); + } + + public override Task GenerateAsync(string purpose, UserManager manager, User user) + { + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); + if (!HasProperMetaData(provider)) + { + return null; + } + + return base.GenerateAsync(purpose, manager, user); + } + + private static bool HasProperMetaData(TwoFactorProvider provider) + { + return provider?.MetaData != null && provider.MetaData.ContainsKey("Email") && + !string.IsNullOrWhiteSpace((string)provider.MetaData["Email"]); + } +} diff --git a/src/Core/Auth/Models/Api/Request/Accounts/RegisterVerificationEmailClickedRequestModel.cs b/src/Core/Auth/Models/Api/Request/Accounts/RegisterVerificationEmailClickedRequestModel.cs new file mode 100644 index 000000000..4de8d563c --- /dev/null +++ b/src/Core/Auth/Models/Api/Request/Accounts/RegisterVerificationEmailClickedRequestModel.cs @@ -0,0 +1,17 @@ +#nullable enable +using System.ComponentModel.DataAnnotations; +using Bit.Core.Utilities; + +namespace Bit.Core.Auth.Models.Api.Request.Accounts; + +public class RegisterVerificationEmailClickedRequestModel +{ + [Required] + [StrictEmailAddress] + [StringLength(256)] + public string Email { get; set; } + + [Required] + public string EmailVerificationToken { get; set; } + +} diff --git a/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs b/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs index 06990afea..b5f2b77cf 100644 --- a/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs +++ b/src/Core/Auth/Models/Api/Response/UserDecryptionOptions.cs @@ -54,18 +54,21 @@ public class TrustedDeviceUserDecryptionOption public bool HasAdminApproval { get; } public bool HasLoginApprovingDevice { get; } public bool HasManageResetPasswordPermission { get; } + public bool IsTdeOffboarding { get; } public string? EncryptedPrivateKey { get; } public string? EncryptedUserKey { get; } public TrustedDeviceUserDecryptionOption(bool hasAdminApproval, bool hasLoginApprovingDevice, bool hasManageResetPasswordPermission, + bool isTdeOffboarding, string? encryptedPrivateKey, string? encryptedUserKey) { HasAdminApproval = hasAdminApproval; HasLoginApprovingDevice = hasLoginApprovingDevice; HasManageResetPasswordPermission = hasManageResetPasswordPermission; + IsTdeOffboarding = isTdeOffboarding; EncryptedPrivateKey = encryptedPrivateKey; EncryptedUserKey = encryptedUserKey; } diff --git a/src/Core/Auth/Models/Business/Tokenables/RegistrationEmailVerificationTokenable.cs b/src/Core/Auth/Models/Business/Tokenables/RegistrationEmailVerificationTokenable.cs index 7d1a5832b..006da7008 100644 --- a/src/Core/Auth/Models/Business/Tokenables/RegistrationEmailVerificationTokenable.cs +++ b/src/Core/Auth/Models/Business/Tokenables/RegistrationEmailVerificationTokenable.cs @@ -55,4 +55,12 @@ public class RegistrationEmailVerificationTokenable : ExpiringTokenable && !string.IsNullOrWhiteSpace(Email); + public static bool ValidateToken(IDataProtectorTokenFactory dataProtectorTokenFactory, string token, string userEmail) + { + return dataProtectorTokenFactory.TryUnprotect(token, out var tokenable) + && tokenable.Valid + && tokenable.TokenIsValid(userEmail); + } + + } diff --git a/src/Core/Auth/Models/Business/Tokenables/TwoFactorAuthenticatorUserVerificationTokenable.cs b/src/Core/Auth/Models/Business/Tokenables/TwoFactorAuthenticatorUserVerificationTokenable.cs new file mode 100644 index 000000000..70a94f592 --- /dev/null +++ b/src/Core/Auth/Models/Business/Tokenables/TwoFactorAuthenticatorUserVerificationTokenable.cs @@ -0,0 +1,62 @@ +using Bit.Core.Entities; +using Bit.Core.Tokens; +using Newtonsoft.Json; + +namespace Bit.Core.Auth.Models.Business.Tokenables; + +/// +/// A tokenable object that gives a user the ability to update their authenticator two factor settings. +/// +/// +/// We protect two factor updates behind user verification (re-authentication) to protect against attacks of opportunity +/// (e.g. a user leaves their web vault unlocked). Most two factor options only require user verification (UV) when +/// enabling or disabling the option, retrieving the current status usually isn't a sensitive operation. However, +/// the status of authenticator two factor is sensitive because it reveals the user's secret key, which means both +/// operations must be protected by UV. +/// +/// TOTP as a UV option is only allowed to be used once, so we return this tokenable when retrieving the current status +/// (and secret key) of authenticator two factor to give the user a means of passing UV when updating (enabling/disabling). +/// +public class TwoFactorAuthenticatorUserVerificationTokenable : ExpiringTokenable +{ + private static readonly TimeSpan _tokenLifetime = TimeSpan.FromMinutes(30); + + public const string ClearTextPrefix = "TwoFactorAuthenticatorUserVerification"; + public const string DataProtectorPurpose = "TwoFactorAuthenticatorUserVerificationTokenDataProtector"; + public const string TokenIdentifier = "TwoFactorAuthenticatorUserVerificationToken"; + public string Identifier { get; set; } = TokenIdentifier; + public Guid UserId { get; set; } + public string Key { get; set; } + + public override bool Valid => Identifier == TokenIdentifier && + UserId != default; + + [JsonConstructor] + public TwoFactorAuthenticatorUserVerificationTokenable() + { + ExpirationDate = DateTime.UtcNow.Add(_tokenLifetime); + } + + public TwoFactorAuthenticatorUserVerificationTokenable(User user, string key) : this() + { + UserId = user?.Id ?? default; + Key = key; + } + + public bool TokenIsValid(User user, string key) + { + if (UserId == default + || user == null + || string.IsNullOrWhiteSpace(key)) + { + return false; + } + + return UserId == user.Id && Key == key; + } + + protected override bool TokenIsValid() => + Identifier == TokenIdentifier + && UserId != default + && !string.IsNullOrWhiteSpace(Key); +} diff --git a/src/Core/Auth/Repositories/Cosmos/Base64IdStringConverter.cs b/src/Core/Auth/Repositories/Cosmos/Base64IdStringConverter.cs index 5ec53100f..68ccf698f 100644 --- a/src/Core/Auth/Repositories/Cosmos/Base64IdStringConverter.cs +++ b/src/Core/Auth/Repositories/Cosmos/Base64IdStringConverter.cs @@ -2,17 +2,19 @@ using System.Text.Json.Serialization; using Bit.Core.Utilities; +#nullable enable + namespace Bit.Core.Auth.Repositories.Cosmos; -public class Base64IdStringConverter : JsonConverter +public class Base64IdStringConverter : JsonConverter { - public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ToKey(reader.GetString()); - public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) => writer.WriteStringValue(ToId(value)); - public static string ToId(string key) + public static string? ToId(string? key) { if (key == null) { @@ -21,7 +23,7 @@ public class Base64IdStringConverter : JsonConverter return CoreHelpers.TransformToBase64Url(key); } - public static string ToKey(string id) + public static string? ToKey(string? id) { if (id == null) { diff --git a/src/Core/Auth/Repositories/Cosmos/GrantRepository.cs b/src/Core/Auth/Repositories/Cosmos/GrantRepository.cs index 66fc3fb79..36356f8d3 100644 --- a/src/Core/Auth/Repositories/Cosmos/GrantRepository.cs +++ b/src/Core/Auth/Repositories/Cosmos/GrantRepository.cs @@ -6,6 +6,8 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.Azure.Cosmos; +#nullable enable + namespace Bit.Core.Auth.Repositories.Cosmos; public class GrantRepository : IGrantRepository @@ -34,7 +36,7 @@ public class GrantRepository : IGrantRepository _container = _database.GetContainer("grant"); } - public async Task GetByKeyAsync(string key) + public async Task GetByKeyAsync(string key) { var id = Base64IdStringConverter.ToId(key); try diff --git a/src/Core/Auth/Repositories/IAuthRequestRepository.cs b/src/Core/Auth/Repositories/IAuthRequestRepository.cs index 6662dd15f..3b01a452f 100644 --- a/src/Core/Auth/Repositories/IAuthRequestRepository.cs +++ b/src/Core/Auth/Repositories/IAuthRequestRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Data; +#nullable enable + namespace Bit.Core.Repositories; public interface IAuthRequestRepository : IRepository diff --git a/src/Core/Auth/Repositories/IEmergencyAccessRepository.cs b/src/Core/Auth/Repositories/IEmergencyAccessRepository.cs index e22e93488..6edb941d3 100644 --- a/src/Core/Auth/Repositories/IEmergencyAccessRepository.cs +++ b/src/Core/Auth/Repositories/IEmergencyAccessRepository.cs @@ -2,6 +2,8 @@ using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.UserFeatures.UserKey; +#nullable enable + namespace Bit.Core.Repositories; public interface IEmergencyAccessRepository : IRepository @@ -9,7 +11,7 @@ public interface IEmergencyAccessRepository : IRepository Task GetCountByGrantorIdEmailAsync(Guid grantorId, string email, bool onlyRegisteredUsers); Task> GetManyDetailsByGrantorIdAsync(Guid grantorId); Task> GetManyDetailsByGranteeIdAsync(Guid granteeId); - Task GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId); + Task GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId); Task> GetManyToNotifyAsync(); Task> GetExpiredRecoveriesAsync(); diff --git a/src/Core/Auth/Repositories/IGrantRepository.cs b/src/Core/Auth/Repositories/IGrantRepository.cs index 2304385be..7ed01fadd 100644 --- a/src/Core/Auth/Repositories/IGrantRepository.cs +++ b/src/Core/Auth/Repositories/IGrantRepository.cs @@ -1,10 +1,12 @@ using Bit.Core.Auth.Models.Data; +#nullable enable + namespace Bit.Core.Auth.Repositories; public interface IGrantRepository { - Task GetByKeyAsync(string key); + Task GetByKeyAsync(string key); Task> GetManyAsync(string subjectId, string sessionId, string clientId, string type); Task SaveAsync(IGrant obj); Task DeleteByKeyAsync(string key); diff --git a/src/Core/Auth/Repositories/ISsoConfigRepository.cs b/src/Core/Auth/Repositories/ISsoConfigRepository.cs index 2ed1a15ea..f5d6c5201 100644 --- a/src/Core/Auth/Repositories/ISsoConfigRepository.cs +++ b/src/Core/Auth/Repositories/ISsoConfigRepository.cs @@ -1,11 +1,13 @@ using Bit.Core.Auth.Entities; using Bit.Core.Repositories; +#nullable enable + namespace Bit.Core.Auth.Repositories; public interface ISsoConfigRepository : IRepository { - Task GetByOrganizationIdAsync(Guid organizationId); - Task GetByIdentifierAsync(string identifier); + Task GetByOrganizationIdAsync(Guid organizationId); + Task GetByIdentifierAsync(string identifier); Task> GetManyByRevisionNotBeforeDate(DateTime? notBefore); } diff --git a/src/Core/Auth/Repositories/ISsoUserRepository.cs b/src/Core/Auth/Repositories/ISsoUserRepository.cs index 9c97cfc94..6691b033a 100644 --- a/src/Core/Auth/Repositories/ISsoUserRepository.cs +++ b/src/Core/Auth/Repositories/ISsoUserRepository.cs @@ -1,10 +1,12 @@ using Bit.Core.Auth.Entities; using Bit.Core.Repositories; +#nullable enable + namespace Bit.Core.Auth.Repositories; public interface ISsoUserRepository : IRepository { Task DeleteAsync(Guid userId, Guid? organizationId); - Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId); + Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId); } diff --git a/src/Core/Auth/Repositories/IWebAuthnCredentialRepository.cs b/src/Core/Auth/Repositories/IWebAuthnCredentialRepository.cs index 1fab56d07..9a7fc8820 100644 --- a/src/Core/Auth/Repositories/IWebAuthnCredentialRepository.cs +++ b/src/Core/Auth/Repositories/IWebAuthnCredentialRepository.cs @@ -3,11 +3,13 @@ using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Repositories; +#nullable enable + namespace Bit.Core.Auth.Repositories; public interface IWebAuthnCredentialRepository : IRepository { - Task GetByIdAsync(Guid id, Guid userId); + Task GetByIdAsync(Guid id, Guid userId); Task> GetManyByUserIdAsync(Guid userId); Task UpdateAsync(WebAuthnCredential credential); UpdateEncryptedDataForKeyRotation UpdateKeysForRotationAsync(Guid userId, IEnumerable credentials); diff --git a/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs b/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs index db14db7fe..38df8e598 100644 --- a/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs +++ b/src/Core/Auth/Services/Implementations/EmergencyAccessService.cs @@ -33,10 +33,6 @@ public class EmergencyAccessService : IEmergencyAccessService private readonly IPasswordHasher _passwordHasher; private readonly IOrganizationService _organizationService; private readonly IDataProtectorTokenFactory _dataProtectorTokenizer; - private readonly IFeatureService _featureService; - - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); public EmergencyAccessService( IEmergencyAccessRepository emergencyAccessRepository, @@ -50,8 +46,7 @@ public class EmergencyAccessService : IEmergencyAccessService IPasswordHasher passwordHasher, GlobalSettings globalSettings, IOrganizationService organizationService, - IDataProtectorTokenFactory dataProtectorTokenizer, - IFeatureService featureService) + IDataProtectorTokenFactory dataProtectorTokenizer) { _emergencyAccessRepository = emergencyAccessRepository; _organizationUserRepository = organizationUserRepository; @@ -65,7 +60,6 @@ public class EmergencyAccessService : IEmergencyAccessService _globalSettings = globalSettings; _organizationService = organizationService; _dataProtectorTokenizer = dataProtectorTokenizer; - _featureService = featureService; } public async Task InviteAsync(User invitingUser, string email, EmergencyAccessType type, int waitTime) @@ -393,7 +387,7 @@ public class EmergencyAccessService : IEmergencyAccessService throw new BadRequestException("Emergency Access not valid."); } - var ciphers = await _cipherRepository.GetManyByUserIdAsync(emergencyAccess.GrantorId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: false); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(emergencyAccess.GrantorId, withOrganizations: false); return new EmergencyAccessViewData { diff --git a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs index 6ca6307a6..9d6a3bb3b 100644 --- a/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs +++ b/src/Core/Auth/UserFeatures/Registration/Implementations/RegisterUserCommand.cs @@ -37,6 +37,8 @@ public class RegisterUserCommand : IRegisterUserCommand private readonly IUserService _userService; private readonly IMailService _mailService; + private readonly string _disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator."; + public RegisterUserCommand( IGlobalSettings globalSettings, IOrganizationUserRepository organizationUserRepository, @@ -124,8 +126,6 @@ public class RegisterUserCommand : IRegisterUserCommand private void ValidateOrgInviteToken(string orgInviteToken, Guid? orgUserId, User user) { - const string disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator."; - var orgInviteTokenProvided = !string.IsNullOrWhiteSpace(orgInviteToken); if (orgInviteTokenProvided && orgUserId.HasValue) @@ -140,7 +140,7 @@ public class RegisterUserCommand : IRegisterUserCommand if (_globalSettings.DisableUserRegistration) { - throw new BadRequestException(disabledUserRegistrationExceptionMsg); + throw new BadRequestException(_disabledUserRegistrationExceptionMsg); } throw new BadRequestException("Organization invite token is invalid."); @@ -152,7 +152,7 @@ public class RegisterUserCommand : IRegisterUserCommand // as you can't register without them. if (_globalSettings.DisableUserRegistration) { - throw new BadRequestException(disabledUserRegistrationExceptionMsg); + throw new BadRequestException(_disabledUserRegistrationExceptionMsg); } // Open registration is allowed @@ -233,6 +233,14 @@ public class RegisterUserCommand : IRegisterUserCommand public async Task RegisterUserViaEmailVerificationToken(User user, string masterPasswordHash, string emailVerificationToken) { + // We validate open registration on send of initial email and here b/c a user could technically start the + // account creation process while open registration is enabled and then finish it after it has been + // disabled by the self hosted admin. + if (_globalSettings.DisableUserRegistration) + { + throw new BadRequestException(_disabledUserRegistrationExceptionMsg); + } + var tokenable = ValidateRegistrationEmailVerificationTokenable(emailVerificationToken, user.Email); user.EmailVerified = true; diff --git a/src/Core/Auth/UserFeatures/Registration/Implementations/SendVerificationEmailForRegistrationCommand.cs b/src/Core/Auth/UserFeatures/Registration/Implementations/SendVerificationEmailForRegistrationCommand.cs index d1cdca5e5..21a421b9d 100644 --- a/src/Core/Auth/UserFeatures/Registration/Implementations/SendVerificationEmailForRegistrationCommand.cs +++ b/src/Core/Auth/UserFeatures/Registration/Implementations/SendVerificationEmailForRegistrationCommand.cs @@ -39,6 +39,11 @@ public class SendVerificationEmailForRegistrationCommand : ISendVerificationEmai public async Task Run(string email, string? name, bool receiveMarketingEmails) { + if (_globalSettings.DisableUserRegistration) + { + throw new BadRequestException("Open registration has been disabled by the system administrator."); + } + if (string.IsNullOrWhiteSpace(email)) { throw new ArgumentNullException(nameof(email)); diff --git a/src/Core/Auth/UserFeatures/TdeOffboardingPassword/Interfaces/ITdeOffboardingPasswordCommand.cs b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/Interfaces/ITdeOffboardingPasswordCommand.cs new file mode 100644 index 000000000..1ff64ffab --- /dev/null +++ b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/Interfaces/ITdeOffboardingPasswordCommand.cs @@ -0,0 +1,14 @@ +using Bit.Core.Entities; +using Microsoft.AspNetCore.Identity; + +namespace Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; + +/// +/// Manages the setting of the master password for JIT provisioned TDE in an organization, after the organization disabled TDE. +/// This command is invoked, when the user first logs in after the organization has switched from TDE to master password based decryption. +/// +public interface ITdeOffboardingPasswordCommand +{ + public Task UpdateTdeOffboardingPasswordAsync(User user, string masterPassword, string key, + string orgSsoIdentifier); +} diff --git a/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs new file mode 100644 index 000000000..d33db18e4 --- /dev/null +++ b/src/Core/Auth/UserFeatures/TdeOffboardingPassword/TdeOffboardingPasswordCommand.cs @@ -0,0 +1,99 @@ +using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Identity; + +namespace Bit.Core.Auth.UserFeatures.UserMasterPassword; + +public class TdeOffboardingPasswordCommand : ITdeOffboardingPasswordCommand +{ + private readonly IUserService _userService; + private readonly IUserRepository _userRepository; + private readonly IEventService _eventService; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ISsoUserRepository _ssoUserRepository; + private readonly ISsoConfigRepository _ssoConfigRepository; + private readonly IPushNotificationService _pushService; + + + public TdeOffboardingPasswordCommand( + IUserService userService, + IUserRepository userRepository, + IEventService eventService, + IOrganizationUserRepository organizationUserRepository, + ISsoUserRepository ssoUserRepository, + ISsoConfigRepository ssoConfigRepository, + IPushNotificationService pushService) + { + _userService = userService; + _userRepository = userRepository; + _eventService = eventService; + _organizationUserRepository = organizationUserRepository; + _ssoUserRepository = ssoUserRepository; + _ssoConfigRepository = ssoConfigRepository; + _pushService = pushService; + } + + public async Task UpdateTdeOffboardingPasswordAsync(User user, string newMasterPassword, string key, string hint) + { + if (string.IsNullOrWhiteSpace(newMasterPassword)) + { + throw new BadRequestException("Master password is required."); + } + + if (string.IsNullOrWhiteSpace(key)) + { + throw new BadRequestException("Key is required."); + } + + if (user.HasMasterPassword()) + { + throw new BadRequestException("User already has a master password."); + } + var orgUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id); + orgUserDetails = orgUserDetails.Where(x => x.UseSso).ToList(); + if (orgUserDetails.Count == 0) + { + throw new BadRequestException("User is not part of any organization that has SSO enabled."); + } + + var orgSSOUsers = await Task.WhenAll(orgUserDetails.Select(async x => await _ssoUserRepository.GetByUserIdOrganizationIdAsync(x.OrganizationId, user.Id))); + if (orgSSOUsers.Length != 1) + { + throw new BadRequestException("User is part of no or multiple SSO configurations."); + } + + var orgUser = orgUserDetails.First(); + var orgSSOConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgUser.OrganizationId); + if (orgSSOConfig == null) + { + throw new BadRequestException("Organization SSO configuration not found."); + } + else if (orgSSOConfig.GetData().MemberDecryptionType != Enums.MemberDecryptionType.MasterPassword) + { + throw new BadRequestException("Organization SSO Member Decryption Type is not Master Password."); + } + + var result = await _userService.UpdatePasswordHash(user, newMasterPassword); + if (!result.Succeeded) + { + return result; + } + + user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; + user.ForcePasswordReset = false; + user.Key = key; + user.MasterPasswordHint = hint; + + await _userRepository.ReplaceAsync(user); + await _eventService.LogUserEventAsync(user.Id, EventType.User_UpdatedTempPassword); + await _pushService.PushLogOutAsync(user.Id); + + return IdentityResult.Success; + } + +} diff --git a/src/Core/Auth/UserFeatures/TwoFactorAuth/Interfaces/ITwoFactorIsEnabledQuery.cs b/src/Core/Auth/UserFeatures/TwoFactorAuth/Interfaces/ITwoFactorIsEnabledQuery.cs new file mode 100644 index 000000000..203ef3acc --- /dev/null +++ b/src/Core/Auth/UserFeatures/TwoFactorAuth/Interfaces/ITwoFactorIsEnabledQuery.cs @@ -0,0 +1,23 @@ +using Bit.Core.Auth.Models; + +namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; + +public interface ITwoFactorIsEnabledQuery +{ + /// + /// Returns a list of user IDs and whether two factor is enabled for each user. + /// + /// The list of user IDs to check. + Task> TwoFactorIsEnabledAsync(IEnumerable userIds); + /// + /// Returns a list of users and whether two factor is enabled for each user. + /// + /// The list of users to check. + /// The type of user in the list. Must implement . + Task> TwoFactorIsEnabledAsync(IEnumerable users) where T : ITwoFactorProvidersUser; + /// + /// Returns whether two factor is enabled for the user. + /// + /// The user to check. + Task TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user); +} diff --git a/src/Core/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQuery.cs b/src/Core/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQuery.cs new file mode 100644 index 000000000..bda2094f2 --- /dev/null +++ b/src/Core/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQuery.cs @@ -0,0 +1,123 @@ +using Bit.Core.Auth.Models; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; +using Bit.Core.Repositories; + +namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth; + +public class TwoFactorIsEnabledQuery : ITwoFactorIsEnabledQuery +{ + private readonly IUserRepository _userRepository; + + public TwoFactorIsEnabledQuery(IUserRepository userRepository) + { + _userRepository = userRepository; + } + + public async Task> TwoFactorIsEnabledAsync(IEnumerable userIds) + { + var result = new List<(Guid userId, bool hasTwoFactor)>(); + if (userIds == null || !userIds.Any()) + { + return result; + } + + var userDetails = await _userRepository.GetManyWithCalculatedPremiumAsync(userIds.ToList()); + + foreach (var userDetail in userDetails) + { + var hasTwoFactor = false; + var providers = userDetail.GetTwoFactorProviders(); + if (providers != null) + { + // Get all enabled providers + var enabledProviderKeys = from provider in providers + where provider.Value?.Enabled ?? false + select provider.Key; + + // Find the first provider that is enabled and passes the premium check + hasTwoFactor = enabledProviderKeys + .Select(type => userDetail.HasPremiumAccess || !TwoFactorProvider.RequiresPremium(type)) + .FirstOrDefault(); + } + + result.Add((userDetail.Id, hasTwoFactor)); + } + + return result; + } + + public async Task> TwoFactorIsEnabledAsync(IEnumerable users) where T : ITwoFactorProvidersUser + { + var userIds = users + .Select(u => u.GetUserId()) + .Where(u => u.HasValue) + .Select(u => u.Value) + .ToList(); + + var twoFactorResults = await TwoFactorIsEnabledAsync(userIds); + + var result = new List<(T user, bool twoFactorIsEnabled)>(); + + foreach (var user in users) + { + var userId = user.GetUserId(); + if (userId.HasValue) + { + var hasTwoFactor = twoFactorResults.FirstOrDefault(res => res.userId == userId.Value).twoFactorIsEnabled; + result.Add((user, hasTwoFactor)); + } + else + { + result.Add((user, false)); + } + } + + return result; + } + + public async Task TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user) + { + var userId = user.GetUserId(); + if (!userId.HasValue) + { + return false; + } + + var providers = user.GetTwoFactorProviders(); + if (providers == null || !providers.Any()) + { + return false; + } + + // Get all enabled providers + var enabledProviderKeys = providers + .Where(provider => provider.Value?.Enabled ?? false) + .Select(provider => provider.Key); + + if (!enabledProviderKeys.Any()) + { + return false; + } + + // Determine if any enabled provider passes the premium check + var hasTwoFactor = enabledProviderKeys + .Select(type => user.GetPremium() || !TwoFactorProvider.RequiresPremium(type)) + .FirstOrDefault(); + + // If no enabled provider passes the check, check the repository for organization premium access + if (!hasTwoFactor) + { + var userDetails = await _userRepository.GetManyWithCalculatedPremiumAsync(new List { userId.Value }); + var userDetail = userDetails.FirstOrDefault(); + + if (userDetail != null) + { + hasTwoFactor = enabledProviderKeys + .Select(type => userDetail.HasPremiumAccess || !TwoFactorProvider.RequiresPremium(type)) + .FirstOrDefault(); + } + } + + return hasTwoFactor; + } +} diff --git a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs index cbe7b0d4e..2469c124b 100644 --- a/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs +++ b/src/Core/Auth/UserFeatures/UserServiceCollectionExtensions.cs @@ -2,6 +2,9 @@ using Bit.Core.Auth.UserFeatures.Registration; using Bit.Core.Auth.UserFeatures.Registration.Implementations; +using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Auth.UserFeatures.UserKey.Implementations; using Bit.Core.Auth.UserFeatures.UserMasterPassword; @@ -22,6 +25,8 @@ public static class UserServiceCollectionExtensions services.AddUserPasswordCommands(); services.AddUserRegistrationCommands(); services.AddWebAuthnLoginCommands(); + services.AddTdeOffboardingPasswordCommands(); + services.AddTwoFactorQueries(); } public static void AddUserKeyCommands(this IServiceCollection services, IGlobalSettings globalSettings) @@ -34,6 +39,11 @@ public static class UserServiceCollectionExtensions services.AddScoped(); } + private static void AddTdeOffboardingPasswordCommands(this IServiceCollection services) + { + services.AddScoped(); + } + private static void AddUserRegistrationCommands(this IServiceCollection services) { services.AddScoped(); @@ -47,4 +57,9 @@ public static class UserServiceCollectionExtensions services.AddScoped(); services.AddScoped(); } + + private static void AddTwoFactorQueries(this IServiceCollection services) + { + services.AddScoped(); + } } diff --git a/src/Core/Billing/BillingException.cs b/src/Core/Billing/BillingException.cs index a6944b3ed..cdb3ce6b5 100644 --- a/src/Core/Billing/BillingException.cs +++ b/src/Core/Billing/BillingException.cs @@ -1,9 +1,9 @@ namespace Bit.Core.Billing; public class BillingException( - string clientFriendlyMessage, - string internalMessage = null, - Exception innerException = null) : Exception(internalMessage, innerException) + string response = null, + string message = null, + Exception innerException = null) : Exception(message, innerException) { - public string ClientFriendlyMessage { get; set; } = clientFriendlyMessage; + public string Response { get; } = response ?? "Something went wrong with your request. Please contact support."; } diff --git a/src/Core/Billing/Constants/StripeConstants.cs b/src/Core/Billing/Constants/StripeConstants.cs index aa5737e3d..53e9baf06 100644 --- a/src/Core/Billing/Constants/StripeConstants.cs +++ b/src/Core/Billing/Constants/StripeConstants.cs @@ -21,6 +21,12 @@ public static class StripeConstants public const string SecretsManagerStandalone = "sm-standalone"; } + public static class ErrorCodes + { + public const string CustomerTaxLocationInvalid = "customer_tax_location_invalid"; + public const string TaxIdInvalid = "tax_id_invalid"; + } + public static class PaymentMethodTypes { public const string Card = "card"; diff --git a/src/Core/Billing/Extensions/BillingExtensions.cs b/src/Core/Billing/Extensions/BillingExtensions.cs index c3ba756ed..d6fa0988b 100644 --- a/src/Core/Billing/Extensions/BillingExtensions.cs +++ b/src/Core/Billing/Extensions/BillingExtensions.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Billing.Enums; +using Bit.Core.Entities; using Bit.Core.Enums; using Stripe; @@ -24,9 +25,9 @@ public static class BillingExtensions PlanType: PlanType.TeamsMonthly or PlanType.EnterpriseMonthly }; - public static bool IsStripeEnabled(this Organization organization) - => !string.IsNullOrEmpty(organization.GatewayCustomerId) && - !string.IsNullOrEmpty(organization.GatewaySubscriptionId); + public static bool IsStripeEnabled(this ISubscriber subscriber) + => !string.IsNullOrEmpty(subscriber.GatewayCustomerId) && + !string.IsNullOrEmpty(subscriber.GatewaySubscriptionId); public static bool IsUnverifiedBankAccount(this SetupIntent setupIntent) => setupIntent is diff --git a/src/Core/Billing/Models/Api/Requests/Accounts/TrialSendVerificationEmailRequestModel.cs b/src/Core/Billing/Models/Api/Requests/Accounts/TrialSendVerificationEmailRequestModel.cs new file mode 100644 index 000000000..2e8780e6a --- /dev/null +++ b/src/Core/Billing/Models/Api/Requests/Accounts/TrialSendVerificationEmailRequestModel.cs @@ -0,0 +1,10 @@ +using Bit.Core.Auth.Models.Api.Request.Accounts; +using Bit.Core.Billing.Enums; + +namespace Bit.Core.Billing.Models.Api.Requests.Accounts; + +public class TrialSendVerificationEmailRequestModel : RegisterSendVerificationEmailRequestModel +{ + public ProductTierType ProductTier { get; set; } + public IEnumerable Products { get; set; } +} diff --git a/src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs b/src/Core/Billing/Models/Business/SponsorOrganizationSubscriptionUpdate.cs similarity index 86% rename from src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs rename to src/Core/Billing/Models/Business/SponsorOrganizationSubscriptionUpdate.cs index 59a745297..830105e37 100644 --- a/src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs +++ b/src/Core/Billing/Models/Business/SponsorOrganizationSubscriptionUpdate.cs @@ -1,6 +1,7 @@ -using Stripe; +using Bit.Core.Models.Business; +using Stripe; -namespace Bit.Core.Models.Business; +namespace Bit.Core.Billing.Models.Business; public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate { @@ -9,10 +10,11 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate private readonly bool _applySponsorship; protected override List PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId }; - public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship) + public SponsorOrganizationSubscriptionUpdate(Core.Models.StaticStore.Plan existingPlan, Core.Models.StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship) { _existingPlanStripeId = existingPlan.PasswordManager.StripePlanId; - _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId; + _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId + ?? Core.Utilities.StaticStore.SponsoredPlans.FirstOrDefault()?.StripePlanId; _applySponsorship = applySponsorship; } diff --git a/src/Core/Billing/Models/ConfiguredProviderPlanDTO.cs b/src/Core/Billing/Models/ConfiguredProviderPlan.cs similarity index 78% rename from src/Core/Billing/Models/ConfiguredProviderPlanDTO.cs rename to src/Core/Billing/Models/ConfiguredProviderPlan.cs index d8ada5716..dadb17653 100644 --- a/src/Core/Billing/Models/ConfiguredProviderPlanDTO.cs +++ b/src/Core/Billing/Models/ConfiguredProviderPlan.cs @@ -3,7 +3,7 @@ using Bit.Core.Billing.Enums; namespace Bit.Core.Billing.Models; -public record ConfiguredProviderPlanDTO( +public record ConfiguredProviderPlan( Guid Id, Guid ProviderId, PlanType PlanType, @@ -11,9 +11,9 @@ public record ConfiguredProviderPlanDTO( int PurchasedSeats, int AssignedSeats) { - public static ConfiguredProviderPlanDTO From(ProviderPlan providerPlan) => + public static ConfiguredProviderPlan From(ProviderPlan providerPlan) => providerPlan.IsConfigured() - ? new ConfiguredProviderPlanDTO( + ? new ConfiguredProviderPlan( providerPlan.Id, providerPlan.ProviderId, providerPlan.PlanType, diff --git a/src/Core/Billing/Models/ConsolidatedBillingSubscriptionDTO.cs b/src/Core/Billing/Models/ConsolidatedBillingSubscriptionDTO.cs deleted file mode 100644 index 4b2f46adc..000000000 --- a/src/Core/Billing/Models/ConsolidatedBillingSubscriptionDTO.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Stripe; - -namespace Bit.Core.Billing.Models; - -public record ConsolidatedBillingSubscriptionDTO( - List ProviderPlans, - Subscription Subscription, - TaxInformationDTO TaxInformation, - SubscriptionSuspensionDTO Suspension); diff --git a/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs b/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs new file mode 100644 index 000000000..df0829608 --- /dev/null +++ b/src/Core/Billing/Models/Mail/TrialInititaionVerifyEmail.cs @@ -0,0 +1,33 @@ +using Bit.Core.Auth.Models.Mail; +using Bit.Core.Billing.Enums; + +namespace Bit.Core.Billing.Models.Mail; + +public class TrialInitiationVerifyEmail : RegisterVerifyEmail +{ + /// + /// See comment on . + /// + public new string Url + { + get => $"{WebVaultUrl}/{Route}" + + $"?token={Token}" + + $"&email={Email}" + + $"&fromEmail=true" + + $"&productTier={(int)ProductTier}" + + $"&product={string.Join(",", Product.Select(p => (int)p))}"; + } + + public ProductTierType ProductTier { get; set; } + + public IEnumerable Product { get; set; } + + /// + /// Currently we only support one product type at a time, despite Product being a collection. + /// If we receive both PasswordManager and SecretsManager, we'll send the user to the PM trial route + /// + private string Route => + Product.Any(p => p == ProductType.PasswordManager) + ? "trial-initiation" + : "secrets-manager-trial-initiation"; +} diff --git a/src/Core/Billing/Models/PaymentInformationDTO.cs b/src/Core/Billing/Models/PaymentInformationDTO.cs index fe3195b3e..897d6a950 100644 --- a/src/Core/Billing/Models/PaymentInformationDTO.cs +++ b/src/Core/Billing/Models/PaymentInformationDTO.cs @@ -3,4 +3,4 @@ public record PaymentInformationDTO( long AccountCredit, MaskedPaymentMethodDTO PaymentMethod, - TaxInformationDTO TaxInformation); + TaxInformation TaxInformation); diff --git a/src/Core/Billing/Models/SubscriptionSuspensionDTO.cs b/src/Core/Billing/Models/SubscriptionSuspension.cs similarity index 75% rename from src/Core/Billing/Models/SubscriptionSuspensionDTO.cs rename to src/Core/Billing/Models/SubscriptionSuspension.cs index ac0261f2c..889c6e2be 100644 --- a/src/Core/Billing/Models/SubscriptionSuspensionDTO.cs +++ b/src/Core/Billing/Models/SubscriptionSuspension.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Billing.Models; -public record SubscriptionSuspensionDTO( +public record SubscriptionSuspension( DateTime SuspensionDate, DateTime UnpaidPeriodEndDate, int GracePeriod); diff --git a/src/Core/Billing/Models/TaxInformationDTO.cs b/src/Core/Billing/Models/TaxInformation.cs similarity index 99% rename from src/Core/Billing/Models/TaxInformationDTO.cs rename to src/Core/Billing/Models/TaxInformation.cs index a5243b9ea..a2e6e187f 100644 --- a/src/Core/Billing/Models/TaxInformationDTO.cs +++ b/src/Core/Billing/Models/TaxInformation.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Billing.Models; -public record TaxInformationDTO( +public record TaxInformation( string Country, string PostalCode, string TaxId, diff --git a/src/Core/Billing/Services/IProviderBillingService.cs b/src/Core/Billing/Services/IProviderBillingService.cs index 5c215bd71..1235fcd5f 100644 --- a/src/Core/Billing/Services/IProviderBillingService.cs +++ b/src/Core/Billing/Services/IProviderBillingService.cs @@ -3,8 +3,8 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Models; using Bit.Core.Models.Business; +using Stripe; namespace Bit.Core.Billing.Services; @@ -24,16 +24,6 @@ public interface IProviderBillingService Organization organization, int seats); - /// - /// Create a Stripe for the specified utilizing the provided . - /// - /// The to create a Stripe customer for. - /// The to use for calculating the customer's automatic tax. - /// - Task CreateCustomer( - Provider provider, - TaxInfo taxInfo); - /// /// Create a Stripe for the provided client utilizing /// the address and tax information of its . @@ -65,15 +55,6 @@ public interface IProviderBillingService Guid providerId, PlanType planType); - /// - /// Retrieves the 's consolidated billing subscription, which includes their Stripe subscription and configured provider plans. - /// - /// The provider to retrieve the consolidated billing subscription for. - /// A containing the provider's Stripe and a list of s representing their configured plans. - /// This method opts for returning rather than throwing exceptions, making it ideal for surfacing data from API endpoints. - Task GetConsolidatedBillingSubscription( - Provider provider); - /// /// Scales the 's seats for the specified using the provided . /// This operation may autoscale the provider's Stripe depending on the 's seat minimum for the @@ -88,11 +69,28 @@ public interface IProviderBillingService int seatAdjustment); /// - /// Starts a Stripe for the given given it has an existing Stripe . + /// For use during the provider setup process, this method creates a Stripe for the specified utilizing the provided . + /// + /// The to create a Stripe customer for. + /// The to use for calculating the customer's automatic tax. + /// The newly created for the . + Task SetupCustomer( + Provider provider, + TaxInfo taxInfo); + + /// + /// For use during the provider setup process, this method starts a Stripe for the given . /// subscriptions will always be started with a for both the /// and plan, and the quantity for each item will be equal the provider's seat minimum for each respective plan. /// /// The provider to create the for. - Task StartSubscription( + /// The newly created for the . + /// This method requires the to already have a linked Stripe via its field. + Task SetupSubscription( Provider provider); + + Task UpdateSeatMinimums( + Provider provider, + int enterpriseSeatMinimum, + int teamsSeatMinimum); } diff --git a/src/Core/Billing/Services/ISubscriberService.cs b/src/Core/Billing/Services/ISubscriberService.cs index 115bd6f32..5183d49be 100644 --- a/src/Core/Billing/Services/ISubscriberService.cs +++ b/src/Core/Billing/Services/ISubscriberService.cs @@ -1,7 +1,6 @@ using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Models.BitStripe; using Stripe; namespace Bit.Core.Billing.Services; @@ -47,18 +46,6 @@ public interface ISubscriberService ISubscriber subscriber, CustomerGetOptions customerGetOptions = null); - /// - /// Retrieves a list of Stripe objects using the 's property. - /// - /// The subscriber to retrieve the Stripe invoices for. - /// Optional parameters that can be passed to Stripe to expand, modify or filter the invoices. The 's - /// will be automatically attached to the provided options as the parameter. - /// A list of Stripe objects. - /// This method opts for returning an empty list rather than throwing exceptions, making it ideal for surfacing data from API endpoints. - Task> GetInvoices( - ISubscriber subscriber, - StripeInvoiceListOptions invoiceListOptions = null); - /// /// Retrieves the account credit, a masked representation of the default payment method and the tax information for the /// provided . This is essentially a consolidated invocation of the @@ -106,10 +93,10 @@ public interface ISubscriberService /// Retrieves the 's tax information using their Stripe 's . /// /// The subscriber to retrieve the tax information for. - /// A representing the 's tax information. + /// A representing the 's tax information. /// Thrown when the is . /// This method opts for returning rather than throwing exceptions, making it ideal for surfacing data from API endpoints. - Task GetTaxInformation( + Task GetTaxInformation( ISubscriber subscriber); /// @@ -137,10 +124,10 @@ public interface ISubscriberService /// Updates the tax information for the provided . /// /// The to update the tax information for. - /// A representing the 's updated tax information. + /// A representing the 's updated tax information. Task UpdateTaxInformation( ISubscriber subscriber, - TaxInformationDTO taxInformation); + TaxInformation taxInformation); /// /// Verifies the subscriber's pending bank account using the provided . diff --git a/src/Core/Billing/Services/Implementations/SubscriberService.cs b/src/Core/Billing/Services/Implementations/SubscriberService.cs index 92f245c3b..850e6737f 100644 --- a/src/Core/Billing/Services/Implementations/SubscriberService.cs +++ b/src/Core/Billing/Services/Implementations/SubscriberService.cs @@ -2,7 +2,6 @@ using Bit.Core.Billing.Models; using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Models.BitStripe; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; @@ -37,7 +36,7 @@ public class SubscriberService( { logger.LogWarning("Cannot cancel subscription ({ID}) that's already inactive", subscription.Id); - throw ContactSupport(); + throw new BillingException(); } var metadata = new Dictionary @@ -148,7 +147,7 @@ public class SubscriberService( { logger.LogError("Cannot retrieve customer for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewayCustomerId)); - throw ContactSupport(); + throw new BillingException(); } try @@ -163,48 +162,16 @@ public class SubscriberService( logger.LogError("Could not find Stripe customer ({CustomerID}) for subscriber ({SubscriberID})", subscriber.GatewayCustomerId, subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } - catch (StripeException exception) + catch (StripeException stripeException) { logger.LogError("An error occurred while trying to retrieve Stripe customer ({CustomerID}) for subscriber ({SubscriberID}): {Error}", - subscriber.GatewayCustomerId, subscriber.Id, exception.Message); + subscriber.GatewayCustomerId, subscriber.Id, stripeException.Message); - throw ContactSupport("An error occurred while trying to retrieve a Stripe Customer", exception); - } - } - - public async Task> GetInvoices( - ISubscriber subscriber, - StripeInvoiceListOptions invoiceListOptions = null) - { - ArgumentNullException.ThrowIfNull(subscriber); - - if (string.IsNullOrEmpty(subscriber.GatewayCustomerId)) - { - logger.LogError("Cannot retrieve invoices for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewayCustomerId)); - - return []; - } - - try - { - if (invoiceListOptions == null) - { - invoiceListOptions = new StripeInvoiceListOptions { Customer = subscriber.GatewayCustomerId }; - } - else - { - invoiceListOptions.Customer = subscriber.GatewayCustomerId; - } - - return await stripeAdapter.InvoiceListAsync(invoiceListOptions); - } - catch (StripeException exception) - { - logger.LogError("An error occurred while trying to retrieve Stripe invoices for subscriber ({SubscriberID}): {Error}", subscriber.Id, exception.Message); - - return []; + throw new BillingException( + message: "An error occurred while trying to retrieve a Stripe customer", + innerException: stripeException); } } @@ -294,7 +261,7 @@ public class SubscriberService( { logger.LogError("Cannot retrieve subscription for subscriber ({SubscriberID}) with no {FieldName}", subscriber.Id, nameof(subscriber.GatewaySubscriptionId)); - throw ContactSupport(); + throw new BillingException(); } try @@ -309,18 +276,20 @@ public class SubscriberService( logger.LogError("Could not find Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID})", subscriber.GatewaySubscriptionId, subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } - catch (StripeException exception) + catch (StripeException stripeException) { logger.LogError("An error occurred while trying to retrieve Stripe subscription ({SubscriptionID}) for subscriber ({SubscriberID}): {Error}", - subscriber.GatewaySubscriptionId, subscriber.Id, exception.Message); + subscriber.GatewaySubscriptionId, subscriber.Id, stripeException.Message); - throw ContactSupport("An error occurred while trying to retrieve a Stripe Subscription", exception); + throw new BillingException( + message: "An error occurred while trying to retrieve a Stripe subscription", + innerException: stripeException); } } - public async Task GetTaxInformation( + public async Task GetTaxInformation( ISubscriber subscriber) { ArgumentNullException.ThrowIfNull(subscriber); @@ -337,7 +306,7 @@ public class SubscriberService( if (string.IsNullOrEmpty(subscriber.GatewayCustomerId)) { - throw ContactSupport(); + throw new BillingException(); } var stripeCustomer = await GetCustomerOrThrow(subscriber, new CustomerGetOptions @@ -353,7 +322,7 @@ public class SubscriberService( { logger.LogError("Failed to retrieve Braintree customer ({ID}) when removing payment method", braintreeCustomerId); - throw ContactSupport(); + throw new BillingException(); } if (braintreeCustomer.DefaultPaymentMethod != null) @@ -369,7 +338,7 @@ public class SubscriberService( logger.LogError("Failed to update payment method for Braintree customer ({ID}) | Message: {Message}", braintreeCustomerId, updateCustomerResult.Message); - throw ContactSupport(); + throw new BillingException(); } var deletePaymentMethodResult = await braintreeGateway.PaymentMethod.DeleteAsync(existingDefaultPaymentMethod.Token); @@ -384,7 +353,7 @@ public class SubscriberService( "Failed to delete Braintree payment method for Customer ({ID}), re-linked payment method. Message: {Message}", braintreeCustomerId, deletePaymentMethodResult.Message); - throw ContactSupport(); + throw new BillingException(); } } else @@ -437,7 +406,7 @@ public class SubscriberService( { logger.LogError("Updated payment method for ({SubscriberID}) must contain a token", subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault @@ -462,7 +431,7 @@ public class SubscriberService( { logger.LogError("There were more than 1 setup intents for subscriber's ({SubscriberID}) updated payment method", subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } var matchingSetupIntent = setupIntentsForUpdatedPaymentMethod.First(); @@ -551,7 +520,7 @@ public class SubscriberService( { logger.LogError("Failed to retrieve Braintree customer ({BraintreeCustomerId}) when updating payment method for subscriber ({SubscriberID})", braintreeCustomerId, subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } await ReplaceBraintreePaymentMethodAsync(braintreeCustomer, token); @@ -570,14 +539,14 @@ public class SubscriberService( { logger.LogError("Cannot update subscriber's ({SubscriberID}) payment method to type ({PaymentMethodType}) as it is not supported", subscriber.Id, type.ToString()); - throw ContactSupport(); + throw new BillingException(); } } } public async Task UpdateTaxInformation( ISubscriber subscriber, - TaxInformationDTO taxInformation) + TaxInformation taxInformation) { ArgumentNullException.ThrowIfNull(subscriber); ArgumentNullException.ThrowIfNull(taxInformation); @@ -635,7 +604,7 @@ public class SubscriberService( { logger.LogError("No setup intent ID exists to verify for subscriber with ID ({SubscriberID})", subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } var (amount1, amount2) = microdeposits; @@ -706,7 +675,7 @@ public class SubscriberService( logger.LogError("Failed to create Braintree customer for subscriber ({ID})", subscriber.Id); - throw ContactSupport(); + throw new BillingException(); } private async Task GetMaskedPaymentMethodDTOAsync( @@ -751,7 +720,7 @@ public class SubscriberService( return MaskedPaymentMethodDTO.From(setupIntent); } - private static TaxInformationDTO GetTaxInformationDTOFrom( + private static TaxInformation GetTaxInformationDTOFrom( Customer customer) { if (customer.Address == null) @@ -759,7 +728,7 @@ public class SubscriberService( return null; } - return new TaxInformationDTO( + return new TaxInformation( customer.Address.Country, customer.Address.PostalCode, customer.TaxIds?.FirstOrDefault()?.Value, @@ -825,7 +794,7 @@ public class SubscriberService( { logger.LogError("Failed to replace payment method for Braintree customer ({ID}) - Creation of new payment method failed | Error: {Error}", customer.Id, createPaymentMethodResult.Message); - throw ContactSupport(); + throw new BillingException(); } var updateCustomerResult = await braintreeGateway.Customer.UpdateAsync( @@ -839,7 +808,7 @@ public class SubscriberService( await braintreeGateway.PaymentMethod.DeleteAsync(createPaymentMethodResult.Target.Token); - throw ContactSupport(); + throw new BillingException(); } if (existingDefaultPaymentMethod != null) diff --git a/src/Core/Billing/TrialInitiation/Registration/ISendTrialInitiationEmailForRegistrationCommand.cs b/src/Core/Billing/TrialInitiation/Registration/ISendTrialInitiationEmailForRegistrationCommand.cs new file mode 100644 index 000000000..01550228b --- /dev/null +++ b/src/Core/Billing/TrialInitiation/Registration/ISendTrialInitiationEmailForRegistrationCommand.cs @@ -0,0 +1,14 @@ +#nullable enable +using Bit.Core.Billing.Enums; + +namespace Bit.Core.Billing.TrialInitiation.Registration; + +public interface ISendTrialInitiationEmailForRegistrationCommand +{ + public Task Handle( + string email, + string? name, + bool receiveMarketingEmails, + ProductTierType productTier, + IEnumerable products); +} diff --git a/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs b/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs new file mode 100644 index 000000000..6657be085 --- /dev/null +++ b/src/Core/Billing/TrialInitiation/Registration/Implementations/SendTrialInitiationEmailForRegistrationCommand.cs @@ -0,0 +1,74 @@ +#nullable enable +using Bit.Core.Auth.Models.Business.Tokenables; +using Bit.Core.Billing.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Core.Tokens; +using Bit.Core.Utilities; + +namespace Bit.Core.Billing.TrialInitiation.Registration.Implementations; + +public class SendTrialInitiationEmailForRegistrationCommand( + IUserRepository userRepository, + GlobalSettings globalSettings, + IMailService mailService, + IDataProtectorTokenFactory tokenDataFactory) + : ISendTrialInitiationEmailForRegistrationCommand +{ + public async Task Handle( + string email, + string? name, + bool receiveMarketingEmails, + ProductTierType productTier, + IEnumerable products) + { + ArgumentException.ThrowIfNullOrWhiteSpace(email, nameof(email)); + + var userExists = await CheckUserExistsConstantTimeAsync(email); + var token = GenerateToken(email, name, receiveMarketingEmails); + + if (!globalSettings.EnableEmailVerification) + { + await PerformConstantTimeOperationsAsync(); + + if (userExists) + { + throw new BadRequestException($"Email {email} is already taken"); + } + + return token; + } + + await PerformConstantTimeOperationsAsync(); + + if (!userExists) + { + await mailService.SendTrialInitiationSignupEmailAsync(email, token, productTier, products); + } + + return null; + } + + /// + /// Perform constant time operations to prevent timing attacks + /// + private static async Task PerformConstantTimeOperationsAsync() + { + await Task.Delay(130); + } + + private string GenerateToken(string email, string? name, bool receiveMarketingEmails) + { + var tokenable = new RegistrationEmailVerificationTokenable(email, name, receiveMarketingEmails); + return tokenDataFactory.Protect(tokenable); + } + + private async Task CheckUserExistsConstantTimeAsync(string email) + { + var user = await userRepository.GetByEmailAsync(email); + + return CoreHelpers.FixedTimeEquals(user?.Email.ToLowerInvariant() ?? string.Empty, email.ToLowerInvariant()); + } +} diff --git a/src/Core/Billing/TrialInitiation/TrialInitiationCollectionExtensions.cs b/src/Core/Billing/TrialInitiation/TrialInitiationCollectionExtensions.cs new file mode 100644 index 000000000..a3b1f97f6 --- /dev/null +++ b/src/Core/Billing/TrialInitiation/TrialInitiationCollectionExtensions.cs @@ -0,0 +1,13 @@ +using Bit.Core.Billing.TrialInitiation.Registration; +using Bit.Core.Billing.TrialInitiation.Registration.Implementations; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.Billing.TrialInitiation; + +public static class TrialInitiationCollectionExtensions +{ + public static void AddTrialInitiationServices(this IServiceCollection services) + { + services.AddSingleton(); + } +} diff --git a/src/Core/Billing/Utilities.cs b/src/Core/Billing/Utilities.cs index 2c5ad8547..b8bc1887b 100644 --- a/src/Core/Billing/Utilities.cs +++ b/src/Core/Billing/Utilities.cs @@ -8,12 +8,7 @@ public static class Utilities { public const string BraintreeCustomerIdKey = "btCustomerId"; - public static BillingException ContactSupport( - string internalMessage = null, - Exception innerException = null) => new("Something went wrong with your request. Please contact support.", - internalMessage, innerException); - - public static async Task GetSuspensionAsync( + public static async Task GetSubscriptionSuspensionAsync( IStripeAdapter stripeAdapter, Subscription subscription) { @@ -49,7 +44,7 @@ public static class Utilities const int gracePeriod = 14; - return new SubscriptionSuspensionDTO( + return new SubscriptionSuspension( firstOverdueInvoice.Created.AddDays(gracePeriod), firstOverdueInvoice.PeriodEnd, gracePeriod); @@ -67,7 +62,7 @@ public static class Utilities const int gracePeriod = 30; - return new SubscriptionSuspensionDTO( + return new SubscriptionSuspension( firstOverdueInvoice.DueDate.Value.AddDays(gracePeriod), firstOverdueInvoice.PeriodEnd, gracePeriod); @@ -75,4 +70,21 @@ public static class Utilities default: return null; } } + + public static TaxInformation GetTaxInformation(Customer customer) + { + if (customer.Address == null) + { + return null; + } + + return new TaxInformation( + customer.Address.Country, + customer.Address.PostalCode, + customer.TaxIds?.FirstOrDefault()?.Value, + customer.Address.Line1, + customer.Address.Line2, + customer.Address.City, + customer.Address.State); + } } diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index c360e3b0a..445882780 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -23,8 +23,6 @@ public static class Constants public const string Fido2KeyCipherMinimumVersion = "2023.10.0"; - public const string CipherKeyEncryptionMinimumVersion = "2024.2.0"; - /// /// Used by IdentityServer to identify our own provider. /// @@ -106,35 +104,38 @@ public static class FeatureFlagKeys public const string BrowserFilelessImport = "browser-fileless-import"; public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair"; public const string UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection"; - - /// - /// Deprecated - never used, do not use. Will always default to false. Will be deleted as part of Flexible Collections cleanup - /// - public const string FlexibleCollections = "flexible-collections-disabled-do-not-use"; public const string FlexibleCollectionsV1 = "flexible-collections-v-1"; // v-1 is intentional public const string ItemShare = "item-share"; public const string KeyRotationImprovements = "key-rotation-improvements"; public const string DuoRedirect = "duo-redirect"; - public const string PM5766AutomaticTax = "PM-5766-automatic-tax"; public const string PM5864DollarThreshold = "PM-5864-dollar-threshold"; public const string ShowPaymentMethodWarningBanners = "show-payment-method-warning-banners"; public const string AC2101UpdateTrialInitiationEmail = "AC-2101-update-trial-initiation-email"; public const string EnableConsolidatedBilling = "enable-consolidated-billing"; public const string AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section"; - public const string UnassignedItemsBanner = "unassigned-items-banner"; public const string EnableDeleteProvider = "AC-1218-delete-provider"; public const string EmailVerification = "email-verification"; public const string EmailVerificationDisableTimingDelays = "email-verification-disable-timing-delays"; public const string AnhFcmv1Migration = "anh-fcmv1-migration"; public const string ExtensionRefresh = "extension-refresh"; public const string RestrictProviderAccess = "restrict-provider-access"; + public const string PM4154BulkEncryptionService = "PM-4154-bulk-encryption-service"; public const string VaultBulkManagementAction = "vault-bulk-management-action"; public const string BulkDeviceApproval = "bulk-device-approval"; public const string MemberAccessReport = "ac-2059-member-access-report"; public const string BlockLegacyUsers = "block-legacy-users"; public const string InlineMenuFieldQualification = "inline-menu-field-qualification"; public const string TwoFactorComponentRefactor = "two-factor-component-refactor"; - public const string GroupsComponentRefactor = "groups-component-refactor"; + public const string InlineMenuPositioningImprovements = "inline-menu-positioning-improvements"; + public const string AC2828_ProviderPortalMembersPage = "AC-2828_provider-portal-members-page"; + public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner"; + public const string DeviceTrustLogging = "pm-8285-device-trust-logging"; + public const string AuthenticatorTwoFactorToken = "authenticator-2fa-token"; + public const string EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub"; + public const string UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh"; + public const string GenerateIdentityFillScriptRefactor = "generate-identity-fill-script-refactor"; + public const string DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2"; + public const string MembersTwoFAQueryOptimization = "ac-1698-members-two-fa-query-optimization"; public static List GetAllKeys() { @@ -150,8 +151,8 @@ public static class FeatureFlagKeys return new Dictionary() { { DuoRedirect, "true" }, - { UnassignedItemsBanner, "true"}, - { FlexibleCollectionsV1, "true" } + { FlexibleCollectionsV1, "true" }, + { BulkDeviceApproval, "true" } }; } } diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index 90ad275d0..20413068e 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -217,17 +217,6 @@ public class CurrentContext : ICurrentContext })); } - if (claimsDict.ContainsKey(Claims.OrganizationManager)) - { - organizations.AddRange(claimsDict[Claims.OrganizationManager].Select(c => - new CurrentContextOrganization - { - Id = new Guid(c.Value), - Type = OrganizationUserType.Manager, - AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value), - })); - } - if (claimsDict.ContainsKey(Claims.OrganizationCustom)) { organizations.AddRange(claimsDict[Claims.OrganizationCustom].Select(c => @@ -274,12 +263,6 @@ public class CurrentContext : ICurrentContext return (Organizations?.Any(o => o.Id == orgId) ?? false) || await OrganizationOwner(orgId); } - public async Task OrganizationManager(Guid orgId) - { - return await OrganizationAdmin(orgId) || - (Organizations?.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Manager) ?? false); - } - public async Task OrganizationAdmin(Guid orgId) { return await OrganizationOwner(orgId) || @@ -336,32 +319,6 @@ public class CurrentContext : ICurrentContext return await EditAnyCollection(orgId) || (org != null && org.Permissions.DeleteAnyCollection); } - public async Task EditAssignedCollections(Guid orgId) - { - return await OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId - && (o.Permissions?.EditAssignedCollections ?? false)) ?? false); - } - - public async Task DeleteAssignedCollections(Guid orgId) - { - return await OrganizationManager(orgId) || (Organizations?.Any(o => o.Id == orgId - && (o.Permissions?.DeleteAssignedCollections ?? false)) ?? false); - } - - public async Task ViewAssignedCollections(Guid orgId) - { - /* - * Required to display the existing collections under which the new collection can be nested. - * Owner, Admin, Manager, and Provider checks are handled via the EditAssigned/DeleteAssigned context calls. - * This entire method will be moved to the CollectionAuthorizationHandler in the future - */ - - var org = GetOrganization(orgId); - return await EditAssignedCollections(orgId) - || await DeleteAssignedCollections(orgId) - || (org != null && org.Permissions.CreateNewCollections); - } - public async Task ManageGroups(Guid orgId) { return await OrganizationAdmin(orgId) || (Organizations?.Any(o => o.Id == orgId @@ -426,6 +383,11 @@ public class CurrentContext : ICurrentContext return await EditSubscription(orgId); } + public async Task AccessMembersTab(Guid orgId) + { + return await OrganizationAdmin(orgId) || await ManageUsers(orgId) || await ManageResetPassword(orgId); + } + public bool ProviderProviderAdmin(Guid providerId) { return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false; diff --git a/src/Core/Context/ICurrentContext.cs b/src/Core/Context/ICurrentContext.cs index 57fa7271b..e41c660d4 100644 --- a/src/Core/Context/ICurrentContext.cs +++ b/src/Core/Context/ICurrentContext.cs @@ -36,8 +36,6 @@ public interface ICurrentContext Task OrganizationUser(Guid orgId); - [Obsolete("Manager role is deprecated after Flexible Collections.")] - Task OrganizationManager(Guid orgId); Task OrganizationAdmin(Guid orgId); Task OrganizationOwner(Guid orgId); Task OrganizationCustom(Guid orgId); @@ -46,16 +44,11 @@ public interface ICurrentContext Task AccessReports(Guid orgId); Task EditAnyCollection(Guid orgId); Task ViewAllCollections(Guid orgId); - [Obsolete("Pre-Flexible Collections logic.")] - Task EditAssignedCollections(Guid orgId); - [Obsolete("Pre-Flexible Collections logic.")] - Task DeleteAssignedCollections(Guid orgId); - [Obsolete("Pre-Flexible Collections logic.")] - Task ViewAssignedCollections(Guid orgId); Task ManageGroups(Guid orgId); Task ManagePolicies(Guid orgId); Task ManageSso(Guid orgId); Task ManageUsers(Guid orgId); + Task AccessMembersTab(Guid orgId); Task ManageScim(Guid orgId); Task ManageResetPassword(Guid orgId); Task ViewSubscription(Guid orgId); diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index f07a97e2e..a23d18528 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -21,35 +21,41 @@ - - + + - - - + + + + - - + + + + - + - + - + @@ -57,7 +63,7 @@ - + diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index af3673f10..ed3fdb21d 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -14,6 +14,7 @@ public enum EventType : int User_UpdatedTempPassword = 1008, User_MigratedKeyToKeyConnector = 1009, User_RequestedDeviceApproval = 1010, + User_TdeOffboardingPasswordSet = 1011, Cipher_Created = 1100, Cipher_Updated = 1101, diff --git a/src/Core/Identity/Claims.cs b/src/Core/Identity/Claims.cs index 318f0b400..b1223a6e6 100644 --- a/src/Core/Identity/Claims.cs +++ b/src/Core/Identity/Claims.cs @@ -9,7 +9,6 @@ public static class Claims public const string OrganizationOwner = "orgowner"; public const string OrganizationAdmin = "orgadmin"; - public const string OrganizationManager = "orgmanager"; public const string OrganizationUser = "orguser"; public const string OrganizationCustom = "orgcustom"; public const string ProviderAdmin = "providerprovideradmin"; diff --git a/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.html.hbs b/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.html.hbs new file mode 100644 index 000000000..6c1b9edec --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.html.hbs @@ -0,0 +1,24 @@ +{{#>FullHtmlLayout}} + + + + + + + + + + +
+ Verify your email address below to finish signing up for your free trial. +
+ If you did not request this email from Bitwarden, you can safely ignore it. +
+
+
+ + Verify email + +
+
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.text.hbs b/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.text.hbs new file mode 100644 index 000000000..690cf7773 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/Billing/TrialInitiationVerifyEmail.text.hbs @@ -0,0 +1,8 @@ +{{#>BasicTextLayout}} +Verify your email address using the link below and start your free trial of Bitwarden. + +If you did not request this email from Bitwarden, you can safely ignore it. + +{{{Url}}} + +{{/BasicTextLayout}} diff --git a/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.html.hbs b/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.html.hbs new file mode 100644 index 000000000..501e09cf1 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.html.hbs @@ -0,0 +1,27 @@ +{{#>FullHtmlLayout}} + + + + +
+ + + + + + + +
+ {{UserNameRequestingAccess}} has requested access to secrets manager for {{OrgName}}:

+
{{EmailContent}} - {{UserNameRequestingAccess}}
+
+ + Contact Bitwarden + +
+
+
Stay safe and secure,
+ The Bitwarden Team +
+ +{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.text.hbs b/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.text.hbs new file mode 100644 index 000000000..62e9b7491 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/SecretsManagerAccessRequest.text.hbs @@ -0,0 +1,17 @@ +{{#>FullTextLayout}} + +{{UserNameRequestingAccess}} has requested access to secrets manager for {{OrgName}}: + +============ + +{{EmailContent}} - {{UserNameRequestingAccess}} + +============ + +Contact Bitwarden (https://bitwarden.com/contact-sales/?utm_source=sm_request_access_email&utm_medium=email) + +============ + +Stay safe and secure, +The Bitwarden Team +{{/FullTextLayout}} diff --git a/src/Core/MailTemplates/Handlebars/TrialInitiation.html.hbs b/src/Core/MailTemplates/Handlebars/TrialInitiation.html.hbs index c71119524..4f31fea4b 100644 --- a/src/Core/MailTemplates/Handlebars/TrialInitiation.html.hbs +++ b/src/Core/MailTemplates/Handlebars/TrialInitiation.html.hbs @@ -1,75 +1,143 @@ {{#>FullHtmlLayout}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Welcome to Bitwarden! You can now get started with secure credential management and extend the benefits of end-to-end encryption across your entire organization. -
-
-
- Your Master Password is the only way to unlock your account and only you hold the key. Memorize it, or write it down and keep it in a safe place. -
-
- Install Bitwarden -
- Access your Bitwarden account from anywhere and any device at {{{WebVaultUrlHostname}}}! For added convenience, download and install Bitwarden on any desktop, device, and browser. -
-
-
- - Windows, Mac, Linux, Android, Apple, Chrome, Safari, Firefox, Edge, Opera, Brave, Vivaldi, Tor - -
-
- Securely Share using Bitwarden Organizations -
- Bitwarden makes it easy for teams and enterprises to securely share passwords, developer secrets, and passkeys via Organizations. Join an Organization if invited, or launch a new one anytime from the Web App with the + New Organization button. -
-
-
- - Login to Bitwarden - -
-
- Bitwarden is Here for You -
- Check out the Help site for documentation, join the Bitwarden Community forums and connect with other enthusiasts on Reddit. If you have any questions or issues, contact support. -
-
-
- Stay safe and secure,
- The Bitwarden Team -
-{{/FullHtmlLayout}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Welcome to Bitwarden! +
+
+
+ Here are a few simple steps to get up and running with Bitwarden Password Manager: +
+
+
+
    +
  1. + Install the browser extension +

    Autofill passwords, save new logins, access the password generator, and more from the Bitwarden + browser extension. The extension keeps Bitwarden easily accessible so you can be secure while + online. +

    +
    + + Install the extension + +
  2. +
  3. + Add passwords to your vault +

    Your secure vault is ready to safely store your sensitive information. Add logins, credit cards, + notes, and identities for fast access and autofilling!

    +
    + + Add passwords to your vault + +
  4. +
  5. + Keep your master password safe and enable biometrics +

    Vault security starts with your Bitwarden master password. Ensure it is strong and complex, then + memorize + it or store it in a safe place. Now you‘re ready to enable biometrics for a faster login + experience with + fingerprint or FaceID.

    +
    + + Enable biometrics + +
  6. +
+
+ Learning Center +
+ Instructional videos and best practice guides for every level are just a click away. +
+ + Visit learning center + +
+
+
+
+ Bring Bitwarden to Work +
+ Are you using Bitwarden for personal or family use? Join the Bitwarden fans who are bringing the password + manager + they know and love to their workplace. +
+ + Bring it + +
+ Signed up for Bitwarden Secrets Manager? +

If you signed up for Bitwarden Secrets Manager to secure + infrastructure and + machine secrets, get up and running quickly with the Secrets + Manager quick start resource.

+
+ Stay safe and secure, +
+ The Bitwarden Team +
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/TrialInitiation.text.hbs b/src/Core/MailTemplates/Handlebars/TrialInitiation.text.hbs index 4d7ea9d52..7090b0f4a 100644 --- a/src/Core/MailTemplates/Handlebars/TrialInitiation.text.hbs +++ b/src/Core/MailTemplates/Handlebars/TrialInitiation.text.hbs @@ -1,39 +1,50 @@ {{#>FullTextLayout}} - Welcome to Bitwarden! You can now get started with secure credential management and extend the benefits of end-to-end encryption across your entire organization. +Welcome to Bitwarden! - Your Master Password is the only way to unlock your account and only you hold the key. Memorize it, or write it down and keep it in a safe place. +Here are a few simple steps to get up and running with Bitwarden Password Manager: - Install Bitwarden - ============ +1. Install the browser extension +============ - Access your Bitwarden account from anywhere and any device at the web vault ({{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email). For added convenience, download and install Bitwarden on any desktop, device and browser (http://www.bitwarden.com/download). +Autofill passwords, save new logins, access the password generator, and more from the Bitwarden browser extension. The extension keeps Bitwarden easily accessible so you can be secure while online. +Install the extension (http://www.bitwarden.com/download) - Download Options - ============ +2. Add passwords to your vault +============ - http://www.bitwarden.com/download +Your secure vault is ready to safely store your sensitive information. Add logins, credit cards, notes, and identities for fast access and autofilling! +Add passwords to your vault ({{{WebVaultUrl}}}/?utm_source=welcome_email&utm_medium=email) - Securely Share using Bitwarden Organizations - ============ +3. Keep your master password safe and enable biometrics +============ - Bitwarden makes it easy for teams and enterprises to securely share passwords, developer secrets, and passkeys via Organizations. Join an Organization if invited, or launch a new one anytime from the Web App ({{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email) with the + New Organization button. +Vault security starts with your Bitwarden master password. Ensure it is strong and complex, then memorize it or store it in a safe place. Now you're ready to enable biometrics for a faster login experience with fingerprint or FaceID. +Enable biometrics (https://bitwarden.com/learning/unlock-your-vault-with-biometrics/) - Login to Bitwarden - ============ +Learning Center +============ - {{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email +Instructional videos and best practice guides for every level are just a click away. +Visit the learning center (https://bitwarden.com/learning/) - Bitwarden is Here for You - ============ +Bring Bitwarden to Work +============ - Check out our Help (http://www.bitwarden.com/help) site for documentation, join the Bitwarden Community forums (https://community.bitwarden.com/) and connect with other enthusiasts on Reddit (https://www.reddit.com/r/Bitwarden/). If you have any questions or issues, contact support (http://www.bitwarden.com/contact). +Are you using Bitwarden for personal or family use? Join the Bitwarden fans who are bringing the password manager they know and love to their workplace. +Bring it (https://bitwarden.com/go/bring-bitwarden-to-work/#get-started) - Stay safe and secure, - The Bitwarden Team +Signed up for Bitwarden Secrets Manager? +============ + +If you signed up for Bitwarden Secrets Manager to secure infrastructure and machine secrets, get up and running quickly with the Secrets Manager quick start resource (https://bitwarden.com/help/secrets-manager-quick-start/). + + +Stay safe and secure, +The Bitwarden Team {{/FullTextLayout}} diff --git a/src/Core/MailTemplates/Handlebars/Welcome.html.hbs b/src/Core/MailTemplates/Handlebars/Welcome.html.hbs index ce2863ba3..5ed0df903 100644 --- a/src/Core/MailTemplates/Handlebars/Welcome.html.hbs +++ b/src/Core/MailTemplates/Handlebars/Welcome.html.hbs @@ -1,75 +1,143 @@ {{#>FullHtmlLayout}} - - -
- Welcome to Bitwarden and thank you for creating an account! Now you can extend robust security to all of your online experiences and devices. -
-
+ + + - - + - - + - - + - - + + + + + + + - - + - - - - - - - - - - - - - - +
+ Welcome to Bitwarden! +
+
- Your Master Password is the only way you can unlock the Vault and only you hold the key. Memorize it, or write it down and keep it in a safe place. +
+ Here are a few simple steps to get up and running with Bitwarden Password Manager: +
+
-
- Get Started: Install Bitwarden +
+
    +
  1. + Install the browser extension +

    Autofill passwords, save new logins, access the password generator, and more from the Bitwarden + browser extension. The extension keeps Bitwarden easily accessible so you can be secure while + online. +

    +
    + + Install the extension + +
  2. +
  3. + Add passwords to your vault +

    Your secure vault is ready to safely store your sensitive information. Add logins, credit cards, + notes, and identities for fast access and autofilling!

    +
    + + Add passwords to your vault + +
  4. +
  5. + Keep your master password safe and enable biometrics +

    Vault security starts with your Bitwarden master password. Ensure it is strong and complex, then + memorize + it or store it in a safe place. Now you‘re ready to enable biometrics for a faster login + experience with + fingerprint or FaceID.

    +
    + + Enable biometrics + +
  6. +
- You can access the Bitwarden Vault from anywhere and any device at {{{WebVaultUrlHostname}}}! You can also download and install Bitwarden on any desktop, device, and browser. -
-
+
+ Learning Center
- - Windows, Mac, Linux, Android, Apple, Chrome, Safari, Firefox, Edge, Opera, Brave, Vivaldi, Tor +
+ Instructional videos and best practice guides for every level are just a click away. +
+ + Visit learning center + +
+
+
+
+ Bring Bitwarden to Work +
+ Are you using Bitwarden for personal or family use? Join the Bitwarden fans who are bringing the password + manager + they know and love to their workplace. +
+ + Bring it
-
- Securely Share using Bitwarden Organizations +
+ Signed up for Bitwarden Secrets Manager? +

If you signed up for Bitwarden Secrets Manager to secure + infrastructure and + machine secrets, get up and running quickly with the Secrets + Manager quick start resource.

- Bitwarden makes it easy to securely share passwords for teams and enterprises through Organizations. Join an Organization if invited, or launch a new one anytime from the Web Vault with the + New Organization button. -
-
-
- - Login to Bitwarden - -
-
- We're Here for You -
- Check out our Help site for documentation, join the Bitwarden Community forums and connect with other enthusiasts on Reddit. If you have any questions or issues, contact us. -
-
-
- Stay safe and secure,
+
+ Stay safe and secure, +
The Bitwarden Team
-{{/FullHtmlLayout}} +{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/Welcome.text.hbs b/src/Core/MailTemplates/Handlebars/Welcome.text.hbs index bbc5e1b7e..2ffcaa170 100644 --- a/src/Core/MailTemplates/Handlebars/Welcome.text.hbs +++ b/src/Core/MailTemplates/Handlebars/Welcome.text.hbs @@ -1,37 +1,48 @@ {{#>FullTextLayout}} -Welcome to Bitwarden and thank you for creating an account! Now you can extend robust security to all of your online experiences and devices. +Welcome to Bitwarden! -Your Master Password is the only way you can unlock the Vault and only you hold the key. Memorize it, or write it down and keep it in a safe place. +Here are a few simple steps to get up and running with Bitwarden Password Manager: -Get Started: Install Bitwarden +1. Install the browser extension ============ -You can access the Bitwarden Vault from anywhere and any device at the web vault ({{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email). You can also download and install Bitwarden on any desktop, device and browser (http://www.bitwarden.com/download). +Autofill passwords, save new logins, access the password generator, and more from the Bitwarden browser extension. The extension keeps Bitwarden easily accessible so you can be secure while online. +Install the extension (http://www.bitwarden.com/download) -Download Options +2. Add passwords to your vault ============ -http://www.bitwarden.com/download +Your secure vault is ready to safely store your sensitive information. Add logins, credit cards, notes, and identities for fast access and autofilling! +Add passwords to your vault ({{{WebVaultUrl}}}/?utm_source=welcome_email&utm_medium=email) -Securely Share using Bitwarden Organizations +3. Keep your master password safe and enable biometrics ============ -Bitwarden makes it easy to securely share passwords for teams and enterprises through Organizations. Join an Organization if invited, or launch a new one anytime from the Web Vault ({{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email) with the + New Organization button. +Vault security starts with your Bitwarden master password. Ensure it is strong and complex, then memorize it or store it in a safe place. Now you're ready to enable biometrics for a faster login experience with fingerprint or FaceID. +Enable biometrics (https://bitwarden.com/learning/unlock-your-vault-with-biometrics/) -Login to Bitwarden +Learning Center ============ -{{WebVaultUrl}}/?utm_source=welcome_email&utm_medium=email +Instructional videos and best practice guides for every level are just a click away. +Visit the learning center (https://bitwarden.com/learning/) -We're here for you +Bring Bitwarden to Work ============ -Check out our Help (http://www.bitwarden.com/help) site for documentation, join the Bitwarden Community forums (https://community.bitwarden.com/) and connect with other enthusiasts on Reddit (https://www.reddit.com/r/Bitwarden/). If you have any questions or issues, contact us (http://www.bitwarden.com/contact). +Are you using Bitwarden for personal or family use? Join the Bitwarden fans who are bringing the password manager they know and love to their workplace. +Bring it (https://bitwarden.com/go/bring-bitwarden-to-work/#get-started) + + +Signed up for Bitwarden Secrets Manager? +============ + +If you signed up for Bitwarden Secrets Manager to secure infrastructure and machine secrets, get up and running quickly with the Secrets Manager quick start resource (https://bitwarden.com/help/secrets-manager-quick-start/). Stay safe and secure, diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index cfc374ad8..d5c54ef3e 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -350,16 +350,11 @@ public class OrganizationLicense : ILicense organization.SmServiceAccounts == SmServiceAccounts; } - // Restore validity check when Flexible Collections are enabled for cloud and self-host - // https://bitwarden.atlassian.net/browse/AC-1875 - // if (valid && Version >= 14) - // { - // valid = organization.LimitCollectionCreationDeletion == LimitCollectionCreationDeletion; - // } - // if (valid && Version >= 15) - // { - // valid = organization.AllowAdminAccessToAllCollectionItems == AllowAdminAccessToAllCollectionItems; - // } + /* + * Version 14 added LimitCollectionCreationDeletion and Version 15 added AllowAdminAccessToAllCollectionItems, + * however these are just user settings and it is not worth failing validation if they mismatch. + * They are intentionally excluded. + */ return valid; } diff --git a/src/Core/Models/Business/ProviderSubscriptionUpdate.cs b/src/Core/Models/Business/ProviderSubscriptionUpdate.cs index cffce8a7a..d66013ad1 100644 --- a/src/Core/Models/Business/ProviderSubscriptionUpdate.cs +++ b/src/Core/Models/Business/ProviderSubscriptionUpdate.cs @@ -1,9 +1,8 @@ -using Bit.Core.Billing.Enums; +using Bit.Core.Billing; +using Bit.Core.Billing.Enums; using Bit.Core.Billing.Extensions; using Stripe; -using static Bit.Core.Billing.Utilities; - namespace Bit.Core.Models.Business; public class ProviderSubscriptionUpdate : SubscriptionUpdate @@ -21,7 +20,8 @@ public class ProviderSubscriptionUpdate : SubscriptionUpdate { if (!planType.SupportsConsolidatedBilling()) { - throw ContactSupport($"Cannot create a {nameof(ProviderSubscriptionUpdate)} for {nameof(PlanType)} that doesn't support consolidated billing"); + throw new BillingException( + message: $"Cannot create a {nameof(ProviderSubscriptionUpdate)} for {nameof(PlanType)} that doesn't support consolidated billing"); } var plan = Utilities.StaticStore.GetPlan(planType); diff --git a/src/Core/Models/Data/CollectionAdminDetails.cs b/src/Core/Models/Data/CollectionAdminDetails.cs index 036f7d037..2521c2130 100644 --- a/src/Core/Models/Data/CollectionAdminDetails.cs +++ b/src/Core/Models/Data/CollectionAdminDetails.cs @@ -7,8 +7,8 @@ namespace Bit.Core.Models.Data; /// public class CollectionAdminDetails : CollectionDetails { - public IEnumerable? Groups { get; set; } = new List(); - public IEnumerable? Users { get; set; } = new List(); + public IEnumerable Groups { get; set; } = []; + public IEnumerable Users { get; set; } = []; /// /// Flag for whether the user has been explicitly assigned to the collection either directly or through a group. diff --git a/src/Core/Models/Data/UserWithCalculatedPremium.cs b/src/Core/Models/Data/UserWithCalculatedPremium.cs new file mode 100644 index 000000000..d71e5741e --- /dev/null +++ b/src/Core/Models/Data/UserWithCalculatedPremium.cs @@ -0,0 +1,62 @@ +using Bit.Core.Entities; + +namespace Bit.Core.Models.Data; + +/// +/// Represents a user with an additional property indicating if the user has premium access. +/// +public class UserWithCalculatedPremium : User +{ + public UserWithCalculatedPremium() { } + + public UserWithCalculatedPremium(User user) + { + Id = user.Id; + Name = user.Name; + Email = user.Email; + EmailVerified = user.EmailVerified; + MasterPassword = user.MasterPassword; + MasterPasswordHint = user.MasterPasswordHint; + Culture = user.Culture; + SecurityStamp = user.SecurityStamp; + TwoFactorProviders = user.TwoFactorProviders; + TwoFactorRecoveryCode = user.TwoFactorRecoveryCode; + EquivalentDomains = user.EquivalentDomains; + ExcludedGlobalEquivalentDomains = user.ExcludedGlobalEquivalentDomains; + AccountRevisionDate = user.AccountRevisionDate; + Key = user.Key; + PublicKey = user.PublicKey; + PrivateKey = user.PrivateKey; + Premium = user.Premium; + PremiumExpirationDate = user.PremiumExpirationDate; + RenewalReminderDate = user.RenewalReminderDate; + Storage = user.Storage; + MaxStorageGb = user.MaxStorageGb; + Gateway = user.Gateway; + GatewayCustomerId = user.GatewayCustomerId; + GatewaySubscriptionId = user.GatewaySubscriptionId; + ReferenceData = user.ReferenceData; + LicenseKey = user.LicenseKey; + ApiKey = user.ApiKey; + Kdf = user.Kdf; + KdfIterations = user.KdfIterations; + KdfMemory = user.KdfMemory; + KdfParallelism = user.KdfParallelism; + CreationDate = user.CreationDate; + RevisionDate = user.RevisionDate; + ForcePasswordReset = user.ForcePasswordReset; + UsesKeyConnector = user.UsesKeyConnector; + FailedLoginCount = user.FailedLoginCount; + LastFailedLoginDate = user.LastFailedLoginDate; + AvatarColor = user.AvatarColor; + LastPasswordChangeDate = user.LastPasswordChangeDate; + LastKdfChangeDate = user.LastKdfChangeDate; + LastKeyRotationDate = user.LastKeyRotationDate; + LastEmailChangeDate = user.LastEmailChangeDate; + } + + /// + /// Indicates if the user has premium access, either individually or through an organization. + /// + public bool HasPremiumAccess { get; set; } +} diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index daf5caf00..12c4dd7e3 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -156,4 +156,3 @@ public static class OrganizationServiceCollectionExtensions ); } } - diff --git a/src/Core/Repositories/ICollectionCipherRepository.cs b/src/Core/Repositories/ICollectionCipherRepository.cs index bcc86c35c..9494fec0e 100644 --- a/src/Core/Repositories/ICollectionCipherRepository.cs +++ b/src/Core/Repositories/ICollectionCipherRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface ICollectionCipherRepository diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs index 7710e7d93..a0bf2fc98 100644 --- a/src/Core/Repositories/ICollectionRepository.cs +++ b/src/Core/Repositories/ICollectionRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Models.Data; +#nullable enable + namespace Bit.Core.Repositories; public interface ICollectionRepository : IRepository @@ -10,7 +12,7 @@ public interface ICollectionRepository : IRepository /// /// Returns a collection and fetches group/user associations for the collection. /// - Task> GetByIdWithAccessAsync(Guid id); + Task> GetByIdWithAccessAsync(Guid id); /// /// Return all collections that belong to the organization. Does not include any permission details or group/user @@ -29,7 +31,7 @@ public interface ICollectionRepository : IRepository /// Return all collections a user has access to across all of the organization they're a member of. Includes permission /// details for each collection. /// - Task> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections); + Task> GetManyByUserIdAsync(Guid userId); /// /// Returns all collections for an organization, including permission info for the specified user. @@ -43,7 +45,7 @@ public interface ICollectionRepository : IRepository /// This does not perform any authorization checks internally! /// Optionally, you can include access relationships for other Groups/Users and the collection. /// - Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships); + Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships); Task CreateAsync(Collection obj, IEnumerable groups, IEnumerable users); Task ReplaceAsync(Collection obj, IEnumerable groups, IEnumerable users); diff --git a/src/Core/Repositories/IDeviceRepository.cs b/src/Core/Repositories/IDeviceRepository.cs index 5424d5fe3..c5d14a094 100644 --- a/src/Core/Repositories/IDeviceRepository.cs +++ b/src/Core/Repositories/IDeviceRepository.cs @@ -1,12 +1,14 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface IDeviceRepository : IRepository { - Task GetByIdAsync(Guid id, Guid userId); - Task GetByIdentifierAsync(string identifier); - Task GetByIdentifierAsync(string identifier, Guid userId); + Task GetByIdAsync(Guid id, Guid userId); + Task GetByIdentifierAsync(string identifier); + Task GetByIdentifierAsync(string identifier, Guid userId); Task> GetManyByUserIdAsync(Guid userId); Task ClearPushTokenAsync(Guid id); } diff --git a/src/Core/Repositories/IEventRepository.cs b/src/Core/Repositories/IEventRepository.cs index dda9b589c..e39ad33d1 100644 --- a/src/Core/Repositories/IEventRepository.cs +++ b/src/Core/Repositories/IEventRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Models.Data; using Bit.Core.Vault.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface IEventRepository diff --git a/src/Core/Repositories/IInstallationDeviceRepository.cs b/src/Core/Repositories/IInstallationDeviceRepository.cs index bdbeaf297..714902d9f 100644 --- a/src/Core/Repositories/IInstallationDeviceRepository.cs +++ b/src/Core/Repositories/IInstallationDeviceRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.Models.Data; +#nullable enable + namespace Bit.Core.Repositories; public interface IInstallationDeviceRepository diff --git a/src/Core/Repositories/IInstallationRepository.cs b/src/Core/Repositories/IInstallationRepository.cs index 65ee34aaf..f9c7d85ed 100644 --- a/src/Core/Repositories/IInstallationRepository.cs +++ b/src/Core/Repositories/IInstallationRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface IInstallationRepository : IRepository diff --git a/src/Core/Repositories/IMaintenanceRepository.cs b/src/Core/Repositories/IMaintenanceRepository.cs index a89c38bd0..425b9223f 100644 --- a/src/Core/Repositories/IMaintenanceRepository.cs +++ b/src/Core/Repositories/IMaintenanceRepository.cs @@ -1,5 +1,7 @@ namespace Bit.Core.Repositories; +#nullable enable + public interface IMaintenanceRepository { Task UpdateStatisticsAsync(); diff --git a/src/Core/Repositories/IOrganizationApiKeyRepository.cs b/src/Core/Repositories/IOrganizationApiKeyRepository.cs index 778db9d73..bf6ff6581 100644 --- a/src/Core/Repositories/IOrganizationApiKeyRepository.cs +++ b/src/Core/Repositories/IOrganizationApiKeyRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; +#nullable enable + namespace Bit.Core.Repositories; public interface IOrganizationApiKeyRepository : IRepository diff --git a/src/Core/Repositories/IOrganizationConnectionRepository.cs b/src/Core/Repositories/IOrganizationConnectionRepository.cs index b480333f6..634243f78 100644 --- a/src/Core/Repositories/IOrganizationConnectionRepository.cs +++ b/src/Core/Repositories/IOrganizationConnectionRepository.cs @@ -1,11 +1,13 @@ using Bit.Core.Entities; using Bit.Core.Enums; +#nullable enable + namespace Bit.Core.Repositories; public interface IOrganizationConnectionRepository : IRepository { - Task GetByIdOrganizationIdAsync(Guid id, Guid organizationId); + Task GetByIdOrganizationIdAsync(Guid id, Guid organizationId); Task> GetByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type); Task> GetEnabledByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type); } diff --git a/src/Core/Repositories/IOrganizationDomainRepository.cs b/src/Core/Repositories/IOrganizationDomainRepository.cs index 1308c4110..3fde08a54 100644 --- a/src/Core/Repositories/IOrganizationDomainRepository.cs +++ b/src/Core/Repositories/IOrganizationDomainRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Models.Data.Organizations; +#nullable enable + namespace Bit.Core.Repositories; public interface IOrganizationDomainRepository : IRepository @@ -8,9 +10,9 @@ public interface IOrganizationDomainRepository : IRepository> GetClaimedDomainsByDomainNameAsync(string domainName); Task> GetDomainsByOrganizationIdAsync(Guid orgId); Task> GetManyByNextRunDateAsync(DateTime date); - Task GetOrganizationDomainSsoDetailsAsync(string email); - Task GetDomainByIdOrganizationIdAsync(Guid id, Guid organizationId); - Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName); + Task GetOrganizationDomainSsoDetailsAsync(string email); + Task GetDomainByIdOrganizationIdAsync(Guid id, Guid organizationId); + Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName); Task> GetExpiredOrganizationDomainsAsync(); Task DeleteExpiredAsync(int expirationPeriod); } diff --git a/src/Core/Repositories/IOrganizationSponsorshipRepository.cs b/src/Core/Repositories/IOrganizationSponsorshipRepository.cs index 232fd1b9d..30e6ee4a3 100644 --- a/src/Core/Repositories/IOrganizationSponsorshipRepository.cs +++ b/src/Core/Repositories/IOrganizationSponsorshipRepository.cs @@ -1,15 +1,17 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface IOrganizationSponsorshipRepository : IRepository { - Task> CreateManyAsync(IEnumerable organizationSponsorships); + Task?> CreateManyAsync(IEnumerable organizationSponsorships); Task ReplaceManyAsync(IEnumerable organizationSponsorships); Task UpsertManyAsync(IEnumerable organizationSponsorships); Task DeleteManyAsync(IEnumerable organizationSponsorshipIds); Task> GetManyBySponsoringOrganizationAsync(Guid sponsoringOrganizationId); - Task GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId); - Task GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId); + Task GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId); + Task GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId); Task GetLatestSyncDateBySponsoringOrganizationIdAsync(Guid sponsoringOrganizationId); } diff --git a/src/Core/Repositories/IRepository.cs b/src/Core/Repositories/IRepository.cs index 18bb81ff8..7a13e4fde 100644 --- a/src/Core/Repositories/IRepository.cs +++ b/src/Core/Repositories/IRepository.cs @@ -1,10 +1,12 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface IRepository where TId : IEquatable where T : class, ITableObject { - Task GetByIdAsync(TId id); + Task GetByIdAsync(TId id); Task CreateAsync(T obj); Task ReplaceAsync(T obj); Task UpsertAsync(T obj); diff --git a/src/Core/Repositories/ITaxRateRepository.cs b/src/Core/Repositories/ITaxRateRepository.cs index a8557a789..c4d9e4123 100644 --- a/src/Core/Repositories/ITaxRateRepository.cs +++ b/src/Core/Repositories/ITaxRateRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.Entities; +#nullable enable + namespace Bit.Core.Repositories; public interface ITaxRateRepository : IRepository diff --git a/src/Core/Repositories/ITransactionRepository.cs b/src/Core/Repositories/ITransactionRepository.cs index 911d021b4..8491ef201 100644 --- a/src/Core/Repositories/ITransactionRepository.cs +++ b/src/Core/Repositories/ITransactionRepository.cs @@ -1,6 +1,8 @@ using Bit.Core.Entities; using Bit.Core.Enums; +#nullable enable + namespace Bit.Core.Repositories; public interface ITransactionRepository : IRepository @@ -8,5 +10,5 @@ public interface ITransactionRepository : IRepository Task> GetManyByUserIdAsync(Guid userId, int? limit = null); Task> GetManyByOrganizationIdAsync(Guid organizationId, int? limit = null); Task> GetManyByProviderIdAsync(Guid providerId, int? limit = null); - Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId); + Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId); } diff --git a/src/Core/Repositories/IUserRepository.cs b/src/Core/Repositories/IUserRepository.cs index 4bbbe1b65..b7c654f43 100644 --- a/src/Core/Repositories/IUserRepository.cs +++ b/src/Core/Repositories/IUserRepository.cs @@ -2,28 +2,34 @@ using Bit.Core.Entities; using Bit.Core.Models.Data; +#nullable enable + namespace Bit.Core.Repositories; public interface IUserRepository : IRepository { - Task GetByEmailAsync(string email); + Task GetByEmailAsync(string email); Task> GetManyByEmailsAsync(IEnumerable emails); - Task GetBySsoUserAsync(string externalId, Guid? organizationId); - Task GetKdfInformationByEmailAsync(string email); + Task GetBySsoUserAsync(string externalId, Guid? organizationId); + Task GetKdfInformationByEmailAsync(string email); Task> SearchAsync(string email, int skip, int take); Task> GetManyByPremiumAsync(bool premium); - Task GetPublicKeyAsync(Guid id); + Task GetPublicKeyAsync(Guid id); Task GetAccountRevisionDateAsync(Guid id); Task UpdateStorageAsync(Guid id); Task UpdateRenewalReminderDateAsync(Guid id, DateTime renewalReminderDate); Task> GetManyAsync(IEnumerable ids); /// + /// Retrieves the data for the requested user IDs and includes an additional property indicating + /// whether the user has premium access directly or through an organization. + /// + Task> GetManyWithCalculatedPremiumAsync(IEnumerable ids); + /// /// Sets a new user key and updates all encrypted data. /// Warning: Any user key encrypted data not included will be lost. /// /// The user to update /// Registered database calls to update re-encrypted data. - [Obsolete("Intended for future improvements to key rotation. Do not use.")] Task UpdateUserKeyAndEncryptedDataAsync(User user, IEnumerable updateDataActions); } diff --git a/src/Core/Repositories/Noop/InstallationDeviceRepository.cs b/src/Core/Repositories/Noop/InstallationDeviceRepository.cs index b70445901..f3d4a4dd5 100644 --- a/src/Core/Repositories/Noop/InstallationDeviceRepository.cs +++ b/src/Core/Repositories/Noop/InstallationDeviceRepository.cs @@ -1,5 +1,7 @@ using Bit.Core.Models.Data; +#nullable enable + namespace Bit.Core.Repositories.Noop; public class InstallationDeviceRepository : IInstallationDeviceRepository diff --git a/src/Core/Repositories/TableStorage/EventRepository.cs b/src/Core/Repositories/TableStorage/EventRepository.cs index 7c5cb97db..81879ef93 100644 --- a/src/Core/Repositories/TableStorage/EventRepository.cs +++ b/src/Core/Repositories/TableStorage/EventRepository.cs @@ -4,6 +4,8 @@ using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; +#nullable enable + namespace Bit.Core.Repositories.TableStorage; public class EventRepository : IEventRepository @@ -78,9 +80,9 @@ public class EventRepository : IEventRepository await CreateEventAsync(entity); } - public async Task CreateManyAsync(IEnumerable e) + public async Task CreateManyAsync(IEnumerable? e) { - if (!e?.Any() ?? true) + if (e is null || !e.Any()) { return; } @@ -91,7 +93,7 @@ public class EventRepository : IEventRepository return; } - var entities = e.Where(ev => ev is EventTableEntity).Select(ev => ev as EventTableEntity); + var entities = e.OfType(); var entityGroups = entities.GroupBy(ent => ent.PartitionKey); foreach (var group in entityGroups) { @@ -134,7 +136,7 @@ public class EventRepository : IEventRepository var result = new PagedResult(); var query = _tableClient.QueryAsync(filter, pageOptions.PageSize); - await using (var enumerator = query.AsPages(pageOptions?.ContinuationToken, + await using (var enumerator = query.AsPages(pageOptions.ContinuationToken, pageOptions.PageSize).GetAsyncEnumerator()) { await enumerator.MoveNextAsync(); diff --git a/src/Core/Repositories/TableStorage/InstallationDeviceRepository.cs b/src/Core/Repositories/TableStorage/InstallationDeviceRepository.cs index 2dee07dc2..a47ebe873 100644 --- a/src/Core/Repositories/TableStorage/InstallationDeviceRepository.cs +++ b/src/Core/Repositories/TableStorage/InstallationDeviceRepository.cs @@ -2,6 +2,8 @@ using Bit.Core.Models.Data; using Bit.Core.Settings; +#nullable enable + namespace Bit.Core.Repositories.TableStorage; public class InstallationDeviceRepository : IInstallationDeviceRepository @@ -23,9 +25,9 @@ public class InstallationDeviceRepository : IInstallationDeviceRepository await _tableClient.UpsertEntityAsync(entity); } - public async Task UpsertManyAsync(IList entities) + public async Task UpsertManyAsync(IList? entities) { - if (!entities?.Any() ?? true) + if (entities is null || !entities.Any()) { return; } diff --git a/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs new file mode 100644 index 000000000..f0ab78a83 --- /dev/null +++ b/src/Core/SecretsManager/AuthorizationRequirements/BulkSecretOperationRequirement.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Authorization.Infrastructure; + +namespace Bit.Core.SecretsManager.AuthorizationRequirements; + +public class BulkSecretOperationRequirement : OperationAuthorizationRequirement +{ +} + +public static class BulkSecretOperations +{ + public static readonly BulkSecretOperationRequirement ReadAll = new() { Name = nameof(ReadAll) }; +} diff --git a/src/Core/SecretsManager/Commands/Requests/Interfaces/IRequestSMAccessCommand.cs b/src/Core/SecretsManager/Commands/Requests/Interfaces/IRequestSMAccessCommand.cs new file mode 100644 index 000000000..330c38553 --- /dev/null +++ b/src/Core/SecretsManager/Commands/Requests/Interfaces/IRequestSMAccessCommand.cs @@ -0,0 +1,10 @@ +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; + +namespace Bit.Core.SecretsManager.Commands.Requests.Interfaces; + +public interface IRequestSMAccessCommand +{ + Task SendRequestAccessToSM(Organization organization, ICollection orgUsers, User user, string emailContent); +} diff --git a/src/Core/SecretsManager/Models/Data/ProjectCounts.cs b/src/Core/SecretsManager/Models/Data/ProjectCounts.cs new file mode 100644 index 000000000..fa809c1fb --- /dev/null +++ b/src/Core/SecretsManager/Models/Data/ProjectCounts.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.SecretsManager.Models.Data; + +public class ProjectCounts +{ + public int Secrets { get; set; } + + public int People { get; set; } + + public int ServiceAccounts { get; set; } +} diff --git a/src/Core/SecretsManager/Models/Data/ServiceAccountCounts.cs b/src/Core/SecretsManager/Models/Data/ServiceAccountCounts.cs new file mode 100644 index 000000000..261453dc2 --- /dev/null +++ b/src/Core/SecretsManager/Models/Data/ServiceAccountCounts.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.SecretsManager.Models.Data; + +public class ServiceAccountCounts +{ + public int Projects { get; set; } + + public int People { get; set; } + + public int AccessTokens { get; set; } +} diff --git a/src/Core/SecretsManager/Models/Mail/RequestSecretsManagerAccessViewModel.cs b/src/Core/SecretsManager/Models/Mail/RequestSecretsManagerAccessViewModel.cs new file mode 100644 index 000000000..1e35f97d1 --- /dev/null +++ b/src/Core/SecretsManager/Models/Mail/RequestSecretsManagerAccessViewModel.cs @@ -0,0 +1,10 @@ +using Bit.Core.Models.Mail; + +namespace Bit.Core.SecretsManager.Models.Mail; + +public class RequestSecretsManagerAccessViewModel : BaseMailModel +{ + public string UserNameRequestingAccess { get; set; } + public string OrgName { get; set; } + public string EmailContent { get; set; } +} diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index cc3aa40cf..7a084b42c 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -17,6 +17,8 @@ public interface IProjectRepository Task<(bool Read, bool Write)> AccessToProjectAsync(Guid id, Guid userId, AccessClientType accessType); Task ProjectsAreInOrganization(List projectIds, Guid organizationId); Task GetProjectCountByOrganizationIdAsync(Guid organizationId); + Task GetProjectCountByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); + Task GetProjectCountsByIdAsync(Guid projectId, Guid userId, AccessClientType accessType); Task> AccessToProjectsAsync(IEnumerable projectIds, Guid userId, AccessClientType accessType); } diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index 8492bac50..20ebb61e9 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -21,6 +21,8 @@ public interface ISecretRepository Task RestoreManyByIdAsync(IEnumerable ids); Task> ImportAsync(IEnumerable secrets); Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType); + Task> AccessToSecretsAsync(IEnumerable ids, Guid userId, AccessClientType accessType); Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays); Task GetSecretsCountByOrganizationIdAsync(Guid organizationId); + Task GetSecretsCountByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); } diff --git a/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs b/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs index 9fa412ddf..a2d12578d 100644 --- a/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs +++ b/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs @@ -17,6 +17,9 @@ public interface IServiceAccountRepository Task> AccessToServiceAccountsAsync(IEnumerable ids, Guid userId, AccessClientType accessType); Task GetServiceAccountCountByOrganizationIdAsync(Guid organizationId); + Task GetServiceAccountCountByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); + Task GetServiceAccountCountsByIdAsync(Guid serviceAccountId, Guid userId, AccessClientType accessType); + Task> GetManyByOrganizationIdWithSecretsDetailsAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task ServiceAccountsAreInOrganizationAsync(List serviceAccountIds, Guid organizationId); } diff --git a/src/Core/SecretsManager/Repositories/Noop/NoopProjectRepository.cs b/src/Core/SecretsManager/Repositories/Noop/NoopProjectRepository.cs index acd428a67..439b32197 100644 --- a/src/Core/SecretsManager/Repositories/Noop/NoopProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/Noop/NoopProjectRepository.cs @@ -63,6 +63,17 @@ public class NoopProjectRepository : IProjectRepository return Task.FromResult(0); } + public Task GetProjectCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + return Task.FromResult(0); + } + + public Task GetProjectCountsByIdAsync(Guid projectId, Guid userId, AccessClientType accessType) + { + return Task.FromResult(null as ProjectCounts); + } + public Task> AccessToProjectsAsync(IEnumerable projectIds, Guid userId, AccessClientType accessType) { diff --git a/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs b/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs index 0448bbaf2..2d434df59 100644 --- a/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs @@ -81,6 +81,12 @@ public class NoopSecretRepository : ISecretRepository return Task.FromResult((false, false)); } + public Task> AccessToSecretsAsync(IEnumerable ids, + Guid userId, AccessClientType accessType) + { + return Task.FromResult(null as Dictionary); + } + public Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays) { return Task.FromResult(0); @@ -90,4 +96,10 @@ public class NoopSecretRepository : ISecretRepository { return Task.FromResult(0); } + + public Task GetSecretsCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + return Task.FromResult(0); + } } diff --git a/src/Core/SecretsManager/Repositories/Noop/NoopServiceAccountRepository.cs b/src/Core/SecretsManager/Repositories/Noop/NoopServiceAccountRepository.cs index 8b5ece931..7155608bc 100644 --- a/src/Core/SecretsManager/Repositories/Noop/NoopServiceAccountRepository.cs +++ b/src/Core/SecretsManager/Repositories/Noop/NoopServiceAccountRepository.cs @@ -64,6 +64,18 @@ public class NoopServiceAccountRepository : IServiceAccountRepository return Task.FromResult(0); } + public Task GetServiceAccountCountByOrganizationIdAsync(Guid organizationId, Guid userId, + AccessClientType accessType) + { + return Task.FromResult(0); + } + + public Task GetServiceAccountCountsByIdAsync(Guid serviceAccountId, Guid userId, + AccessClientType accessType) + { + return Task.FromResult(null as ServiceAccountCounts); + } + public Task> GetManyByOrganizationIdWithSecretsDetailsAsync( Guid organizationId, Guid userId, AccessClientType accessType) { diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 14a08e910..5e786bbe0 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Auth.Entities; +using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Models.Mail; @@ -11,6 +12,11 @@ public interface IMailService Task SendWelcomeEmailAsync(User user); Task SendVerifyEmailEmailAsync(string email, Guid userId, string token); Task SendRegistrationVerificationEmailAsync(string email, string token); + Task SendTrialInitiationSignupEmailAsync( + string email, + string token, + ProductTierType productTier, + IEnumerable products); Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token); Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail); Task SendChangeEmailEmailAsync(string newEmailAddress, string token); @@ -81,5 +87,6 @@ public interface IMailService Task SendTrialInitiationEmailAsync(string email); Task SendInitiateDeletProviderEmailAsync(string email, Provider provider, string token); Task SendInitiateDeleteOrganzationEmailAsync(string email, Organization organization, string token); + Task SendRequestSMAccessToAdminEmailAsync(IEnumerable adminEmails, string organizationName, string userRequestingAccess, string emailContent); } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index a2e50f0ca..0888fb7cf 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -65,6 +65,7 @@ public interface IUserService Task CheckPasswordAsync(User user, string password); Task CanAccessPremium(ITwoFactorProvidersUser user); Task HasPremiumFromOrganization(ITwoFactorProvidersUser user); + [Obsolete("Use ITwoFactorIsEnabledQuery instead.")] Task TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user); Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user); Task GenerateSignInTokenAsync(User user, string purpose); @@ -75,7 +76,7 @@ public interface IUserService string GetUserName(ClaimsPrincipal principal); Task SendOTPAsync(User user); Task VerifyOTPAsync(User user, string token); - Task VerifySecretAsync(User user, string secret); + Task VerifySecretAsync(User user, string secret, bool isSettingMFA = false); void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true); diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index d8d248526..e779ac289 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -15,32 +15,23 @@ public class CollectionService : ICollectionService private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ICollectionRepository _collectionRepository; - private readonly IUserRepository _userRepository; - private readonly IMailService _mailService; private readonly IReferenceEventService _referenceEventService; private readonly ICurrentContext _currentContext; - private readonly IFeatureService _featureService; public CollectionService( IEventService eventService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository, - IUserRepository userRepository, - IMailService mailService, IReferenceEventService referenceEventService, - ICurrentContext currentContext, - IFeatureService featureService) + ICurrentContext currentContext) { _eventService = eventService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _collectionRepository = collectionRepository; - _userRepository = userRepository; - _mailService = mailService; _referenceEventService = referenceEventService; _currentContext = currentContext; - _featureService = featureService; } public async Task SaveAsync(Collection collection, IEnumerable groups = null, @@ -55,26 +46,20 @@ public class CollectionService : ICollectionService var groupsList = groups?.ToList(); var usersList = users?.ToList(); - if (org.FlexibleCollections) + // Cannot use Manage with ReadOnly/HidePasswords permissions + var invalidAssociations = groupsList?.Where(cas => cas.Manage && (cas.ReadOnly || cas.HidePasswords)); + if (invalidAssociations?.Any() ?? false) { - // 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."); - } + throw new BadRequestException("The Manage property is mutually exclusive and cannot be true while the ReadOnly or HidePasswords properties are also true."); + } - // If using Flexible Collections V1 - a collection should always have someone with Can Manage permissions - if (_featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1)) - { - 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."); - } - } + // 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)) @@ -114,7 +99,6 @@ public class CollectionService : ICollectionService public async Task> GetOrganizationCollectionsAsync(Guid organizationId) { if ( - !await _currentContext.ViewAssignedCollections(organizationId) && !await _currentContext.ViewAllCollections(organizationId) && !await _currentContext.ManageUsers(organizationId) && !await _currentContext.ManageGroups(organizationId) && @@ -132,10 +116,7 @@ public class CollectionService : ICollectionService } else { - var collections = await _collectionRepository.GetManyByUserIdAsync( - _currentContext.UserId.Value, - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections) - ); + var collections = await _collectionRepository.GetManyByUserIdAsync(_currentContext.UserId.Value); orgCollections = collections.Where(c => c.OrganizationId == organizationId); } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index d4f56e472..fcae4462e 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -4,10 +4,13 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Models.Mail; +using Bit.Core.Billing.Enums; +using Bit.Core.Billing.Models.Mail; using Bit.Core.Entities; using Bit.Core.Models.Mail; using Bit.Core.Models.Mail.FamiliesForEnterprise; using Bit.Core.Models.Mail.Provider; +using Bit.Core.SecretsManager.Models.Mail; using Bit.Core.Settings; using Bit.Core.Utilities; using HandlebarsDotNet; @@ -69,6 +72,27 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendTrialInitiationSignupEmailAsync( + string email, + string token, + ProductTierType productTier, + IEnumerable products) + { + var message = CreateDefaultMessage("Verify your email", email); + var model = new TrialInitiationVerifyEmail + { + Token = WebUtility.UrlEncode(token), + Email = WebUtility.UrlEncode(email), + WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, + SiteName = _globalSettings.SiteName, + ProductTier = productTier, + Product = products + }; + await AddMessageContentAsync(message, "Billing.TrialInitiationVerifyEmail", model); + message.MetaData.Add("SendGridBypassListManagement", true); + message.Category = "VerifyEmail"; + await _mailDeliveryService.SendEmailAsync(message); + } public async Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token) { @@ -273,7 +297,7 @@ public class HandlebarsMailService : IMailService public async Task SendTrialInitiationEmailAsync(string userEmail) { - var message = CreateDefaultMessage("Welcome to Bitwarden!", userEmail); + var message = CreateDefaultMessage("Welcome to Bitwarden; 3 steps to get started!", userEmail); var model = new BaseMailModel { WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHashAndSecretManagerProduct, @@ -395,6 +419,20 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendRequestSMAccessToAdminEmailAsync(IEnumerable emails, string organizationName, string requestingUserName, string emailContent) + { + var message = CreateDefaultMessage("Access Requested for Secrets Manager", emails); + var model = new RequestSecretsManagerAccessViewModel + { + OrgName = CoreHelpers.SanitizeForEmail(organizationName, false), + UserNameRequestingAccess = CoreHelpers.SanitizeForEmail(requestingUserName, false), + EmailContent = CoreHelpers.SanitizeForEmail(emailContent, false), + }; + await AddMessageContentAsync(message, "SecretsManagerAccessRequest", model); + message.Category = "SecretsManagerAccessRequest"; + await _mailDeliveryService.SendEmailAsync(message); + } + public async Task SendNewDeviceLoggedInEmail(string email, string deviceType, DateTime timestamp, string ip) { var message = CreateDefaultMessage($"New Device Logged In From {deviceType}", email); @@ -477,7 +515,7 @@ public class HandlebarsMailService : IMailService return CreateDefaultMessage(subject, new List { toEmail }); } - private MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails) + private static MailMessage CreateDefaultMessage(string subject, IEnumerable toEmails) { return new MailMessage { diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 7d1776220..2fa84ef35 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -2,6 +2,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Billing.Constants; using Bit.Core.Billing.Models; +using Bit.Core.Billing.Models.Business; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -103,28 +104,6 @@ public class StripePaymentService : IPaymentService throw new GatewayException("Payment method is not supported at this time."); } - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - - if (!pm5766AutomaticTaxIsEnabled && - taxInfo != null && - !string.IsNullOrWhiteSpace(taxInfo.BillingAddressCountry) && - !string.IsNullOrWhiteSpace(taxInfo.BillingAddressPostalCode)) - { - var taxRateSearch = new TaxRate - { - Country = taxInfo.BillingAddressCountry, - PostalCode = taxInfo.BillingAddressPostalCode - }; - var taxRates = await _taxRateRepository.GetByLocationAsync(taxRateSearch); - - // should only be one tax rate per country/zip combo - var taxRate = taxRates.FirstOrDefault(); - if (taxRate != null) - { - taxInfo.StripeTaxRateId = taxRate.Id; - } - } - var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon , additionalSmSeats, additionalServiceAccount); @@ -180,7 +159,7 @@ public class StripePaymentService : IPaymentService subCreateOptions.AddExpand("latest_invoice.payment_intent"); subCreateOptions.Customer = customer.Id; - if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer)) + if (CustomerHasTaxLocationVerified(customer)) { subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; } @@ -273,31 +252,7 @@ public class StripePaymentService : IPaymentService throw new GatewayException("Could not find customer payment profile."); } - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - var taxInfo = upgrade.TaxInfo; - - if (!pm5766AutomaticTaxIsEnabled && - taxInfo != null && - !string.IsNullOrWhiteSpace(taxInfo.BillingAddressCountry) && - !string.IsNullOrWhiteSpace(taxInfo.BillingAddressPostalCode)) - { - var taxRateSearch = new TaxRate - { - Country = taxInfo.BillingAddressCountry, - PostalCode = taxInfo.BillingAddressPostalCode - }; - var taxRates = await _taxRateRepository.GetByLocationAsync(taxRateSearch); - - // should only be one tax rate per country/zip combo - var taxRate = taxRates.FirstOrDefault(); - if (taxRate != null) - { - taxInfo.StripeTaxRateId = taxRate.Id; - } - } - - if (pm5766AutomaticTaxIsEnabled && - !string.IsNullOrEmpty(upgrade.TaxInfo?.BillingAddressCountry) && + if (!string.IsNullOrEmpty(upgrade.TaxInfo?.BillingAddressCountry) && !string.IsNullOrEmpty(upgrade.TaxInfo?.BillingAddressPostalCode)) { var addressOptions = new AddressOptions @@ -319,7 +274,7 @@ public class StripePaymentService : IPaymentService var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade); - if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer)) + if (CustomerHasTaxLocationVerified(customer)) { subCreateOptions.DefaultTaxRates = []; subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; @@ -533,26 +488,6 @@ public class StripePaymentService : IPaymentService Quantity = 1 }); - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - - if (!pm5766AutomaticTaxIsEnabled && - !string.IsNullOrWhiteSpace(taxInfo?.BillingAddressCountry) && - !string.IsNullOrWhiteSpace(taxInfo?.BillingAddressPostalCode)) - { - var taxRates = await _taxRateRepository.GetByLocationAsync( - new TaxRate - { - Country = taxInfo.BillingAddressCountry, - PostalCode = taxInfo.BillingAddressPostalCode - } - ); - var taxRate = taxRates.FirstOrDefault(); - if (taxRate != null) - { - subCreateOptions.DefaultTaxRates = [taxRate.Id]; - } - } - if (additionalStorageGb > 0) { subCreateOptions.Items.Add(new SubscriptionItemOptions @@ -562,7 +497,7 @@ public class StripePaymentService : IPaymentService }); } - if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer)) + if (CustomerHasTaxLocationVerified(customer)) { subCreateOptions.DefaultTaxRates = []; subCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }; @@ -605,8 +540,7 @@ public class StripePaymentService : IPaymentService SubscriptionItems = ToInvoiceSubscriptionItemOptions(subCreateOptions.Items) }); - if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax) && - CustomerHasTaxLocationVerified(customer)) + if (CustomerHasTaxLocationVerified(customer)) { previewInvoice.AutomaticTax = new InvoiceAutomaticTax { Enabled = true }; } @@ -669,8 +603,7 @@ public class StripePaymentService : IPaymentService SubscriptionDefaultTaxRates = subCreateOptions.DefaultTaxRates, }; - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - if (pm5766AutomaticTaxIsEnabled && CustomerHasTaxLocationVerified(customer)) + if (CustomerHasTaxLocationVerified(customer)) { upcomingInvoiceOptions.AutomaticTax = new InvoiceAutomaticTaxOptions { Enabled = true }; upcomingInvoiceOptions.SubscriptionDefaultTaxRates = []; @@ -800,9 +733,7 @@ public class StripePaymentService : IPaymentService new SubscriptionPendingInvoiceItemIntervalOptions { Interval = "month" }; } - var pm5766AutomaticTaxIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax); - if (pm5766AutomaticTaxIsEnabled && - sub.AutomaticTax.Enabled != true && + if (sub.AutomaticTax.Enabled != true && CustomerHasTaxLocationVerified(sub.Customer)) { subUpdateOptions.DefaultTaxRates = []; @@ -815,26 +746,6 @@ public class StripePaymentService : IPaymentService return null; } - if (!pm5766AutomaticTaxIsEnabled) - { - var customer = await _stripeAdapter.CustomerGetAsync(sub.CustomerId); - - if (!string.IsNullOrWhiteSpace(customer?.Address?.Country) - && !string.IsNullOrWhiteSpace(customer?.Address?.PostalCode)) - { - var taxRates = await _taxRateRepository.GetByLocationAsync(new TaxRate - { - Country = customer.Address.Country, - PostalCode = customer.Address.PostalCode - }); - var taxRate = taxRates.FirstOrDefault(); - if (taxRate != null && !sub.DefaultTaxRates.Any(x => x.Equals(taxRate.Id))) - { - subUpdateOptions.DefaultTaxRates = [taxRate.Id]; - } - } - } - string paymentIntentClientSecret = null; try { @@ -1502,8 +1413,7 @@ public class StripePaymentService : IPaymentService }); } - if (_featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax) && - !string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) && + if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId) && customer.Subscriptions.Any(sub => sub.Id == subscriber.GatewaySubscriptionId && !sub.AutomaticTax.Enabled) && diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 9239b3a2b..2132e6480 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -325,9 +325,8 @@ public class UserService : UserManager, IUserService, IDisposable } var email = ((string)provider.MetaData["Email"]).ToLowerInvariant(); - var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, - "2faEmail:" + email); - + var token = await base.GenerateTwoFactorTokenAsync(user, + CoreHelpers.CustomProviderName(TwoFactorProviderType.Email)); await _mailService.SendTwoFactorEmailAsync(email, token); } @@ -340,8 +339,8 @@ public class UserService : UserManager, IUserService, IDisposable } var email = ((string)provider.MetaData["Email"]).ToLowerInvariant(); - return await base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider, - "2faEmail:" + email, token); + return await base.VerifyTwoFactorTokenAsync(user, + CoreHelpers.CustomProviderName(TwoFactorProviderType.Email), token); } public async Task StartWebAuthnRegistrationAsync(User user) @@ -1343,7 +1342,7 @@ public class UserService : UserManager, IUserService, IDisposable "otp:" + user.Email, token); } - public async Task VerifySecretAsync(User user, string secret) + public async Task VerifySecretAsync(User user, string secret, bool isSettingMFA = false) { bool isVerified; if (user.HasMasterPassword()) @@ -1355,6 +1354,12 @@ public class UserService : UserManager, IUserService, IDisposable isVerified = await CheckPasswordAsync(user, secret) || await VerifyOTPAsync(user, secret); } + else if (isSettingMFA) + { + // this is temporary to allow users to view their MFA settings without invalidating email TOTP + // Will be removed with PM-9925 + isVerified = true; + } else { // If they don't have a password at all they can only do OTP diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 998714d13..f637ae904 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -1,6 +1,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Auth.Entities; +using Bit.Core.Billing.Enums; using Bit.Core.Entities; using Bit.Core.Models.Mail; @@ -23,6 +24,15 @@ public class NoopMailService : IMailService return Task.FromResult(0); } + public Task SendTrialInitiationSignupEmailAsync( + string email, + string token, + ProductTierType productTier, + IEnumerable products) + { + return Task.FromResult(0); + } + public Task SendChangeEmailEmailAsync(string newEmailAddress, string token) { return Task.FromResult(0); @@ -280,5 +290,6 @@ public class NoopMailService : IMailService { return Task.FromResult(0); } + public Task SendRequestSMAccessToAdminEmailAsync(IEnumerable adminEmails, string organizationName, string userRequestingAccess, string emailContent) => throw new NotImplementedException(); } diff --git a/src/Core/Tools/Enums/ReferenceEventSource.cs b/src/Core/Tools/Enums/ReferenceEventSource.cs index 6030cb201..87a71cf45 100644 --- a/src/Core/Tools/Enums/ReferenceEventSource.cs +++ b/src/Core/Tools/Enums/ReferenceEventSource.cs @@ -10,6 +10,6 @@ public enum ReferenceEventSource User, [EnumMember(Value = "provider")] Provider, - [EnumMember(Value = "registrationStart")] - RegistrationStart, + [EnumMember(Value = "registration")] + Registration, } diff --git a/src/Core/Tools/Enums/ReferenceEventType.cs b/src/Core/Tools/Enums/ReferenceEventType.cs index 17d86e717..a1446b9fc 100644 --- a/src/Core/Tools/Enums/ReferenceEventType.cs +++ b/src/Core/Tools/Enums/ReferenceEventType.cs @@ -6,6 +6,8 @@ public enum ReferenceEventType { [EnumMember(Value = "signup-email-submit")] SignupEmailSubmit, + [EnumMember(Value = "signup-email-clicked")] + SignupEmailClicked, [EnumMember(Value = "signup")] Signup, [EnumMember(Value = "upgrade-plan")] diff --git a/src/Core/Tools/Models/Business/ReferenceEvent.cs b/src/Core/Tools/Models/Business/ReferenceEvent.cs index 9b4befdbc..a93817ca4 100644 --- a/src/Core/Tools/Models/Business/ReferenceEvent.cs +++ b/src/Core/Tools/Models/Business/ReferenceEvent.cs @@ -256,7 +256,19 @@ public class ReferenceEvent public string? PlanUpgradePath { get; set; } /// - /// Used for the sign up event to determine if the user has opted in to marketing emails. + /// Used for the event to determine if the user has opted in to marketing emails. /// public bool? ReceiveMarketingEmails { get; set; } + + /// + /// Used for the event to indicate if the user + /// landed on the registration finish screen with a valid or invalid email verification token. + /// + public bool? EmailVerificationTokenValid { get; set; } + + /// + /// Used for the event to indicate if the user + /// landed on the registration finish screen after re-clicking an already used link. + /// + public bool? UserAlreadyExists { get; set; } } diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index c263ccdbe..d900c82e2 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -388,6 +388,8 @@ public static class CoreHelpers /// Base64 standard formatted string public static string TransformFromBase64Url(string input) { + // TODO: .NET 9 Ships Base64Url in box, investigate replacing this usage with that + // Ref: https://github.com/dotnet/runtime/pull/102364 var output = input; // 62nd char of encoding output = output.Replace('-', '+'); @@ -700,12 +702,6 @@ public static class CoreHelpers claims.Add(new KeyValuePair(Claims.OrganizationAdmin, org.Id.ToString())); } break; - case Enums.OrganizationUserType.Manager: - foreach (var org in group) - { - claims.Add(new KeyValuePair(Claims.OrganizationManager, org.Id.ToString())); - } - break; case Enums.OrganizationUserType.User: foreach (var org in group) { @@ -876,4 +872,40 @@ public static class CoreHelpers { return _whiteSpaceRegex.Replace(input, newValue); } + + public static string RedactEmailAddress(string email) + { + if (string.IsNullOrWhiteSpace(email)) + { + return null; + } + + var emailParts = email.Split('@'); + + string shownPart; + if (emailParts[0].Length > 2 && emailParts[0].Length <= 4) + { + shownPart = emailParts[0].Substring(0, 1); + } + else if (emailParts[0].Length > 4) + { + shownPart = emailParts[0].Substring(0, 2); + } + else + { + shownPart = string.Empty; + } + + string redactedPart; + if (emailParts[0].Length > 4) + { + redactedPart = new string('*', emailParts[0].Length - 2); + } + else + { + redactedPart = new string('*', emailParts[0].Length - shownPart.Length); + } + + return $"{shownPart}{redactedPart}@{emailParts[1]}"; + } } diff --git a/src/Core/Vault/Queries/OrganizationCiphersQuery.cs b/src/Core/Vault/Queries/OrganizationCiphersQuery.cs index feed09808..f91e3cbbb 100644 --- a/src/Core/Vault/Queries/OrganizationCiphersQuery.cs +++ b/src/Core/Vault/Queries/OrganizationCiphersQuery.cs @@ -1,6 +1,4 @@ -using Bit.Core.Exceptions; -using Bit.Core.Repositories; -using Bit.Core.Services; +using Bit.Core.Repositories; using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Repositories; @@ -10,15 +8,11 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery { private readonly ICipherRepository _cipherRepository; private readonly ICollectionCipherRepository _collectionCipherRepository; - private readonly IFeatureService _featureService; - private bool FlexibleCollectionsV1Enabled => _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); - - public OrganizationCiphersQuery(ICipherRepository cipherRepository, ICollectionCipherRepository collectionCipherRepository, IFeatureService featureService) + public OrganizationCiphersQuery(ICipherRepository cipherRepository, ICollectionCipherRepository collectionCipherRepository) { _cipherRepository = cipherRepository; _collectionCipherRepository = collectionCipherRepository; - _featureService = featureService; } /// @@ -26,13 +20,7 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery /// public async Task> GetOrganizationCiphersForUser(Guid organizationId, Guid userId) { - if (!FlexibleCollectionsV1Enabled) - { - // Flexible collections is OFF, should not be using this query - throw new FeatureUnavailableException("Flexible collections is OFF when it should be ON."); - } - - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: true, withOrganizations: true); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: true); var orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId).ToList(); var orgCipherIds = orgCiphers.Select(c => c.Id); @@ -50,12 +38,6 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery /// public async Task> GetAllOrganizationCiphers(Guid organizationId) { - if (!FlexibleCollectionsV1Enabled) - { - // Flexible collections is OFF, should not be using this query - throw new FeatureUnavailableException("Flexible collections is OFF when it should be ON."); - } - var orgCiphers = await _cipherRepository.GetManyOrganizationDetailsByOrganizationIdAsync(organizationId); var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(organizationId); var collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key); @@ -68,12 +50,6 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery /// public async Task> GetUnassignedOrganizationCiphers(Guid organizationId) { - if (!FlexibleCollectionsV1Enabled) - { - // Flexible collections is OFF, should not be using this query - throw new FeatureUnavailableException("Flexible collections is OFF when it should be ON."); - } - return await _cipherRepository.GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(organizationId); } } diff --git a/src/Core/Vault/Repositories/ICipherRepository.cs b/src/Core/Vault/Repositories/ICipherRepository.cs index 630778e84..132aa5ac6 100644 --- a/src/Core/Vault/Repositories/ICipherRepository.cs +++ b/src/Core/Vault/Repositories/ICipherRepository.cs @@ -12,7 +12,7 @@ public interface ICipherRepository : IRepository Task GetOrganizationDetailsByIdAsync(Guid id); Task> GetManyOrganizationDetailsByOrganizationIdAsync(Guid organizationId); Task GetCanEditByIdAsync(Guid userId, Guid cipherId); - Task> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true); + Task> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true); Task> GetManyByOrganizationIdAsync(Guid organizationId); Task> GetManyUnassignedOrganizationDetailsByOrganizationIdAsync(Guid organizationId); Task CreateAsync(Cipher cipher, IEnumerable collectionIds); diff --git a/src/Core/Vault/Services/Implementations/CipherService.cs b/src/Core/Vault/Services/Implementations/CipherService.cs index 4f4905e2a..d6947b541 100644 --- a/src/Core/Vault/Services/Implementations/CipherService.cs +++ b/src/Core/Vault/Services/Implementations/CipherService.cs @@ -38,10 +38,6 @@ public class CipherService : ICipherService private const long _fileSizeLeeway = 1024L * 1024L; // 1MB private readonly IReferenceEventService _referenceEventService; private readonly ICurrentContext _currentContext; - private readonly IFeatureService _featureService; - - private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); public CipherService( ICipherRepository cipherRepository, @@ -58,8 +54,7 @@ public class CipherService : ICipherService IPolicyService policyService, GlobalSettings globalSettings, IReferenceEventService referenceEventService, - ICurrentContext currentContext, - IFeatureService featureService) + ICurrentContext currentContext) { _cipherRepository = cipherRepository; _folderRepository = folderRepository; @@ -76,7 +71,6 @@ public class CipherService : ICipherService _globalSettings = globalSettings; _referenceEventService = referenceEventService; _currentContext = currentContext; - _featureService = featureService; } public async Task SaveAsync(Cipher cipher, Guid savingUserId, DateTime? lastKnownRevisionDate, @@ -430,7 +424,7 @@ public class CipherService : ICipherService } else { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId, useFlexibleCollections: UseFlexibleCollections); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId); deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList(); await _cipherRepository.DeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId); @@ -788,15 +782,12 @@ public class CipherService : ICipherService { collection.SetNewId(); newCollections.Add(collection); - if (org.FlexibleCollections) + newCollectionUsers.Add(new CollectionUser { - newCollectionUsers.Add(new CollectionUser - { - CollectionId = collection.Id, - OrganizationUserId = importingOrgUser.Id, - Manage = true - }); - } + CollectionId = collection.Id, + OrganizationUserId = importingOrgUser.Id, + Manage = true + }); } } @@ -875,7 +866,7 @@ public class CipherService : ICipherService } else { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId, useFlexibleCollections: UseFlexibleCollections); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(deletingUserId); deletingCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(x => (Cipher)x).ToList(); await _cipherRepository.SoftDeleteAsync(deletingCiphers.Select(c => c.Id), deletingUserId); @@ -941,7 +932,7 @@ public class CipherService : ICipherService } else { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId, useFlexibleCollections: UseFlexibleCollections); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(restoringUserId); restoringCiphers = ciphers.Where(c => cipherIdsSet.Contains(c.Id) && c.Edit).Select(c => (CipherOrganizationDetails)c).ToList(); revisionDate = await _cipherRepository.RestoreAsync(restoringCiphers.Select(c => c.Id), restoringUserId); @@ -979,7 +970,7 @@ public class CipherService : ICipherService } else { - var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, useFlexibleCollections: UseFlexibleCollections, withOrganizations: true); + var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: true); orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId); } diff --git a/src/Identity/Billing/Controller/AccountsController.cs b/src/Identity/Billing/Controller/AccountsController.cs new file mode 100644 index 000000000..f06fc7bf2 --- /dev/null +++ b/src/Identity/Billing/Controller/AccountsController.cs @@ -0,0 +1,48 @@ +using Bit.Core; +using Bit.Core.Billing.Models.Api.Requests.Accounts; +using Bit.Core.Billing.TrialInitiation.Registration; +using Bit.Core.Context; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Models.Business; +using Bit.Core.Tools.Services; +using Bit.Core.Utilities; +using Bit.SharedWeb.Utilities; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Identity.Billing.Controller; + +[Route("accounts")] +[ExceptionHandlerFilter] +public class AccountsController( + ICurrentContext currentContext, + ISendTrialInitiationEmailForRegistrationCommand sendTrialInitiationEmailForRegistrationCommand, + IReferenceEventService referenceEventService) : Microsoft.AspNetCore.Mvc.Controller +{ + [RequireFeature(FeatureFlagKeys.EmailVerification)] + [HttpPost("trial/send-verification-email")] + public async Task PostTrialInitiationSendVerificationEmailAsync([FromBody] TrialSendVerificationEmailRequestModel model) + { + var token = await sendTrialInitiationEmailForRegistrationCommand.Handle( + model.Email, + model.Name, + model.ReceiveMarketingEmails, + model.ProductTier, + model.Products); + + var refEvent = new ReferenceEvent + { + Type = ReferenceEventType.SignupEmailSubmit, + ClientId = currentContext.ClientId, + ClientVersion = currentContext.ClientVersion, + Source = ReferenceEventSource.Registration + }; + await referenceEventService.RaiseEventAsync(refEvent); + + if (token != null) + { + return Ok(token); + } + + return NoContent(); + } +} diff --git a/src/Identity/Controllers/AccountsController.cs b/src/Identity/Controllers/AccountsController.cs index 37a18bb9a..c3cad4a4a 100644 --- a/src/Identity/Controllers/AccountsController.cs +++ b/src/Identity/Controllers/AccountsController.cs @@ -41,6 +41,7 @@ public class AccountsController : Controller private readonly ISendVerificationEmailForRegistrationCommand _sendVerificationEmailForRegistrationCommand; private readonly IReferenceEventService _referenceEventService; private readonly IFeatureService _featureService; + private readonly IDataProtectorTokenFactory _registrationEmailVerificationTokenDataFactory; public AccountsController( ICurrentContext currentContext, @@ -52,7 +53,8 @@ public class AccountsController : Controller IGetWebAuthnLoginCredentialAssertionOptionsCommand getWebAuthnLoginCredentialAssertionOptionsCommand, ISendVerificationEmailForRegistrationCommand sendVerificationEmailForRegistrationCommand, IReferenceEventService referenceEventService, - IFeatureService featureService + IFeatureService featureService, + IDataProtectorTokenFactory registrationEmailVerificationTokenDataFactory ) { _currentContext = currentContext; @@ -65,6 +67,7 @@ public class AccountsController : Controller _sendVerificationEmailForRegistrationCommand = sendVerificationEmailForRegistrationCommand; _referenceEventService = referenceEventService; _featureService = featureService; + _registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory; } [HttpPost("register")] @@ -90,7 +93,7 @@ public class AccountsController : Controller Type = ReferenceEventType.SignupEmailSubmit, ClientId = _currentContext.ClientId, ClientVersion = _currentContext.ClientVersion, - Source = ReferenceEventSource.RegistrationStart + Source = ReferenceEventSource.Registration }; await _referenceEventService.RaiseEventAsync(refEvent); @@ -102,6 +105,39 @@ public class AccountsController : Controller return NoContent(); } + [RequireFeature(FeatureFlagKeys.EmailVerification)] + [HttpPost("register/verification-email-clicked")] + public async Task PostRegisterVerificationEmailClicked([FromBody] RegisterVerificationEmailClickedRequestModel model) + { + var tokenValid = RegistrationEmailVerificationTokenable.ValidateToken(_registrationEmailVerificationTokenDataFactory, model.EmailVerificationToken, model.Email); + + // Check to see if the user already exists - this is just to catch the unlikely but possible case + // where a user finishes registration and then clicks the email verification link again. + var user = await _userRepository.GetByEmailAsync(model.Email); + var userExists = user != null; + + var refEvent = new ReferenceEvent + { + Type = ReferenceEventType.SignupEmailClicked, + ClientId = _currentContext.ClientId, + ClientVersion = _currentContext.ClientVersion, + Source = ReferenceEventSource.Registration, + EmailVerificationTokenValid = tokenValid, + UserAlreadyExists = userExists + }; + + await _referenceEventService.RaiseEventAsync(refEvent); + + if (!tokenValid || userExists) + { + throw new BadRequestException("Expired link. Please restart registration or try logging in. You may already have an account"); + } + + return Ok(); + + + } + [RequireFeature(FeatureFlagKeys.EmailVerification)] [HttpPost("register/finish")] public async Task PostRegisterFinish([FromBody] RegisterFinishRequestModel model) diff --git a/src/Identity/Identity.csproj b/src/Identity/Identity.csproj index b88cec5e3..f4db37deb 100644 --- a/src/Identity/Identity.csproj +++ b/src/Identity/Identity.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Identity/IdentityServer/ApiResources.cs b/src/Identity/IdentityServer/ApiResources.cs index aa4104127..a0712aafe 100644 --- a/src/Identity/IdentityServer/ApiResources.cs +++ b/src/Identity/IdentityServer/ApiResources.cs @@ -20,7 +20,6 @@ public class ApiResources Claims.Device, Claims.OrganizationOwner, Claims.OrganizationAdmin, - Claims.OrganizationManager, Claims.OrganizationUser, Claims.OrganizationCustom, Claims.ProviderAdmin, diff --git a/src/Identity/IdentityServer/BaseRequestValidator.cs b/src/Identity/IdentityServer/BaseRequestValidator.cs index b9a262389..a7e7254b8 100644 --- a/src/Identity/IdentityServer/BaseRequestValidator.cs +++ b/src/Identity/IdentityServer/BaseRequestValidator.cs @@ -511,7 +511,9 @@ public abstract class BaseRequestValidator where T : class } else if (type == TwoFactorProviderType.Email) { - return new Dictionary { ["Email"] = token }; + var twoFactorEmail = (string)provider.MetaData["Email"]; + var redactedEmail = CoreHelpers.RedactEmailAddress(twoFactorEmail); + return new Dictionary { ["Email"] = redactedEmail }; } else if (type == TwoFactorProviderType.YubiKey) { diff --git a/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs b/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs index b9fba5af2..2dc1f2926 100644 --- a/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs +++ b/src/Identity/IdentityServer/UserDecryptionOptionsBuilder.cs @@ -4,6 +4,7 @@ using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Auth.Utilities; using Bit.Core.Context; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Identity.Utilities; @@ -95,8 +96,9 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder return; } - var ssoConfigurationData = _ssoConfig.GetData(); - if (ssoConfigurationData is not { MemberDecryptionType: MemberDecryptionType.TrustedDeviceEncryption }) + var isTdeActive = _ssoConfig.GetData() is { MemberDecryptionType: MemberDecryptionType.TrustedDeviceEncryption }; + var isTdeOffboarding = _user != null && !_user.HasMasterPassword() && _device != null && _device.IsTrusted() && !isTdeActive; + if (!isTdeActive && !isTdeOffboarding) { return; } @@ -136,6 +138,7 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder // If sso configuration data is not null then I know for sure that ssoConfiguration isn't null var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(_ssoConfig.OrganizationId, _user.Id); + hasManageResetPasswordPermission |= organizationUser != null && (organizationUser.Type == OrganizationUserType.Owner || organizationUser.Type == OrganizationUserType.Admin); // They are only able to be approved by an admin if they have enrolled is reset password hasAdminApproval = organizationUser != null && !string.IsNullOrEmpty(organizationUser.ResetPasswordKey); } @@ -144,6 +147,7 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder hasAdminApproval, hasLoginApprovingDevice, hasManageResetPasswordPermission, + isTdeOffboarding, encryptedPrivateKey, encryptedUserKey); } diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index 61d3d291d..65c303e75 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -108,6 +108,10 @@ public class Startup options.SaveTokens = false; options.GetClaimsFromUserInfoEndpoint = true; + // Some browsers (safari) won't allow Secure cookies to be set on a http connection + options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.NonceCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; + options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents { OnRedirectToIdentityProvider = context => diff --git a/src/Identity/appsettings.Development.json b/src/Identity/appsettings.Development.json index 2eaecdfa3..0fdc9db98 100644 --- a/src/Identity/appsettings.Development.json +++ b/src/Identity/appsettings.Development.json @@ -14,6 +14,12 @@ "internalVault": "https://localhost:8080", "internalSso": "http://localhost:51822" }, + "mail": { + "smtp": { + "host": "localhost", + "port": 10250 + } + }, "attachment": { "connectionString": "UseDevelopmentStorage=true" }, diff --git a/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs index df68c06d0..db6419d38 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/AuthRequestRepository.cs @@ -8,6 +8,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class AuthRequestRepository : Repository, IAuthRequestRepository diff --git a/src/Infrastructure.Dapper/Auth/Repositories/EmergencyAccessRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/EmergencyAccessRepository.cs index 195ebfada..e6bf92bde 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/EmergencyAccessRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/EmergencyAccessRepository.cs @@ -9,6 +9,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class EmergencyAccessRepository : Repository, IEmergencyAccessRepository @@ -60,7 +62,7 @@ public class EmergencyAccessRepository : Repository, IEme } } - public async Task GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId) + public async Task GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Auth/Repositories/GrantRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/GrantRepository.cs index a12969a8f..7389dd657 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/GrantRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/GrantRepository.cs @@ -7,6 +7,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class GrantRepository : BaseRepository, IGrantRepository @@ -19,7 +21,7 @@ public class GrantRepository : BaseRepository, IGrantRepository : base(connectionString, readOnlyConnectionString) { } - public async Task GetByKeyAsync(string key) + public async Task GetByKeyAsync(string key) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Auth/Repositories/SsoConfigRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/SsoConfigRepository.cs index 0922b3a73..942ca1b3c 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/SsoConfigRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/SsoConfigRepository.cs @@ -6,6 +6,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class SsoConfigRepository : Repository, ISsoConfigRepository @@ -18,7 +20,7 @@ public class SsoConfigRepository : Repository, ISsoConfigReposi : base(connectionString, readOnlyConnectionString) { } - public async Task GetByOrganizationIdAsync(Guid organizationId) + public async Task GetByOrganizationIdAsync(Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) { @@ -31,7 +33,7 @@ public class SsoConfigRepository : Repository, ISsoConfigReposi } } - public async Task GetByIdentifierAsync(string identifier) + public async Task GetByIdentifierAsync(string identifier) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Auth/Repositories/SsoUserRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/SsoUserRepository.cs index a25708f7e..99cbc25c5 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/SsoUserRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/SsoUserRepository.cs @@ -6,6 +6,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; public class SsoUserRepository : Repository, ISsoUserRepository @@ -29,7 +31,7 @@ public class SsoUserRepository : Repository, ISsoUserRepository } } - public async Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId) + public async Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Auth/Repositories/WebAuthnCredentialRepository.cs b/src/Infrastructure.Dapper/Auth/Repositories/WebAuthnCredentialRepository.cs index 85a7cc64e..0f7e1ea1b 100644 --- a/src/Infrastructure.Dapper/Auth/Repositories/WebAuthnCredentialRepository.cs +++ b/src/Infrastructure.Dapper/Auth/Repositories/WebAuthnCredentialRepository.cs @@ -9,6 +9,8 @@ using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Auth.Repositories; @@ -22,7 +24,7 @@ public class WebAuthnCredentialRepository : Repository : base(connectionString, readOnlyConnectionString) { } - public async Task GetByIdAsync(Guid id, Guid userId) + public async Task GetByIdAsync(Guid id, Guid userId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/DapperHelpers.cs b/src/Infrastructure.Dapper/DapperHelpers.cs index d72ed0745..865cad39e 100644 --- a/src/Infrastructure.Dapper/DapperHelpers.cs +++ b/src/Infrastructure.Dapper/DapperHelpers.cs @@ -3,6 +3,8 @@ using Bit.Core.Entities; using Bit.Core.Models.Data; using Dapper; +#nullable enable + namespace Bit.Infrastructure.Dapper; public static class DapperHelpers @@ -64,7 +66,7 @@ public static class DapperHelpers var table = new DataTable(); table.SetTypeName("[dbo].[OrganizationSponsorshipType]"); - var columnData = new List<(string name, Type type, Func getter)> + var columnData = new List<(string name, Type type, Func getter)> { (nameof(OrganizationSponsorship.Id), typeof(Guid), ou => ou.Id), (nameof(OrganizationSponsorship.SponsoringOrganizationId), typeof(Guid), ou => ou.SponsoringOrganizationId), @@ -82,7 +84,7 @@ public static class DapperHelpers } public static DataTable BuildTable(this IEnumerable entities, DataTable table, - List<(string name, Type type, Func getter)> columnData) + List<(string name, Type type, Func getter)> columnData) { foreach (var (name, type, getter) in columnData) { diff --git a/src/Infrastructure.Dapper/Repositories/BaseRepository.cs b/src/Infrastructure.Dapper/Repositories/BaseRepository.cs index 4a3694d85..a5a8cd0ee 100644 --- a/src/Infrastructure.Dapper/Repositories/BaseRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/BaseRepository.cs @@ -1,5 +1,7 @@ using Dapper; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public abstract class BaseRepository diff --git a/src/Infrastructure.Dapper/Repositories/CollectionCipherRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionCipherRepository.cs index f15ebf29e..5ed82a9a2 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionCipherRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionCipherRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepository diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 81d45bcf4..2e2c90d39 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Models.Data; @@ -7,6 +8,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class CollectionRepository : Repository, ICollectionRepository @@ -32,7 +35,7 @@ public class CollectionRepository : Repository, ICollectionRep } } - public async Task> GetByIdWithAccessAsync(Guid id) + public async Task> GetByIdWithAccessAsync(Guid id) { using (var connection = new SqlConnection(ConnectionString)) { @@ -46,7 +49,7 @@ public class CollectionRepository : Repository, ICollectionRep var users = (await results.ReadAsync()).ToList(); var access = new CollectionAccessDetails { Groups = groups, Users = users }; - return new Tuple(collection, access); + return new Tuple(collection, access); } } @@ -120,16 +123,12 @@ public class CollectionRepository : Repository, ICollectionRep } } - public async Task> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections) + public async Task> GetManyByUserIdAsync(Guid userId) { - var sprocName = useFlexibleCollections - ? $"[{Schema}].[Collection_ReadByUserId_V2]" - : $"[{Schema}].[Collection_ReadByUserId]"; - using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.QueryAsync( - sprocName, + $"[{Schema}].[Collection_ReadByUserId]", new { UserId = userId }, commandType: CommandType.StoredProcedure); @@ -186,7 +185,7 @@ public class CollectionRepository : Repository, ICollectionRep } } - public async Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships) + public async Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships) { using (var connection = new SqlConnection(ConnectionString)) { @@ -197,19 +196,20 @@ public class CollectionRepository : Repository, ICollectionRep var collectionDetails = await results.ReadFirstOrDefaultAsync(); - if (!includeAccessRelationships) return collectionDetails; + if (!includeAccessRelationships || collectionDetails == null) return collectionDetails; - collectionDetails.Groups = (await results.ReadAsync()).ToList(); + // TODO-NRE: collectionDetails should be checked for null and probably return early + collectionDetails!.Groups = (await results.ReadAsync()).ToList(); collectionDetails.Users = (await results.ReadAsync()).ToList(); return collectionDetails; } } - public async Task CreateAsync(Collection obj, IEnumerable groups, IEnumerable users) + public async Task CreateAsync(Collection obj, IEnumerable? groups, IEnumerable? users) { obj.SetNewId(); - var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); + var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))!; objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); @@ -223,9 +223,9 @@ public class CollectionRepository : Repository, ICollectionRep } } - public async Task ReplaceAsync(Collection obj, IEnumerable groups, IEnumerable users) + public async Task ReplaceAsync(Collection obj, IEnumerable? groups, IEnumerable? users) { - var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj)); + var objWithGroupsAndUsers = JsonSerializer.Deserialize(JsonSerializer.Serialize(obj))!; objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty().ToArrayTVP(); @@ -311,7 +311,9 @@ public class CollectionRepository : Repository, ICollectionRep public class CollectionWithGroupsAndUsers : Collection { - public DataTable Groups { get; set; } - public DataTable Users { get; set; } + [DisallowNull] + public DataTable? Groups { get; set; } + [DisallowNull] + public DataTable? Users { get; set; } } } diff --git a/src/Infrastructure.Dapper/Repositories/DateTimeHandler.cs b/src/Infrastructure.Dapper/Repositories/DateTimeHandler.cs index ac48653ec..3063761ab 100644 --- a/src/Infrastructure.Dapper/Repositories/DateTimeHandler.cs +++ b/src/Infrastructure.Dapper/Repositories/DateTimeHandler.cs @@ -1,6 +1,8 @@ using System.Data; using Dapper; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class DateTimeHandler : SqlMapper.TypeHandler diff --git a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs index 656e4d0c9..7216d87f5 100644 --- a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class DeviceRepository : Repository, IDeviceRepository @@ -17,7 +19,7 @@ public class DeviceRepository : Repository, IDeviceRepository : base(connectionString, readOnlyConnectionString) { } - public async Task GetByIdAsync(Guid id, Guid userId) + public async Task GetByIdAsync(Guid id, Guid userId) { var device = await GetByIdAsync(id); if (device == null || device.UserId != userId) @@ -28,7 +30,7 @@ public class DeviceRepository : Repository, IDeviceRepository return device; } - public async Task GetByIdentifierAsync(string identifier) + public async Task GetByIdentifierAsync(string identifier) { using (var connection = new SqlConnection(ConnectionString)) { @@ -44,7 +46,7 @@ public class DeviceRepository : Repository, IDeviceRepository } } - public async Task GetByIdentifierAsync(string identifier, Guid userId) + public async Task GetByIdentifierAsync(string identifier, Guid userId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Repositories/EventRepository.cs b/src/Infrastructure.Dapper/Repositories/EventRepository.cs index b41687daa..85e3cc7fc 100644 --- a/src/Infrastructure.Dapper/Repositories/EventRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/EventRepository.cs @@ -7,6 +7,8 @@ using Bit.Core.Vault.Entities; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class EventRepository : Repository, IEventRepository @@ -23,7 +25,7 @@ public class EventRepository : Repository, IEventRepository PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByUserId]", - new Dictionary + new Dictionary { ["@UserId"] = userId }, startDate, endDate, pageOptions); @@ -33,7 +35,7 @@ public class EventRepository : Repository, IEventRepository DateTime startDate, DateTime endDate, PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationId]", - new Dictionary + new Dictionary { ["@OrganizationId"] = organizationId }, startDate, endDate, pageOptions); @@ -43,7 +45,7 @@ public class EventRepository : Repository, IEventRepository DateTime startDate, DateTime endDate, PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationIdActingUserId]", - new Dictionary + new Dictionary { ["@OrganizationId"] = organizationId, ["@ActingUserId"] = actingUserId @@ -54,7 +56,7 @@ public class EventRepository : Repository, IEventRepository DateTime startDate, DateTime endDate, PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByProviderId]", - new Dictionary + new Dictionary { ["@ProviderId"] = providerId }, startDate, endDate, pageOptions); @@ -64,7 +66,7 @@ public class EventRepository : Repository, IEventRepository DateTime startDate, DateTime endDate, PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByProviderIdActingUserId]", - new Dictionary + new Dictionary { ["@ProviderId"] = providerId, ["@ActingUserId"] = actingUserId @@ -75,7 +77,7 @@ public class EventRepository : Repository, IEventRepository PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByCipherId]", - new Dictionary + new Dictionary { ["@OrganizationId"] = cipher.OrganizationId, ["@UserId"] = cipher.UserId, @@ -93,9 +95,9 @@ public class EventRepository : Repository, IEventRepository await base.CreateAsync(ev); } - public async Task CreateManyAsync(IEnumerable entities) + public async Task CreateManyAsync(IEnumerable? entities) { - if (!entities?.Any() ?? true) + if (entities is null || !entities.Any()) { return; } @@ -112,7 +114,7 @@ public class EventRepository : Repository, IEventRepository using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, null)) { bulkCopy.DestinationTableName = "[dbo].[Event]"; - var dataTable = BuildEventsTable(bulkCopy, entities.Select(e => e is Event ? e as Event : new Event(e))); + var dataTable = BuildEventsTable(bulkCopy, entities.Select(e => e is Event @event ? @event : new Event(e))); await bulkCopy.WriteToServerAsync(dataTable); } } @@ -123,7 +125,7 @@ public class EventRepository : Repository, IEventRepository PageOptions pageOptions) { return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationIdServiceAccountId]", - new Dictionary + new Dictionary { ["@OrganizationId"] = organizationId, ["@ServiceAccountId"] = serviceAccountId @@ -131,7 +133,7 @@ public class EventRepository : Repository, IEventRepository } private async Task> GetManyAsync(string sprocName, - IDictionary sprocParams, DateTime startDate, DateTime endDate, PageOptions pageOptions) + IDictionary sprocParams, DateTime startDate, DateTime endDate, PageOptions pageOptions) { DateTime? beforeDate = null; if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && diff --git a/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs b/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs index 0bb38761c..ae1093269 100644 --- a/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/InstallationRepository.cs @@ -2,6 +2,8 @@ using Bit.Core.Repositories; using Bit.Core.Settings; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class InstallationRepository : Repository, IInstallationRepository diff --git a/src/Infrastructure.Dapper/Repositories/MaintenanceRepository.cs b/src/Infrastructure.Dapper/Repositories/MaintenanceRepository.cs index e7985f12f..266fe1089 100644 --- a/src/Infrastructure.Dapper/Repositories/MaintenanceRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/MaintenanceRepository.cs @@ -4,6 +4,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class MaintenanceRepository : BaseRepository, IMaintenanceRepository diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationApiKeyRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationApiKeyRepository.cs index a1f599f34..d0600450e 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationApiKeyRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationApiKeyRepository.cs @@ -6,6 +6,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationApiKeyRepository : Repository, IOrganizationApiKeyRepository diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs index 0168f57e3..69a707b2a 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs @@ -6,6 +6,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationConnectionRepository : Repository, IOrganizationConnectionRepository @@ -14,7 +16,7 @@ public class OrganizationConnectionRepository : Repository GetByIdOrganizationIdAsync(Guid id, Guid organizationId) + public async Task GetByIdOrganizationIdAsync(Guid id, Guid organizationId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs index 6202a121d..31d599f0c 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs @@ -6,6 +6,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationDomainRepository : Repository, IOrganizationDomainRepository @@ -55,7 +57,7 @@ public class OrganizationDomainRepository : Repository return results.ToList(); } - public async Task GetOrganizationDomainSsoDetailsAsync(string email) + public async Task GetOrganizationDomainSsoDetailsAsync(string email) { using (var connection = new SqlConnection(ConnectionString)) { @@ -69,7 +71,7 @@ public class OrganizationDomainRepository : Repository } } - public async Task GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) + public async Task GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) { using (var connection = new SqlConnection(ConnectionString)) { @@ -83,7 +85,7 @@ public class OrganizationDomainRepository : Repository } } - public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationSponsorshipRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationSponsorshipRepository.cs index c8fa3e0d6..cebf4b55c 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationSponsorshipRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationSponsorshipRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class OrganizationSponsorshipRepository : Repository, IOrganizationSponsorshipRepository @@ -17,7 +19,7 @@ public class OrganizationSponsorshipRepository : Repository> CreateManyAsync(IEnumerable organizationSponsorships) + public async Task?> CreateManyAsync(IEnumerable organizationSponsorships) { if (!organizationSponsorships.Any()) { @@ -87,7 +89,7 @@ public class OrganizationSponsorshipRepository : Repository GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId) + public async Task GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId) { using (var connection = new SqlConnection(ConnectionString)) { @@ -103,7 +105,7 @@ public class OrganizationSponsorshipRepository : Repository GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId) + public async Task GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Repositories/Repository.cs b/src/Infrastructure.Dapper/Repositories/Repository.cs index 500260be6..fd37b611d 100644 --- a/src/Infrastructure.Dapper/Repositories/Repository.cs +++ b/src/Infrastructure.Dapper/Repositories/Repository.cs @@ -4,6 +4,8 @@ using Bit.Core.Repositories; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public abstract class Repository : BaseRepository, IRepository @@ -11,7 +13,7 @@ public abstract class Repository : BaseRepository, IRepository where T : class, ITableObject { public Repository(string connectionString, string readOnlyConnectionString, - string schema = null, string table = null) + string? schema = null, string? table = null) : base(connectionString, readOnlyConnectionString) { if (!string.IsNullOrWhiteSpace(table)) @@ -28,7 +30,7 @@ public abstract class Repository : BaseRepository, IRepository protected string Schema { get; private set; } = "dbo"; protected string Table { get; private set; } = typeof(T).Name; - public virtual async Task GetByIdAsync(TId id) + public virtual async Task GetByIdAsync(TId id) { using (var connection = new SqlConnection(ConnectionString)) { diff --git a/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs b/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs index 6aecf1a52..be6001726 100644 --- a/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/TaxRateRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class TaxRateRepository : Repository, ITaxRateRepository diff --git a/src/Infrastructure.Dapper/Repositories/TransactionRepository.cs b/src/Infrastructure.Dapper/Repositories/TransactionRepository.cs index 5ac930b69..88f10368c 100644 --- a/src/Infrastructure.Dapper/Repositories/TransactionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/TransactionRepository.cs @@ -6,6 +6,8 @@ using Bit.Core.Settings; using Dapper; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class TransactionRepository : Repository, ITransactionRepository @@ -55,7 +57,7 @@ public class TransactionRepository : Repository, ITransaction return results.ToList(); } - public async Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId) + public async Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId) { // maybe come back to this using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/UserRepository.cs b/src/Infrastructure.Dapper/Repositories/UserRepository.cs index 09d14f1b9..a96c98677 100644 --- a/src/Infrastructure.Dapper/Repositories/UserRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/UserRepository.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Text.Json; using Bit.Core; using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Entities; @@ -9,6 +10,8 @@ using Dapper; using Microsoft.AspNetCore.DataProtection; using Microsoft.Data.SqlClient; +#nullable enable + namespace Bit.Infrastructure.Dapper.Repositories; public class UserRepository : Repository, IUserRepository @@ -18,23 +21,19 @@ public class UserRepository : Repository, IUserRepository public UserRepository( GlobalSettings globalSettings, IDataProtectionProvider dataProtectionProvider) - : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + : base(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) { _dataProtector = dataProtectionProvider.CreateProtector(Constants.DatabaseFieldProtectorPurpose); } - public UserRepository(string connectionString, string readOnlyConnectionString) - : base(connectionString, readOnlyConnectionString) - { } - - public override async Task GetByIdAsync(Guid id) + public override async Task GetByIdAsync(Guid id) { var user = await base.GetByIdAsync(id); UnprotectData(user); return user; } - public async Task GetByEmailAsync(string email) + public async Task GetByEmailAsync(string email) { using (var connection = new SqlConnection(ConnectionString)) { @@ -69,7 +68,7 @@ public class UserRepository : Repository, IUserRepository } } - public async Task GetBySsoUserAsync(string externalId, Guid? organizationId) + public async Task GetBySsoUserAsync(string externalId, Guid? organizationId) { using (var connection = new SqlConnection(ConnectionString)) { @@ -83,7 +82,7 @@ public class UserRepository : Repository, IUserRepository } } - public async Task GetKdfInformationByEmailAsync(string email) + public async Task GetKdfInformationByEmailAsync(string email) { using (var connection = new SqlConnection(ConnectionString)) { @@ -125,7 +124,7 @@ public class UserRepository : Repository, IUserRepository } } - public async Task GetPublicKeyAsync(Guid id) + public async Task GetPublicKeyAsync(Guid id) { using (var connection = new SqlConnection(ConnectionString)) { @@ -257,6 +256,20 @@ public class UserRepository : Repository, IUserRepository } } + public async Task> GetManyWithCalculatedPremiumAsync(IEnumerable ids) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadByIdsWithCalculatedPremium]", + new { Ids = JsonSerializer.Serialize(ids) }, + commandType: CommandType.StoredProcedure); + + UnprotectData(results); + return results.ToList(); + } + } + private async Task ProtectDataAndSaveAsync(User user, Func saveTask) { if (user == null) @@ -273,13 +286,13 @@ public class UserRepository : Repository, IUserRepository if (!user.MasterPassword?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false) { user.MasterPassword = string.Concat(Constants.DatabaseFieldProtectedPrefix, - _dataProtector.Protect(user.MasterPassword)); + _dataProtector.Protect(user.MasterPassword!)); } if (!user.Key?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false) { user.Key = string.Concat(Constants.DatabaseFieldProtectedPrefix, - _dataProtector.Protect(user.Key)); + _dataProtector.Protect(user.Key!)); } // Save @@ -290,7 +303,7 @@ public class UserRepository : Repository, IUserRepository user.Key = originalKey; } - private void UnprotectData(User user) + private void UnprotectData(User? user) { if (user == null) { diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index ca496b4a1..697edb3f3 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -77,14 +77,12 @@ public class CipherRepository : Repository, ICipherRepository } } - public async Task> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true) + public async Task> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true) { string sprocName = null; if (withOrganizations) { - sprocName = useFlexibleCollections - ? $"[{Schema}].[CipherDetails_ReadByUserId_V2]" - : $"[{Schema}].[CipherDetails_ReadByUserId]"; + sprocName = $"[{Schema}].[CipherDetails_ReadByUserId]"; } else { @@ -752,6 +750,8 @@ public class CipherRepository : Repository, ICipherRepository collectionsTable.Columns.Add(creationDateColumn); var revisionDateColumn = new DataColumn(nameof(c.RevisionDate), c.RevisionDate.GetType()); collectionsTable.Columns.Add(revisionDateColumn); + var externalIdColumn = new DataColumn(nameof(c.ExternalId), typeof(string)); + collectionsTable.Columns.Add(externalIdColumn); foreach (DataColumn col in collectionsTable.Columns) { @@ -771,6 +771,7 @@ public class CipherRepository : Repository, ICipherRepository row[nameColumn] = collection.Name; row[creationDateColumn] = collection.CreationDate; row[revisionDateColumn] = collection.RevisionDate; + row[externalIdColumn] = collection.ExternalId; collectionsTable.Rows.Add(row); } diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs index ee46643fe..601ca1275 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationRepository.cs @@ -100,8 +100,7 @@ public class OrganizationRepository : Repository new CollectionAccessSelection { @@ -257,7 +256,7 @@ public class OrganizationUserRepository : Repository new CollectionAccessSelection { diff --git a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs index 965e1d879..1584b26f0 100644 --- a/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -68,7 +68,6 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery, IAuthRequestRepository @@ -25,7 +27,7 @@ public class AuthRequestRepository : Repository (a.Type != AuthRequestType.AdminApproval && a.CreationDate.AddSeconds(userRequestExpiration.TotalSeconds) < DateTime.UtcNow) || (a.Type == AuthRequestType.AdminApproval && a.Approved != true && a.CreationDate.AddSeconds(adminRequestExpiration.TotalSeconds) < DateTime.UtcNow) - || (a.Type == AuthRequestType.AdminApproval && a.Approved == true && a.ResponseDate.Value.AddSeconds(afterAdminApprovalExpiration.TotalSeconds) < DateTime.UtcNow)) + || (a.Type == AuthRequestType.AdminApproval && a.Approved == true && a.ResponseDate!.Value.AddSeconds(afterAdminApprovalExpiration.TotalSeconds) < DateTime.UtcNow)) .ToListAsync(); dbContext.AuthRequests.RemoveRange(expiredRequests); return await dbContext.SaveChangesAsync(); diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs index e6a32542f..22ca89fa0 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/EmergencyAccessRepository.cs @@ -10,6 +10,8 @@ using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Auth.Repositories; public class EmergencyAccessRepository : Repository, IEmergencyAccessRepository @@ -35,7 +37,7 @@ public class EmergencyAccessRepository : Repository GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId) + public async Task GetDetailsByIdGrantorIdAsync(Guid id, Guid grantorId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/GrantRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/GrantRepository.cs index 09fd46835..dd958e0c6 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/GrantRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/GrantRepository.cs @@ -6,6 +6,8 @@ using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Auth.Repositories; public class GrantRepository : BaseEntityFrameworkRepository, IGrantRepository @@ -36,7 +38,7 @@ public class GrantRepository : BaseEntityFrameworkRepository, IGrantRepository } } - public async Task GetByKeyAsync(string key) + public async Task GetByKeyAsync(string key) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -92,4 +94,3 @@ public class GrantRepository : BaseEntityFrameworkRepository, IGrantRepository } } } - diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessDetailsViewQuery.cs index 7ddbcc346..d666df76c 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessDetailsViewQuery.cs @@ -2,6 +2,8 @@ using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; public class EmergencyAccessDetailsViewQuery : IQuery diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs index 0cdf19f2d..a0926db57 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/EmergencyAccessReadCountByGrantorIdEmailQuery.cs @@ -2,6 +2,8 @@ using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; public class EmergencyAccessReadCountByGrantorIdEmailQuery : IQuery diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/SsoConfigRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/SsoConfigRepository.cs index 3113f31de..c34a024ae 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/SsoConfigRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/SsoConfigRepository.cs @@ -4,6 +4,8 @@ using Bit.Infrastructure.EntityFramework.Auth.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class SsoConfigRepository : Repository, ISsoConfigRepository @@ -12,7 +14,7 @@ public class SsoConfigRepository : Repository context.SsoConfigs) { } - public async Task GetByOrganizationIdAsync(Guid organizationId) + public async Task GetByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -22,7 +24,7 @@ public class SsoConfigRepository : Repository GetByIdentifierAsync(string identifier) + public async Task GetByIdentifierAsync(string identifier) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/SsoUserRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/SsoUserRepository.cs index 278c5e870..7d0152912 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/SsoUserRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/SsoUserRepository.cs @@ -4,6 +4,8 @@ using Bit.Infrastructure.EntityFramework.Auth.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class SsoUserRepository : Repository, ISsoUserRepository @@ -17,13 +19,13 @@ public class SsoUserRepository : Repository su.UserId == userId && su.OrganizationId == organizationId); - dbContext.Entry(entity).State = EntityState.Deleted; - await dbContext.SaveChangesAsync(); + await dbContext.SsoUsers + .Where(su => su.UserId == userId && su.OrganizationId == organizationId) + .ExecuteDeleteAsync(); } } - public async Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId) + public async Task GetByUserIdOrganizationIdAsync(Guid organizationId, Guid userId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/WebAuthnCredentialRepository.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/WebAuthnCredentialRepository.cs index 149981188..b670a3f1d 100644 --- a/src/Infrastructure.EntityFramework/Auth/Repositories/WebAuthnCredentialRepository.cs +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/WebAuthnCredentialRepository.cs @@ -7,6 +7,8 @@ using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Auth.Repositories; public class WebAuthnCredentialRepository : Repository, IWebAuthnCredentialRepository @@ -15,7 +17,7 @@ public class WebAuthnCredentialRepository : Repository context.WebAuthnCredentials) { } - public async Task GetByIdAsync(Guid id, Guid userId) + public async Task GetByIdAsync(Guid id, Guid userId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/EfExtensions.cs b/src/Infrastructure.EntityFramework/EfExtensions.cs index 50cd700ed..b9088ea7e 100644 --- a/src/Infrastructure.EntityFramework/EfExtensions.cs +++ b/src/Infrastructure.EntityFramework/EfExtensions.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; +#nullable enable + namespace Bit.Infrastructure.EntityFramework; public static class EfExtensions diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkCache.cs b/src/Infrastructure.EntityFramework/EntityFrameworkCache.cs index 1bffa1c77..197723feb 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkCache.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkCache.cs @@ -4,13 +4,15 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework; public class EntityFrameworkCache : IDistributedCache { #if DEBUG // Used for debugging in tests - public Task scanTask; + public Task? scanTask; #endif private static readonly TimeSpan _defaultSlidingExpiration = TimeSpan.FromMinutes(20); private static readonly TimeSpan _expiredItemsDeletionInterval = TimeSpan.FromMinutes(30); @@ -22,14 +24,14 @@ public class EntityFrameworkCache : IDistributedCache public EntityFrameworkCache( IServiceScopeFactory serviceScopeFactory, - TimeProvider timeProvider = null) + TimeProvider? timeProvider = null) { _deleteExpiredCachedItemsDelegate = DeleteExpiredCacheItems; _serviceScopeFactory = serviceScopeFactory; _timeProvider = timeProvider ?? TimeProvider.System; } - public byte[] Get(string key) + public byte[]? Get(string key) { ArgumentNullException.ThrowIfNull(key); @@ -53,7 +55,7 @@ public class EntityFrameworkCache : IDistributedCache return cache?.Value; } - public async Task GetAsync(string key, CancellationToken token = default) + public async Task GetAsync(string key, CancellationToken token = default) { ArgumentNullException.ThrowIfNull(key); token.ThrowIfCancellationRequested(); @@ -181,7 +183,7 @@ public class EntityFrameworkCache : IDistributedCache ScanForExpiredItemsIfRequired(); } - private Cache SetCache(Cache cache, string key, byte[] value, DistributedCacheEntryOptions options) + private Cache SetCache(Cache? cache, string key, byte[] value, DistributedCacheEntryOptions options) { var utcNow = _timeProvider.GetUtcNow().DateTime; diff --git a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj index 86adc2702..792629090 100644 --- a/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj +++ b/src/Infrastructure.EntityFramework/Infrastructure.EntityFramework.csproj @@ -3,9 +3,9 @@ - - - + + + diff --git a/src/Infrastructure.EntityFramework/Models/Cache.cs b/src/Infrastructure.EntityFramework/Models/Cache.cs index f03c09d8d..2a630e5b8 100644 --- a/src/Infrastructure.EntityFramework/Models/Cache.cs +++ b/src/Infrastructure.EntityFramework/Models/Cache.cs @@ -1,12 +1,14 @@ using System.ComponentModel.DataAnnotations; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Models; public class Cache { [StringLength(449)] - public string Id { get; set; } - public byte[] Value { get; set; } + public required string Id { get; set; } + public byte[] Value { get; set; } = null!; public DateTime ExpiresAtTime { get; set; } public long? SlidingExpirationInSeconds { get; set; } public DateTime? AbsoluteExpiration { get; set; } diff --git a/src/Infrastructure.EntityFramework/Repositories/BaseEntityFrameworkRepository.cs b/src/Infrastructure.EntityFramework/Repositories/BaseEntityFrameworkRepository.cs index 6cf7cbb46..0039e881f 100644 --- a/src/Infrastructure.EntityFramework/Repositories/BaseEntityFrameworkRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/BaseEntityFrameworkRepository.cs @@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using User = Bit.Core.Entities.User; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public abstract class BaseEntityFrameworkRepository diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs index 94ae69172..d0787f730 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs @@ -1,11 +1,12 @@ using AutoMapper; -using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using CollectionCipher = Bit.Core.Entities.CollectionCipher; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollectionCipherRepository @@ -22,7 +23,7 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec var entity = Mapper.Map(obj); dbContext.Add(entity); await dbContext.SaveChangesAsync(); - var organizationId = (await dbContext.Ciphers.FirstOrDefaultAsync(c => c.Id.Equals(obj.CipherId))).OrganizationId; + var organizationId = (await dbContext.Ciphers.FirstOrDefaultAsync(c => c.Id.Equals(obj.CipherId)))?.OrganizationId; if (organizationId.HasValue) { await dbContext.UserBumpAccountRevisionDateByCollectionIdAsync(obj.CollectionId, organizationId.Value); @@ -81,36 +82,10 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec .Select(c => c.OrganizationId) .FirstAsync(); - List availableCollections; - - // TODO AC-1375: use the query below to remove AccessAll from this method - // var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId); - // availableCollections = await availableCollectionsQuery - // .Run(dbContext) - // .Select(c => c.Id).ToListAsync(); - - availableCollections = await (from c in dbContext.Collections - join o in dbContext.Organizations on c.OrganizationId equals o.Id - join ou in dbContext.OrganizationUsers - on new { OrganizationId = o.Id, UserId = (Guid?)userId } equals - new { ou.OrganizationId, ou.UserId } - join cu in dbContext.CollectionUsers - on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals - new { AccessAll = false, cu.CollectionId, cu.OrganizationUserId } into cu_g - from cu in cu_g.DefaultIfEmpty() - join gu in dbContext.GroupUsers - on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g - from gu in gu_g.DefaultIfEmpty() - join g in dbContext.Groups on gu.GroupId equals g.Id into g_g - from g in g_g.DefaultIfEmpty() - join cg in dbContext.CollectionGroups - on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals - new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g - from cg in cg_g.DefaultIfEmpty() - where o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed - && (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly) - select c.Id).ToListAsync(); + var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId); + var availableCollections = await availableCollectionsQuery + .Run(dbContext) + .Select(c => c.Id).ToListAsync(); var collectionCiphers = await (from cc in dbContext.CollectionCiphers where cc.CipherId == cipherId @@ -190,37 +165,9 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec { var dbContext = GetDatabaseContext(scope); - IQueryable availableCollections; - - // TODO AC-1375: use the query below to remove AccessAll from this method - // var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId); - // availableCollections = availableCollectionsQuery - // .Run(dbContext); - - availableCollections = from c in dbContext.Collections - join o in dbContext.Organizations - on c.OrganizationId equals o.Id - join ou in dbContext.OrganizationUsers - on o.Id equals ou.OrganizationId - where ou.UserId == userId - join cu in dbContext.CollectionUsers - on ou.Id equals cu.OrganizationUserId into cu_g - from cu in cu_g.DefaultIfEmpty() - where !ou.AccessAll && cu.CollectionId == c.Id - join gu in dbContext.GroupUsers - on ou.Id equals gu.OrganizationUserId into gu_g - from gu in gu_g.DefaultIfEmpty() - where cu.CollectionId == null && !ou.AccessAll - join g in dbContext.Groups - on gu.GroupId equals g.Id into g_g - from g in g_g.DefaultIfEmpty() - join cg in dbContext.CollectionGroups - on gu.GroupId equals cg.GroupId into cg_g - from cg in cg_g.DefaultIfEmpty() - where !g.AccessAll && cg.CollectionId == c.Id && - (o.Id == organizationId && o.Enabled && ou.Status == OrganizationUserStatusType.Confirmed && - (ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly)) - select c; + var availableCollectionsQuery = new CollectionsReadByOrganizationIdUserIdQuery(organizationId, userId); + var availableCollections = availableCollectionsQuery + .Run(dbContext); if (await availableCollections.CountAsync() < 1) { diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs index a47bb59bc..cdc5caf36 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs @@ -6,6 +6,8 @@ using Bit.Infrastructure.EntityFramework.Repositories.Queries; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class CollectionRepository : Repository, ICollectionRepository @@ -110,7 +112,7 @@ public class CollectionRepository : Repository> GetByIdWithAccessAsync(Guid id) + public async Task> GetByIdWithAccessAsync(Guid id) { var collection = await base.GetByIdAsync(id); using (var scope = ServiceScopeFactory.CreateScope()) @@ -139,7 +141,7 @@ public class CollectionRepository : Repository(collection, access); + return new Tuple(collection, access); } } @@ -221,13 +223,13 @@ public class CollectionRepository : Repository> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections) + public async Task> GetManyByUserIdAsync(Guid userId) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - var baseCollectionQuery = new UserCollectionDetailsQuery(userId, useFlexibleCollections).Run(dbContext); + var baseCollectionQuery = new UserCollectionDetailsQuery(userId).Run(dbContext); if (dbContext.Database.IsSqlite()) { @@ -392,7 +394,7 @@ public class CollectionRepository : Repository GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, + public async Task GetByIdWithPermissionsAsync(Guid collectionId, Guid? userId, bool includeAccessRelationships) { using (var scope = ServiceScopeFactory.CreateScope()) @@ -400,7 +402,7 @@ public class CollectionRepository : Repository, IDeviceRepository @@ -24,7 +26,7 @@ public class DeviceRepository : Repository, } } - public async Task GetByIdAsync(Guid id, Guid userId) + public async Task GetByIdAsync(Guid id, Guid userId) { var device = await base.GetByIdAsync(id); if (device == null || device.UserId != userId) @@ -35,7 +37,7 @@ public class DeviceRepository : Repository, return Mapper.Map(device); } - public async Task GetByIdentifierAsync(string identifier) + public async Task GetByIdentifierAsync(string identifier) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -46,7 +48,7 @@ public class DeviceRepository : Repository, } } - public async Task GetByIdentifierAsync(string identifier, Guid userId) + public async Task GetByIdentifierAsync(string identifier, Guid userId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs b/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs index 3e3ebb21a..55aad0a3c 100644 --- a/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs @@ -8,6 +8,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Cipher = Bit.Core.Vault.Entities.Cipher; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class EventRepository : Repository, IEventRepository @@ -28,7 +30,7 @@ public class EventRepository : Repository, IEv public async Task CreateManyAsync(IEnumerable entities) { - if (!entities?.Any() ?? true) + if (entities is null || !entities.Any()) { return; } diff --git a/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs index 292e98f85..64777a384 100644 --- a/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/InstallationRepository.cs @@ -3,6 +3,8 @@ using Bit.Core.Repositories; using Bit.Infrastructure.EntityFramework.Models; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class InstallationRepository : Repository, IInstallationRepository diff --git a/src/Infrastructure.EntityFramework/Repositories/MaintenanceRepository.cs b/src/Infrastructure.EntityFramework/Repositories/MaintenanceRepository.cs index 340834ca5..bff454b5e 100644 --- a/src/Infrastructure.EntityFramework/Repositories/MaintenanceRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/MaintenanceRepository.cs @@ -2,6 +2,8 @@ using Bit.Core.Repositories; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class MaintenanceRepository : BaseEntityFrameworkRepository, IMaintenanceRepository diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationApiKeyRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationApiKeyRepository.cs index 52cf3d5e6..22a915579 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationApiKeyRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationApiKeyRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class OrganizationApiKeyRepository : Repository, IOrganizationApiKeyRepository diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs index ad8359758..ab03e4499 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs @@ -5,6 +5,8 @@ using Bit.Core.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class OrganizationConnectionRepository : Repository, IOrganizationConnectionRepository @@ -15,7 +17,7 @@ public class OrganizationConnectionRepository : Repository GetByIdOrganizationIdAsync(Guid id, Guid organizationId) + public async Task GetByIdOrganizationIdAsync(Guid id, Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index 11ff8e047..9135c8bd1 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -6,6 +6,8 @@ using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class OrganizationDomainRepository : Repository, IOrganizationDomainRepository @@ -67,7 +69,7 @@ public class OrganizationDomainRepository : Repository>(results); } - public async Task GetOrganizationDomainSsoDetailsAsync(string email) + public async Task GetOrganizationDomainSsoDetailsAsync(string email) { var domainName = new MailAddress(email).Host; @@ -93,7 +95,7 @@ public class OrganizationDomainRepository : Repository GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) + public async Task GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); @@ -105,7 +107,7 @@ public class OrganizationDomainRepository : Repository(domain); } - public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationSponsorshipRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationSponsorshipRepository.cs index de0af89df..0f76772c5 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationSponsorshipRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationSponsorshipRepository.cs @@ -4,6 +4,8 @@ using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class OrganizationSponsorshipRepository : Repository, IOrganizationSponsorshipRepository @@ -12,10 +14,11 @@ public class OrganizationSponsorshipRepository : Repository context.OrganizationSponsorships) { } - public async Task> CreateManyAsync(IEnumerable organizationSponsorships) + public async Task?> CreateManyAsync(IEnumerable organizationSponsorships) { if (!organizationSponsorships.Any()) { + // TODO: This differs from SQL server implementation, we should have both return empty collection return new List(); } @@ -79,7 +82,7 @@ public class OrganizationSponsorshipRepository : Repository GetByOfferedToEmailAsync(string email) + public async Task GetByOfferedToEmailAsync(string email) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -90,7 +93,7 @@ public class OrganizationSponsorshipRepository : Repository GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId) + public async Task GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -101,7 +104,7 @@ public class OrganizationSponsorshipRepository : Repository GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId) + public async Task GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/CollectionCipherReadByUserIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/CollectionCipherReadByUserIdQuery.cs index e452dad5d..72db379ea 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/CollectionCipherReadByUserIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/CollectionCipherReadByUserIdQuery.cs @@ -13,11 +13,6 @@ public class CollectionCipherReadByUserIdQuery : IQuery } public virtual IQueryable Run(DatabaseContext dbContext) - { - return Run_VCurrent(dbContext); - } - - private IQueryable Run_VNext(DatabaseContext dbContext) { var query = from cc in dbContext.CollectionCiphers @@ -52,40 +47,4 @@ public class CollectionCipherReadByUserIdQuery : IQuery select cc; return query; } - - private IQueryable Run_VCurrent(DatabaseContext dbContext) - { - var query = from cc in dbContext.CollectionCiphers - - join c in dbContext.Collections - on cc.CollectionId equals c.Id - - join ou in dbContext.OrganizationUsers - on new { c.OrganizationId, UserId = (Guid?)_userId } equals - new { ou.OrganizationId, ou.UserId } - - join cu in dbContext.CollectionUsers - on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals - new { AccessAll = false, cu.CollectionId, cu.OrganizationUserId } into cu_g - from cu in cu_g.DefaultIfEmpty() - - join gu in dbContext.GroupUsers - on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g - from gu in gu_g.DefaultIfEmpty() - - join g in dbContext.Groups - on gu.GroupId equals g.Id into g_g - from g in g_g.DefaultIfEmpty() - - join cg in dbContext.CollectionGroups - on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals - new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g - from cg in cg_g.DefaultIfEmpty() - - where ou.Status == OrganizationUserStatusType.Confirmed && - (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null) - select cc; - return query; - } } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs index 55195d09b..ce018313d 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserBumpAccountRevisionDateByCipherIdQuery.cs @@ -26,13 +26,13 @@ public class UserBumpAccountRevisionDateByCipherIdQuery : IQuery from cc in cc_g.DefaultIfEmpty() join collectionUser in dbContext.CollectionUsers - on new { ou.AccessAll, OrganizationUserId = ou.Id, cc.CollectionId } equals - new { AccessAll = false, collectionUser.OrganizationUserId, collectionUser.CollectionId } into cu_g + on new { OrganizationUserId = ou.Id, cc.CollectionId } equals + new { collectionUser.OrganizationUserId, collectionUser.CollectionId } into cu_g from cu in cu_g.DefaultIfEmpty() join groupUser in dbContext.GroupUsers - on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { CollectionId = (Guid?)null, AccessAll = false, groupUser.OrganizationUserId } into gu_g + on new { CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals + new { CollectionId = (Guid?)null, groupUser.OrganizationUserId } into gu_g from gu in gu_g.DefaultIfEmpty() join grp in dbContext.Groups @@ -40,16 +40,14 @@ public class UserBumpAccountRevisionDateByCipherIdQuery : IQuery from g in g_g.DefaultIfEmpty() join collectionGroup in dbContext.CollectionGroups - on new { g.AccessAll, gu.GroupId, cc.CollectionId } equals - new { AccessAll = false, collectionGroup.GroupId, collectionGroup.CollectionId } into cg_g + on new { gu.GroupId, cc.CollectionId } equals + new { collectionGroup.GroupId, collectionGroup.CollectionId } into cg_g from cg in cg_g.DefaultIfEmpty() where ou.OrganizationId == _organizationId && ou.Status == OrganizationUserStatusType.Confirmed && (cu.CollectionId != null || - cg.CollectionId != null || - ou.AccessAll || - g.AccessAll) + cg.CollectionId != null) select u; return query; } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs index a5f06fb61..ec381ff82 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCipherDetailsQuery.cs @@ -8,87 +8,12 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; public class UserCipherDetailsQuery : IQuery { private readonly Guid? _userId; - private readonly bool _useFlexibleCollections; - public UserCipherDetailsQuery(Guid? userId, bool useFlexibleCollections) + public UserCipherDetailsQuery(Guid? userId) { _userId = userId; - _useFlexibleCollections = useFlexibleCollections; } public virtual IQueryable Run(DatabaseContext dbContext) - { - return _useFlexibleCollections - ? Run_VNext(dbContext) - : Run_VCurrent(dbContext); - } - - private IQueryable Run_VCurrent(DatabaseContext dbContext) - { - var query = from c in dbContext.Ciphers - - join ou in dbContext.OrganizationUsers - on new { CipherUserId = c.UserId, c.OrganizationId, UserId = _userId, Status = OrganizationUserStatusType.Confirmed } equals - new { CipherUserId = (Guid?)null, OrganizationId = (Guid?)ou.OrganizationId, ou.UserId, ou.Status } - - join o in dbContext.Organizations - on new { c.OrganizationId, OuOrganizationId = ou.OrganizationId, Enabled = true } equals - new { OrganizationId = (Guid?)o.Id, OuOrganizationId = o.Id, o.Enabled } - - join cc in dbContext.CollectionCiphers - on new { ou.AccessAll, CipherId = c.Id } equals - new { AccessAll = false, cc.CipherId } into cc_g - from cc in cc_g.DefaultIfEmpty() - - join cu in dbContext.CollectionUsers - on new { cc.CollectionId, OrganizationUserId = ou.Id } equals - new { cu.CollectionId, cu.OrganizationUserId } into cu_g - from cu in cu_g.DefaultIfEmpty() - - join gu in dbContext.GroupUsers - on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g - from gu in gu_g.DefaultIfEmpty() - - join g in dbContext.Groups - on gu.GroupId equals g.Id into g_g - from g in g_g.DefaultIfEmpty() - - join cg in dbContext.CollectionGroups - on new { g.AccessAll, cc.CollectionId, gu.GroupId } equals - new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g - from cg in cg_g.DefaultIfEmpty() - - where ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null - - select c; - - var query2 = from c in dbContext.Ciphers - where c.UserId == _userId - select c; - - var union = query.Union(query2).Select(c => new CipherDetails - { - Id = c.Id, - UserId = c.UserId, - OrganizationId = c.OrganizationId, - Type = c.Type, - Data = c.Data, - Attachments = c.Attachments, - CreationDate = c.CreationDate, - RevisionDate = c.RevisionDate, - DeletedDate = c.DeletedDate, - Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"), - FolderId = GetFolderId(_userId, c), - Edit = true, - Reprompt = c.Reprompt, - ViewPassword = true, - OrganizationUseTotp = false, - Key = c.Key - }); - return union; - } - - private IQueryable Run_VNext(DatabaseContext dbContext) { var query = from c in dbContext.Ciphers diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCollectionDetailsQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCollectionDetailsQuery.cs index bf9154eda..74e15f99b 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/UserCollectionDetailsQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/UserCollectionDetailsQuery.cs @@ -6,22 +6,13 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; public class UserCollectionDetailsQuery : IQuery { private readonly Guid? _userId; - private readonly bool _useFlexibleCollections; - public UserCollectionDetailsQuery(Guid? userId, bool useFlexibleCollections) + public UserCollectionDetailsQuery(Guid? userId) { _userId = userId; - _useFlexibleCollections = useFlexibleCollections; } public virtual IQueryable Run(DatabaseContext dbContext) - { - return _useFlexibleCollections - ? Run_vNext(dbContext) - : Run_vLegacy(dbContext); - } - - private IQueryable Run_vNext(DatabaseContext dbContext) { var query = from c in dbContext.Collections @@ -69,56 +60,4 @@ public class UserCollectionDetailsQuery : IQuery Manage = (bool?)x.cu.Manage ?? (bool?)x.cg.Manage ?? false, }); } - - private IQueryable Run_vLegacy(DatabaseContext dbContext) - { - var query = from c in dbContext.Collections - - join ou in dbContext.OrganizationUsers - on c.OrganizationId equals ou.OrganizationId - - join o in dbContext.Organizations - on c.OrganizationId equals o.Id - - join cu in dbContext.CollectionUsers - on new { ou.AccessAll, CollectionId = c.Id, OrganizationUserId = ou.Id } equals - new { AccessAll = false, cu.CollectionId, cu.OrganizationUserId } into cu_g - from cu in cu_g.DefaultIfEmpty() - - join gu in dbContext.GroupUsers - on new { CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g - from gu in gu_g.DefaultIfEmpty() - - join g in dbContext.Groups - on gu.GroupId equals g.Id into g_g - from g in g_g.DefaultIfEmpty() - - join cg in dbContext.CollectionGroups - on new { g.AccessAll, CollectionId = c.Id, gu.GroupId } equals - new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g - from cg in cg_g.DefaultIfEmpty() - - where ou.UserId == _userId && - ou.Status == OrganizationUserStatusType.Confirmed && - o.Enabled && - (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null) - select new { c, ou, o, cu, gu, g, cg }; - - return query.Select(x => new CollectionDetails - { - Id = x.c.Id, - OrganizationId = x.c.OrganizationId, - Name = x.c.Name, - ExternalId = x.c.ExternalId, - CreationDate = x.c.CreationDate, - RevisionDate = x.c.RevisionDate, - ReadOnly = x.ou.AccessAll || x.g.AccessAll || - !((bool?)x.cu.ReadOnly ?? (bool?)x.cg.ReadOnly ?? false) ? false : true, - HidePasswords = x.ou.AccessAll || x.g.AccessAll || - !((bool?)x.cu.HidePasswords ?? (bool?)x.cg.HidePasswords ?? false) ? false : true, - Manage = x.ou.AccessAll || x.g.AccessAll || - !((bool?)x.cu.Manage ?? (bool?)x.cg.Manage ?? false) ? false : true, - }); - } } diff --git a/src/Infrastructure.EntityFramework/Repositories/Repository.cs b/src/Infrastructure.EntityFramework/Repositories/Repository.cs index 4c509540d..7b5d6c7df 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Repository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Repository.cs @@ -5,6 +5,8 @@ using Bit.Infrastructure.EntityFramework.Repositories.Queries; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public abstract class Repository : BaseEntityFrameworkRepository, IRepository @@ -20,7 +22,7 @@ public abstract class Repository : BaseEntityFrameworkRepositor protected Func> GetDbSet { get; private set; } - public virtual async Task GetByIdAsync(TId id) + public virtual async Task GetByIdAsync(TId id) { using (var scope = ServiceScopeFactory.CreateScope()) { diff --git a/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs b/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs index fcf4014a1..38fcaaa1a 100644 --- a/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/TaxRateRepository.cs @@ -4,6 +4,8 @@ using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class TaxRateRepository : Repository, ITaxRateRepository @@ -17,9 +19,9 @@ public class TaxRateRepository : Repository(model); - entity.Active = false; - await dbContext.SaveChangesAsync(); + await dbContext.TaxRates + .Where(tr => tr.Id == model.Id) + .ExecuteUpdateAsync(property => property.SetProperty(tr => tr.Active, false)); } } diff --git a/src/Infrastructure.EntityFramework/Repositories/TransactionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/TransactionRepository.cs index f586c68bd..2150bb8fe 100644 --- a/src/Infrastructure.EntityFramework/Repositories/TransactionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/TransactionRepository.cs @@ -6,6 +6,8 @@ using LinqToDB; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class TransactionRepository : Repository, ITransactionRepository @@ -14,7 +16,7 @@ public class TransactionRepository : Repository context.Transactions) { } - public async Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId) + public async Task GetByGatewayIdAsync(GatewayType gatewayType, string gatewayId) { using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); diff --git a/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs b/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs index 4458e6044..6211d6063 100644 --- a/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/UserRepository.cs @@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using DataModel = Bit.Core.Models.Data; +#nullable enable + namespace Bit.Infrastructure.EntityFramework.Repositories; public class UserRepository : Repository, IUserRepository @@ -14,7 +16,7 @@ public class UserRepository : Repository, IUserR : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Users) { } - public async Task GetByEmailAsync(string email) + public async Task GetByEmailAsync(string email) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -36,7 +38,7 @@ public class UserRepository : Repository, IUserR } } - public async Task GetKdfInformationByEmailAsync(string email) + public async Task GetKdfInformationByEmailAsync(string email) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -89,7 +91,7 @@ public class UserRepository : Repository, IUserR } } - public async Task GetPublicKeyAsync(Guid id) + public async Task GetPublicKeyAsync(Guid id) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -130,7 +132,7 @@ public class UserRepository : Repository, IUserR } } - public async Task GetBySsoUserAsync(string externalId, Guid? organizationId) + public async Task GetBySsoUserAsync(string externalId, Guid? organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -202,6 +204,24 @@ public class UserRepository : Repository, IUserR } } + public async Task> GetManyWithCalculatedPremiumAsync(IEnumerable ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var users = dbContext.Users.Where(x => ids.Contains(x.Id)); + return await users.Select(e => new DataModel.UserWithCalculatedPremium(e) + { + HasPremiumAccess = e.Premium || dbContext.OrganizationUsers + .Any(ou => ou.UserId == e.Id && + dbContext.Organizations + .Any(o => o.Id == ou.OrganizationId && + o.UsersGetPremium == true && + o.Enabled == true)) + }).ToListAsync(); + } + } + public override async Task DeleteAsync(Core.Entities.User user) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Models/ServiceAccount.cs b/src/Infrastructure.EntityFramework/SecretsManager/Models/ServiceAccount.cs index 258716079..812740e7a 100644 --- a/src/Infrastructure.EntityFramework/SecretsManager/Models/ServiceAccount.cs +++ b/src/Infrastructure.EntityFramework/SecretsManager/Models/ServiceAccount.cs @@ -8,6 +8,8 @@ public class ServiceAccount : Core.SecretsManager.Entities.ServiceAccount public virtual Organization Organization { get; set; } public virtual ICollection GroupAccessPolicies { get; set; } public virtual ICollection UserAccessPolicies { get; set; } + public virtual ICollection ProjectAccessPolicies { get; set; } + public virtual ICollection ApiKeys { get; set; } } public class ServiceAccountMapperProfile : Profile diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index dd03d4b7c..23bd2b550 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -308,7 +308,7 @@ public class CipherRepository : Repository c.Id == id); return data; } @@ -359,13 +359,13 @@ public class CipherRepository : Repository> GetManyByUserIdAsync(Guid userId, bool useFlexibleCollections, bool withOrganizations = true) + public async Task> GetManyByUserIdAsync(Guid userId, bool withOrganizations = true) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); IQueryable cipherDetailsView = withOrganizations ? - new UserCipherDetailsQuery(userId, useFlexibleCollections).Run(dbContext) : + new UserCipherDetailsQuery(userId).Run(dbContext) : new CipherDetailsQuery(userId).Run(dbContext); if (!withOrganizations) { @@ -413,7 +413,7 @@ public class CipherRepository : Repository ids.Contains(c.Id)); - var userCipherDetails = new UserCipherDetailsQuery(userId, false).Run(dbContext); + var userCipherDetails = new UserCipherDetailsQuery(userId).Run(dbContext); var idsToMove = from ucd in userCipherDetails join c in cipherEntities on ucd.Id equals c.Id @@ -694,7 +694,7 @@ public class CipherRepository : Repository ids.Contains(c.Id))).ToListAsync(); var query = from ucd in await (userCipherDetailsQuery.Run(dbContext)).ToListAsync() join c in cipherEntitiesToCheck diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherReadCanEditByIdUserIdQuery.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherReadCanEditByIdUserIdQuery.cs index 3f0a17180..76133ed31 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherReadCanEditByIdUserIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/Queries/CipherReadCanEditByIdUserIdQuery.cs @@ -31,8 +31,8 @@ public class CipherReadCanEditByIdUserIdQuery : IQuery from ou in ou_g.DefaultIfEmpty() join cc in dbContext.CollectionCiphers - on new { c.UserId, ou.AccessAll, CipherId = c.Id } equals - new { UserId = (Guid?)null, AccessAll = false, cc.CipherId } into cc_g + on new { c.UserId, CipherId = c.Id } equals + new { UserId = (Guid?)null, cc.CipherId } into cc_g from cc in cc_g.DefaultIfEmpty() join cu in dbContext.CollectionUsers @@ -41,8 +41,8 @@ public class CipherReadCanEditByIdUserIdQuery : IQuery from cu in cu_g.DefaultIfEmpty() join gu in dbContext.GroupUsers - on new { c.UserId, CollectionId = (Guid?)cu.CollectionId, ou.AccessAll, OrganizationUserId = ou.Id } equals - new { UserId = (Guid?)null, CollectionId = (Guid?)null, AccessAll = false, gu.OrganizationUserId } into gu_g + on new { c.UserId, CollectionId = (Guid?)cu.CollectionId, OrganizationUserId = ou.Id } equals + new { UserId = (Guid?)null, CollectionId = (Guid?)null, gu.OrganizationUserId } into gu_g from gu in gu_g.DefaultIfEmpty() join g in dbContext.Groups @@ -50,8 +50,8 @@ public class CipherReadCanEditByIdUserIdQuery : IQuery from g in g_g.DefaultIfEmpty() join cg in dbContext.CollectionGroups - on new { g.AccessAll, cc.CollectionId, gu.GroupId } equals - new { AccessAll = false, cg.CollectionId, cg.GroupId } into cg_g + on new { cc.CollectionId, gu.GroupId } equals + new { cg.CollectionId, cg.GroupId } into cg_g from cg in cg_g.DefaultIfEmpty() where @@ -60,10 +60,10 @@ public class CipherReadCanEditByIdUserIdQuery : IQuery c.UserId == _userId || ( !c.UserId.HasValue && ou.Status == OrganizationUserStatusType.Confirmed && o.Enabled && - (ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null) + (cu.CollectionId != null || cg.CollectionId != null) ) ) && - (c.UserId.HasValue || ou.AccessAll || !cu.ReadOnly || g.AccessAll || !cg.ReadOnly) + (c.UserId.HasValue || !cu.ReadOnly || !cg.ReadOnly) select c; return query; } diff --git a/src/Notifications/Notifications.csproj b/src/Notifications/Notifications.csproj index 828784096..c914ad461 100644 --- a/src/Notifications/Notifications.csproj +++ b/src/Notifications/Notifications.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 3f5b464b5..c2d670bd6 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -16,6 +16,7 @@ using Bit.Core.Auth.Repositories; using Bit.Core.Auth.Services; using Bit.Core.Auth.Services.Implementations; using Bit.Core.Auth.UserFeatures; +using Bit.Core.Billing.TrialInitiation; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.HostedServices; @@ -99,6 +100,7 @@ public static class ServiceCollectionExtensions { services.AddScoped(); services.AddUserServices(globalSettings); + services.AddTrialInitiationServices(); services.AddOrganizationServices(globalSettings); services.AddScoped(); services.AddScoped(); @@ -185,14 +187,18 @@ public static class ServiceCollectionExtensions serviceProvider.GetDataProtectionProvider(), serviceProvider.GetRequiredService>>()) ); - services.AddSingleton>( serviceProvider => new DataProtectorTokenFactory( RegistrationEmailVerificationTokenable.ClearTextPrefix, RegistrationEmailVerificationTokenable.DataProtectorPurpose, serviceProvider.GetDataProtectionProvider(), serviceProvider.GetRequiredService>>())); - + services.AddSingleton>( + serviceProvider => new DataProtectorTokenFactory( + TwoFactorAuthenticatorUserVerificationTokenable.ClearTextPrefix, + TwoFactorAuthenticatorUserVerificationTokenable.DataProtectorPurpose, + serviceProvider.GetDataProtectionProvider(), + serviceProvider.GetRequiredService>>())); } public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings) @@ -400,7 +406,7 @@ public static class ServiceCollectionExtensions .AddTokenProvider>(TokenOptions.DefaultProvider) .AddTokenProvider( CoreHelpers.CustomProviderName(TwoFactorProviderType.Authenticator)) - .AddTokenProvider( + .AddTokenProvider( CoreHelpers.CustomProviderName(TwoFactorProviderType.Email)) .AddTokenProvider( CoreHelpers.CustomProviderName(TwoFactorProviderType.YubiKey)) @@ -408,7 +414,7 @@ public static class ServiceCollectionExtensions CoreHelpers.CustomProviderName(TwoFactorProviderType.Duo)) .AddTokenProvider( CoreHelpers.CustomProviderName(TwoFactorProviderType.Remember)) - .AddTokenProvider>(TokenOptions.DefaultEmailProvider) + .AddTokenProvider(TokenOptions.DefaultEmailProvider) .AddTokenProvider( CoreHelpers.CustomProviderName(TwoFactorProviderType.WebAuthn)); diff --git a/src/Sql/Vault/dbo/Functions/UserCipherDetails.sql b/src/Sql/Vault/dbo/Functions/UserCipherDetails.sql index d42a08cef..6c8c5f8a3 100644 --- a/src/Sql/Vault/dbo/Functions/UserCipherDetails.sql +++ b/src/Sql/Vault/dbo/Functions/UserCipherDetails.sql @@ -4,8 +4,7 @@ AS RETURN WITH [CTE] AS ( SELECT [Id], - [OrganizationId], - [AccessAll] + [OrganizationId] FROM [OrganizationUser] WHERE @@ -15,20 +14,14 @@ WITH [CTE] AS ( SELECT C.*, CASE - WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 + WHEN COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 THEN 1 ELSE 0 END [Edit], CASE - WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 - THEN 1 - ELSE 0 + WHEN COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 + THEN 1 + ELSE 0 END [ViewPassword], CASE WHEN O.[UseTotp] = 1 @@ -42,19 +35,17 @@ INNER JOIN INNER JOIN [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1 LEFT JOIN - [dbo].[CollectionCipher] CC ON OU.[AccessAll] = 0 AND CC.[CipherId] = C.[Id] + [dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] LEFT JOIN [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] WHERE - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL UNION ALL diff --git a/src/Sql/Vault/dbo/Functions/UserCipherDetails_V2.sql b/src/Sql/Vault/dbo/Functions/UserCipherDetails_V2.sql deleted file mode 100644 index 203d360d3..000000000 --- a/src/Sql/Vault/dbo/Functions/UserCipherDetails_V2.sql +++ /dev/null @@ -1,61 +0,0 @@ -CREATE FUNCTION [dbo].[UserCipherDetails_V2](@UserId UNIQUEIDENTIFIER) -RETURNS TABLE -AS RETURN -WITH [CTE] AS ( - SELECT - [Id], - [OrganizationId] - FROM - [OrganizationUser] - WHERE - [UserId] = @UserId - AND [Status] = 2 -- Confirmed -) -SELECT - C.*, - CASE - WHEN COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 - THEN 1 - ELSE 0 - END [Edit], - CASE - WHEN COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 - THEN 1 - ELSE 0 - END [ViewPassword], - CASE - WHEN O.[UseTotp] = 1 - THEN 1 - ELSE 0 - END [OrganizationUseTotp] -FROM - [dbo].[CipherDetails](@UserId) C -INNER JOIN - [CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE]) -INNER JOIN - [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1 -LEFT JOIN - [dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] -LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] -LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] -LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] -LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] -WHERE - CU.[CollectionId] IS NOT NULL - OR CG.[CollectionId] IS NOT NULL - -UNION ALL - -SELECT - *, - 1 [Edit], - 1 [ViewPassword], - 0 [OrganizationUseTotp] -FROM - [dbo].[CipherDetails](@UserId) -WHERE - [UserId] = @UserId diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId_V2.sql deleted file mode 100644 index 0b2c8515a..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByIdUserId_V2.sql +++ /dev/null @@ -1,16 +0,0 @@ -CREATE PROCEDURE [dbo].[CipherDetails_ReadByIdUserId_V2] - @Id UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT TOP 1 - * - FROM - [dbo].[UserCipherDetails_V2](@UserId) - WHERE - [Id] = @Id - ORDER BY - [Edit] DESC -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByUserId_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByUserId_V2.sql deleted file mode 100644 index ab021a4f6..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/CipherDetails_ReadByUserId_V2.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE PROCEDURE [dbo].[CipherDetails_ReadByUserId_V2] - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - * - FROM - [dbo].[UserCipherDetails_V2](@UserId) -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Delete_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Delete_V2.sql deleted file mode 100644 index b57c79934..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Delete_V2.sql +++ /dev/null @@ -1,73 +0,0 @@ -CREATE PROCEDURE [dbo].[Cipher_Delete_V2] - @Ids AS [dbo].[GuidIdArray] READONLY, - @UserId AS UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - CREATE TABLE #Temp - ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [UserId] UNIQUEIDENTIFIER NULL, - [OrganizationId] UNIQUEIDENTIFIER NULL, - [Attachments] BIT NOT NULL - ) - - INSERT INTO #Temp - SELECT - [Id], - [UserId], - [OrganizationId], - CASE WHEN [Attachments] IS NULL THEN 0 ELSE 1 END - FROM - [dbo].[UserCipherDetails_V2](@UserId) - WHERE - [Edit] = 1 - AND [Id] IN (SELECT * FROM @Ids) - - -- Delete ciphers - DELETE - FROM - [dbo].[Cipher] - WHERE - [Id] IN (SELECT [Id] FROM #Temp) - - -- Cleanup orgs - DECLARE @OrgId UNIQUEIDENTIFIER - DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR - SELECT - [OrganizationId] - FROM - #Temp - WHERE - [OrganizationId] IS NOT NULL - GROUP BY - [OrganizationId] - OPEN [OrgCursor] - FETCH NEXT FROM [OrgCursor] INTO @OrgId - WHILE @@FETCH_STATUS = 0 BEGIN - EXEC [dbo].[Organization_UpdateStorage] @OrgId - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId - FETCH NEXT FROM [OrgCursor] INTO @OrgId - END - CLOSE [OrgCursor] - DEALLOCATE [OrgCursor] - - -- Cleanup user - DECLARE @UserCiphersWithStorageCount INT - SELECT - @UserCiphersWithStorageCount = COUNT(1) - FROM - #Temp - WHERE - [UserId] IS NOT NULL - AND [Attachments] = 1 - - IF @UserCiphersWithStorageCount > 0 - BEGIN - EXEC [dbo].[User_UpdateStorage] @UserId - END - EXEC [dbo].[User_BumpAccountRevisionDate] @UserId - - DROP TABLE #Temp -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Move_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Move_V2.sql deleted file mode 100644 index c495c3a26..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Move_V2.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE PROCEDURE [dbo].[Cipher_Move_V2] - @Ids AS [dbo].[GuidIdArray] READONLY, - @FolderId AS UNIQUEIDENTIFIER, - @UserId AS UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"') - DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey) - - ;WITH [IdsToMoveCTE] AS ( - SELECT - [Id] - FROM - [dbo].[UserCipherDetails_V2](@UserId) - WHERE - [Id] IN (SELECT * FROM @Ids) - ) - UPDATE - [dbo].[Cipher] - SET - [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 - WHERE - [Id] IN (SELECT * FROM [IdsToMoveCTE]) - - EXEC [dbo].[User_BumpAccountRevisionDate] @UserId -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_ReadCanEditByIdUserId.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_ReadCanEditByIdUserId.sql index 0c1ad526f..3eb7f73cc 100644 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_ReadCanEditByIdUserId.sql +++ b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_ReadCanEditByIdUserId.sql @@ -9,8 +9,8 @@ BEGIN ;WITH [CTE] AS ( SELECT - CASE - WHEN C.[UserId] IS NOT NULL OR OU.[AccessAll] = 1 OR CU.[ReadOnly] = 0 OR G.[AccessAll] = 1 OR CG.[ReadOnly] = 0 THEN 1 + CASE + WHEN C.[UserId] IS NOT NULL OR CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 THEN 1 ELSE 0 END [Edit] FROM @@ -20,15 +20,15 @@ BEGIN LEFT JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionCipher] CC ON C.[UserId] IS NULL AND OU.[AccessAll] = 0 AND CC.[CipherId] = C.[Id] + [dbo].[CollectionCipher] CC ON C.[UserId] IS NULL AND CC.[CipherId] = C.[Id] LEFT JOIN [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON C.[UserId] IS NULL AND CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON C.[UserId] IS NULL AND CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] WHERE C.Id = @Id AND ( @@ -38,9 +38,7 @@ BEGIN AND OU.[Status] = 2 -- 2 = Confirmed AND O.[Enabled] = 1 AND ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL ) ) @@ -57,4 +55,4 @@ BEGIN [Edit] = 1 SELECT @CanEdit -END \ No newline at end of file +END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Restore_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Restore_V2.sql deleted file mode 100644 index 13b5c1c16..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_Restore_V2.sql +++ /dev/null @@ -1,62 +0,0 @@ -CREATE PROCEDURE [dbo].[Cipher_Restore_V2] - @Ids AS [dbo].[GuidIdArray] READONLY, - @UserId AS UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - CREATE TABLE #Temp - ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [UserId] UNIQUEIDENTIFIER NULL, - [OrganizationId] UNIQUEIDENTIFIER NULL - ) - - INSERT INTO #Temp - SELECT - [Id], - [UserId], - [OrganizationId] - FROM - [dbo].[UserCipherDetails_V2](@UserId) - WHERE - [Edit] = 1 - AND [DeletedDate] IS NOT NULL - AND [Id] IN (SELECT * FROM @Ids) - - DECLARE @UtcNow DATETIME2(7) = GETUTCDATE(); - UPDATE - [dbo].[Cipher] - SET - [DeletedDate] = NULL, - [RevisionDate] = @UtcNow - WHERE - [Id] IN (SELECT [Id] FROM #Temp) - - -- Bump orgs - DECLARE @OrgId UNIQUEIDENTIFIER - DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR - SELECT - [OrganizationId] - FROM - #Temp - WHERE - [OrganizationId] IS NOT NULL - GROUP BY - [OrganizationId] - OPEN [OrgCursor] - FETCH NEXT FROM [OrgCursor] INTO @OrgId - WHILE @@FETCH_STATUS = 0 BEGIN - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId - FETCH NEXT FROM [OrgCursor] INTO @OrgId - END - CLOSE [OrgCursor] - DEALLOCATE [OrgCursor] - - -- Bump user - EXEC [dbo].[User_BumpAccountRevisionDate] @UserId - - DROP TABLE #Temp - - SELECT @UtcNow -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_SoftDelete_V2.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_SoftDelete_V2.sql deleted file mode 100644 index 9a9424767..000000000 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_SoftDelete_V2.sql +++ /dev/null @@ -1,60 +0,0 @@ -CREATE PROCEDURE [dbo].[Cipher_SoftDelete_V2] - @Ids AS [dbo].[GuidIdArray] READONLY, - @UserId AS UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - CREATE TABLE #Temp - ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [UserId] UNIQUEIDENTIFIER NULL, - [OrganizationId] UNIQUEIDENTIFIER NULL - ) - - INSERT INTO #Temp - SELECT - [Id], - [UserId], - [OrganizationId] - FROM - [dbo].[UserCipherDetails_V2](@UserId) - WHERE - [Edit] = 1 - AND [DeletedDate] IS NULL - AND [Id] IN (SELECT * FROM @Ids) - - -- Delete ciphers - DECLARE @UtcNow DATETIME2(7) = GETUTCDATE(); - UPDATE - [dbo].[Cipher] - SET - [DeletedDate] = @UtcNow, - [RevisionDate] = @UtcNow - WHERE - [Id] IN (SELECT [Id] FROM #Temp) - - -- Cleanup orgs - DECLARE @OrgId UNIQUEIDENTIFIER - DECLARE [OrgCursor] CURSOR FORWARD_ONLY FOR - SELECT - [OrganizationId] - FROM - #Temp - WHERE - [OrganizationId] IS NOT NULL - GROUP BY - [OrganizationId] - OPEN [OrgCursor] - FETCH NEXT FROM [OrgCursor] INTO @OrgId - WHILE @@FETCH_STATUS = 0 BEGIN - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId - FETCH NEXT FROM [OrgCursor] INTO @OrgId - END - CLOSE [OrgCursor] - DEALLOCATE [OrgCursor] - - EXEC [dbo].[User_BumpAccountRevisionDate] @UserId - - DROP TABLE #Temp -END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_UpdateCollections.sql b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_UpdateCollections.sql index 6b5e93dc6..a14ef46de 100644 --- a/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_UpdateCollections.sql +++ b/src/Sql/Vault/dbo/Stored Procedures/Cipher/Cipher_UpdateCollections.sql @@ -38,21 +38,19 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] WHERE O.[Id] = @OrganizationId AND O.[Enabled] = 1 AND OU.[Status] = 2 -- Confirmed AND ( - OU.[AccessAll] = 1 - OR CU.[ReadOnly] = 0 - OR G.[AccessAll] = 1 + CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 ) END @@ -77,4 +75,4 @@ BEGIN [Id] IN (SELECT [Id] FROM #AvailableCollections) RETURN(0) -END \ No newline at end of file +END diff --git a/src/Sql/Vault/dbo/Stored Procedures/Folder/Folder_DeleteById.sql b/src/Sql/Vault/dbo/Stored Procedures/Folder/Folder_DeleteById.sql index 2c005a403..d82e92986 100644 --- a/src/Sql/Vault/dbo/Stored Procedures/Folder/Folder_DeleteById.sql +++ b/src/Sql/Vault/dbo/Stored Procedures/Folder/Folder_DeleteById.sql @@ -10,8 +10,7 @@ BEGIN ;WITH [CTE] AS ( SELECT [Id], - [OrganizationId], - [AccessAll] + [OrganizationId] FROM [OrganizationUser] WHERE @@ -29,20 +28,18 @@ BEGIN INNER JOIN [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1 LEFT JOIN - [dbo].[CollectionCipher] CC ON OU.[AccessAll] = 0 AND CC.[CipherId] = C.[Id] + [dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] LEFT JOIN [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] WHERE ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL ) AND JSON_VALUE(C.[Folders], @UserIdPath) = @Id @@ -64,4 +61,4 @@ BEGIN [Id] = @Id EXEC [dbo].[User_BumpAccountRevisionDate] @UserId -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Functions/UserCollectionDetails.sql b/src/Sql/dbo/Functions/UserCollectionDetails.sql index 5d7578727..91dfcec22 100644 --- a/src/Sql/dbo/Functions/UserCollectionDetails.sql +++ b/src/Sql/dbo/Functions/UserCollectionDetails.sql @@ -5,25 +5,19 @@ SELECT C.*, CASE WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 + COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 THEN 0 ELSE 1 END [ReadOnly], CASE WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 + COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 THEN 0 ELSE 1 END [HidePasswords], CASE WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[Manage], CG.[Manage], 0) = 0 + COALESCE(CU.[Manage], CG.[Manage], 0) = 0 THEN 0 ELSE 1 END [Manage] @@ -34,20 +28,18 @@ INNER JOIN INNER JOIN [dbo].[Organization] O ON O.[Id] = C.[OrganizationId] LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] WHERE OU.[UserId] = @UserId AND OU.[Status] = 2 -- 2 = Confirmed AND O.[Enabled] = 1 AND ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL ) diff --git a/src/Sql/dbo/Functions/UserCollectionDetails_V2.sql b/src/Sql/dbo/Functions/UserCollectionDetails_V2.sql deleted file mode 100644 index f3e6a0f39..000000000 --- a/src/Sql/dbo/Functions/UserCollectionDetails_V2.sql +++ /dev/null @@ -1,45 +0,0 @@ -CREATE FUNCTION [dbo].[UserCollectionDetails_V2](@UserId UNIQUEIDENTIFIER) -RETURNS TABLE -AS RETURN -SELECT - C.*, - CASE - WHEN - COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 - THEN 0 - ELSE 1 - END [ReadOnly], - CASE - WHEN - COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 - THEN 0 - ELSE 1 - END [HidePasswords], - CASE - WHEN - COALESCE(CU.[Manage], CG.[Manage], 0) = 0 - THEN 0 - ELSE 1 - END [Manage] -FROM - [dbo].[CollectionView] C -INNER JOIN - [dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] -INNER JOIN - [dbo].[Organization] O ON O.[Id] = C.[OrganizationId] -LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] -LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] -LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] -LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] -WHERE - OU.[UserId] = @UserId - AND OU.[Status] = 2 -- 2 = Confirmed - AND O.[Enabled] = 1 - AND ( - CU.[CollectionId] IS NOT NULL - OR CG.[CollectionId] IS NOT NULL - ) diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId.sql index 9a73ce6a1..37971870c 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId.sql @@ -13,19 +13,17 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] WHERE OU.[Status] = 2 -- Confirmed AND ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId.sql index b83607d65..56d085858 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId.sql @@ -14,20 +14,18 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] WHERE CC.[CipherId] = @CipherId AND OU.[Status] = 2 -- Confirmed AND ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 + CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL ) -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId_V2.sql deleted file mode 100644 index de8cb6e8f..000000000 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserIdCipherId_V2.sql +++ /dev/null @@ -1,31 +0,0 @@ -CREATE PROCEDURE [dbo].[CollectionCipher_ReadByUserIdCipherId_V2] - @UserId UNIQUEIDENTIFIER, - @CipherId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - CC.* - FROM - [dbo].[CollectionCipher] CC - INNER JOIN - [dbo].[Collection] S ON S.[Id] = CC.[CollectionId] - INNER JOIN - [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId - LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] - LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] - WHERE - CC.[CipherId] = @CipherId - AND OU.[Status] = 2 -- Confirmed - AND ( - CU.[CollectionId] IS NOT NULL - OR CG.[CollectionId] IS NOT NULL - ) -END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId_V2.sql deleted file mode 100644 index 6a0b444c3..000000000 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_ReadByUserId_V2.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE PROCEDURE [dbo].[CollectionCipher_ReadByUserId_V2] - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - CC.* - FROM - [dbo].[CollectionCipher] CC - INNER JOIN - [dbo].[Collection] S ON S.[Id] = CC.[CollectionId] - INNER JOIN - [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId - LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] - LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] - WHERE - OU.[Status] = 2 -- Confirmed - AND ( - CU.[CollectionId] IS NOT NULL - OR CG.[CollectionId] IS NOT NULL - ) -END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql index 61ca28f5d..4098ab59e 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql @@ -25,21 +25,19 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] WHERE O.[Id] = @OrgId AND O.[Enabled] = 1 AND OU.[Status] = 2 -- Confirmed AND ( - OU.[AccessAll] = 1 - OR CU.[ReadOnly] = 0 - OR G.[AccessAll] = 1 + CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 ) ), @@ -54,7 +52,7 @@ BEGIN ) MERGE [CollectionCiphersCTE] AS [Target] - USING + USING @CollectionIds AS [Source] ON [Target].[CollectionId] = [Source].[Id] @@ -76,4 +74,4 @@ BEGIN BEGIN EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId END -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers.sql index 0bcb0860f..1c5f165eb 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers.sql @@ -21,21 +21,19 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] WHERE O.[Id] = @OrganizationId AND O.[Enabled] = 1 AND OU.[Status] = 2 -- Confirmed AND ( - OU.[AccessAll] = 1 - OR CU.[ReadOnly] = 0 - OR G.[AccessAll] = 1 + CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 ) @@ -50,7 +48,7 @@ BEGIN [CollectionId], [CipherId] ) - SELECT + SELECT [Collection].[Id], [Cipher].[Id] FROM diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers_V2.sql deleted file mode 100644 index ab06d65e4..000000000 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsForCiphers_V2.sql +++ /dev/null @@ -1,62 +0,0 @@ -CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2] - @CipherIds AS [dbo].[GuidIdArray] READONLY, - @OrganizationId UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER, - @CollectionIds AS [dbo].[GuidIdArray] READONLY -AS -BEGIN - SET NOCOUNT ON - - CREATE TABLE #AvailableCollections ( - [Id] UNIQUEIDENTIFIER - ) - - INSERT INTO #AvailableCollections - SELECT - C.[Id] - FROM - [dbo].[Collection] C - INNER JOIN - [Organization] O ON O.[Id] = C.[OrganizationId] - INNER JOIN - [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId - LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] - LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] - WHERE - O.[Id] = @OrganizationId - AND O.[Enabled] = 1 - AND OU.[Status] = 2 -- Confirmed - AND ( - CU.[ReadOnly] = 0 - OR CG.[ReadOnly] = 0 - ) - - IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 - BEGIN - -- No writable collections available to share with in this organization. - RETURN - END - - INSERT INTO [dbo].[CollectionCipher] - ( - [CollectionId], - [CipherId] - ) - SELECT - [Collection].[Id], - [Cipher].[Id] - FROM - @CollectionIds [Collection] - INNER JOIN - @CipherIds [Cipher] ON 1 = 1 - WHERE - [Collection].[Id] IN (SELECT [Id] FROM #AvailableCollections) - - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections_V2.sql deleted file mode 100644 index c540c1737..000000000 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections_V2.sql +++ /dev/null @@ -1,77 +0,0 @@ -CREATE PROCEDURE [dbo].[CollectionCipher_UpdateCollections_V2] - @CipherId UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER, - @CollectionIds AS [dbo].[GuidIdArray] READONLY -AS -BEGIN - SET NOCOUNT ON - - DECLARE @OrgId UNIQUEIDENTIFIER = ( - SELECT TOP 1 - [OrganizationId] - FROM - [dbo].[Cipher] - WHERE - [Id] = @CipherId - ) - - ;WITH [AvailableCollectionsCTE] AS( - SELECT - C.[Id] - FROM - [dbo].[Collection] C - INNER JOIN - [Organization] O ON O.[Id] = C.[OrganizationId] - INNER JOIN - [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId - LEFT JOIN - [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] - LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] - LEFT JOIN - [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] - WHERE - O.[Id] = @OrgId - AND O.[Enabled] = 1 - AND OU.[Status] = 2 -- Confirmed - AND ( - CU.[ReadOnly] = 0 - OR CG.[ReadOnly] = 0 - ) - ), - [CollectionCiphersCTE] AS( - SELECT - [CollectionId], - [CipherId] - FROM - [dbo].[CollectionCipher] - WHERE - [CipherId] = @CipherId - ) - MERGE - [CollectionCiphersCTE] AS [Target] - USING - @CollectionIds AS [Source] - ON - [Target].[CollectionId] = [Source].[Id] - AND [Target].[CipherId] = @CipherId - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT VALUES - ( - [Source].[Id], - @CipherId - ) - WHEN NOT MATCHED BY SOURCE - AND [Target].[CipherId] = @CipherId - AND [Target].[CollectionId] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - DELETE - ; - - IF @OrgId IS NOT NULL - BEGIN - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId - END -END diff --git a/src/Sql/dbo/Stored Procedures/CollectionUser_ReadByOrganizationUserIds.sql b/src/Sql/dbo/Stored Procedures/CollectionUser_ReadByOrganizationUserIds.sql index 6982371ee..c78d7390a 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionUser_ReadByOrganizationUserIds.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionUser_ReadByOrganizationUserIds.sql @@ -9,7 +9,7 @@ BEGIN FROM [dbo].[OrganizationUser] OU INNER JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = OU.[Id] + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] INNER JOIN @OrganizationUserIds OUI ON OUI.[Id] = OU.[Id] -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql deleted file mode 100644 index c7a68b0d1..000000000 --- a/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql +++ /dev/null @@ -1,83 +0,0 @@ -CREATE PROCEDURE [dbo].[CollectionUser_UpdateUsers_V2] - @CollectionId UNIQUEIDENTIFIER, - @Users AS [dbo].[CollectionAccessSelectionType] READONLY -AS -BEGIN - SET NOCOUNT ON - - DECLARE @OrgId UNIQUEIDENTIFIER = ( - SELECT TOP 1 - [OrganizationId] - FROM - [dbo].[Collection] - WHERE - [Id] = @CollectionId - ) - - -- Update - UPDATE - [Target] - SET - [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] - FROM - [dbo].[CollectionUser] [Target] - INNER JOIN - @Users [Source] ON [Source].[Id] = [Target].[OrganizationUserId] - WHERE - [Target].[CollectionId] = @CollectionId - AND ( - [Target].[ReadOnly] != [Source].[ReadOnly] - OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] - ) - - -- Insert - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - @CollectionId, - [Source].[Id], - [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] - FROM - @Users [Source] - INNER JOIN - [dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId - WHERE - NOT EXISTS ( - SELECT - 1 - FROM - [dbo].[CollectionUser] - WHERE - [CollectionId] = @CollectionId - AND [OrganizationUserId] = [Source].[Id] - ) - - -- Delete - DELETE - CU - FROM - [dbo].[CollectionUser] CU - WHERE - CU.[CollectionId] = @CollectionId - AND NOT EXISTS ( - SELECT - 1 - FROM - @Users - WHERE - [Id] = CU.[OrganizationUserId] - ) - - EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrgId -END diff --git a/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql b/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql deleted file mode 100644 index 11e2cdc07..000000000 --- a/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql +++ /dev/null @@ -1,73 +0,0 @@ -CREATE PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @Name VARCHAR(MAX), - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, - @Users AS [dbo].[CollectionAccessSelectionType] READONLY -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[Collection_Create] @Id, @OrganizationId, @Name, @ExternalId, @CreationDate, @RevisionDate - - -- Groups - ;WITH [AvailableGroupsCTE] AS( - SELECT - [Id] - FROM - [dbo].[Group] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionGroup] - ( - [CollectionId], - [GroupId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - @Id, - [Id], - [ReadOnly], - [HidePasswords], - [Manage] - FROM - @Groups - WHERE - [Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) - - -- Users - ;WITH [AvailableUsersCTE] AS( - SELECT - [Id] - FROM - [dbo].[OrganizationUser] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - @Id, - [Id], - [ReadOnly], - [HidePasswords], - [Manage] - FROM - @Users - WHERE - [Id] IN (SELECT [Id] FROM [AvailableUsersCTE]) - - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/Collection_ReadByUserId_V2.sql b/src/Sql/dbo/Stored Procedures/Collection_ReadByUserId_V2.sql deleted file mode 100644 index 4538dc8da..000000000 --- a/src/Sql/dbo/Stored Procedures/Collection_ReadByUserId_V2.sql +++ /dev/null @@ -1,26 +0,0 @@ -CREATE PROCEDURE [dbo].[Collection_ReadByUserId_V2] - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId, - MIN([ReadOnly]) AS [ReadOnly], - MIN([HidePasswords]) AS [HidePasswords], - MAX([Manage]) AS [Manage] - FROM - [dbo].[UserCollectionDetails_V2](@UserId) - GROUP BY - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId -END diff --git a/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql b/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql deleted file mode 100644 index 1f9cff8fd..000000000 --- a/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql +++ /dev/null @@ -1,111 +0,0 @@ -CREATE PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @Name VARCHAR(MAX), - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, - @Users AS [dbo].[CollectionAccessSelectionType] READONLY -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[Collection_Update] @Id, @OrganizationId, @Name, @ExternalId, @CreationDate, @RevisionDate - - -- Groups - ;WITH [AvailableGroupsCTE] AS( - SELECT - Id - FROM - [dbo].[Group] - WHERE - OrganizationId = @OrganizationId - ) - MERGE - [dbo].[CollectionGroup] AS [Target] - USING - @Groups AS [Source] - ON - [Target].[CollectionId] = @Id - AND [Target].[GroupId] = [Source].[Id] - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT -- Add explicit column list - ( - [CollectionId], - [GroupId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - VALUES - ( - @Id, - [Source].[Id], - [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] - ) - WHEN MATCHED AND ( - [Target].[ReadOnly] != [Source].[ReadOnly] - OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] - ) THEN - UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] - WHEN NOT MATCHED BY SOURCE - AND [Target].[CollectionId] = @Id THEN - DELETE - ; - - -- Users - ;WITH [AvailableGroupsCTE] AS( - SELECT - Id - FROM - [dbo].[OrganizationUser] - WHERE - OrganizationId = @OrganizationId - ) - MERGE - [dbo].[CollectionUser] AS [Target] - USING - @Users AS [Source] - ON - [Target].[CollectionId] = @Id - AND [Target].[OrganizationUserId] = [Source].[Id] - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - VALUES - ( - @Id, - [Source].[Id], - [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] - ) - WHEN MATCHED AND ( - [Target].[ReadOnly] != [Source].[ReadOnly] - OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] - ) THEN - UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] - WHEN NOT MATCHED BY SOURCE - AND [Target].[CollectionId] = @Id THEN - DELETE - ; - - EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/Group_Create.sql b/src/Sql/dbo/Stored Procedures/Group_Create.sql index 893cd57e3..19fd91660 100644 --- a/src/Sql/dbo/Stored Procedures/Group_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Group_Create.sql @@ -2,7 +2,7 @@ @Id UNIQUEIDENTIFIER OUTPUT, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql index e57188ab2..1a5be497e 100644 --- a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql @@ -2,7 +2,7 @@ CREATE PROCEDURE [dbo].[Group_CreateWithCollections] @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql deleted file mode 100644 index 66c98996f..000000000 --- a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql +++ /dev/null @@ -1,44 +0,0 @@ -CREATE PROCEDURE [dbo].[Group_CreateWithCollections_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @Name NVARCHAR(100), - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Collections AS [dbo].[CollectionAccessSelectionType] READONLY -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate - - ;WITH [AvailableCollectionsCTE] AS( - SELECT - [Id] - FROM - [dbo].[Collection] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionGroup] - ( - [CollectionId], - [GroupId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - [Id], - @Id, - [ReadOnly], - [HidePasswords], - [Manage] - FROM - @Collections - WHERE - [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) - - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/Group_Update.sql b/src/Sql/dbo/Stored Procedures/Group_Update.sql index 249e3e41a..68363c6d7 100644 --- a/src/Sql/dbo/Stored Procedures/Group_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Group_Update.sql @@ -2,7 +2,7 @@ @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) @@ -21,4 +21,4 @@ BEGIN [RevisionDate] = @RevisionDate WHERE [Id] = @Id -END \ No newline at end of file +END diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql index de01003bc..d210bb025 100644 --- a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql @@ -2,7 +2,7 @@ @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql deleted file mode 100644 index 40f22a968..000000000 --- a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql +++ /dev/null @@ -1,63 +0,0 @@ -CREATE PROCEDURE [dbo].[Group_UpdateWithCollections_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @Name NVARCHAR(100), - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Collections AS [dbo].[CollectionAccessSelectionType] READONLY -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[Group_Update] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate - - ;WITH [AvailableCollectionsCTE] AS( - SELECT - Id - FROM - [dbo].[Collection] - WHERE - OrganizationId = @OrganizationId - ) - MERGE - [dbo].[CollectionGroup] AS [Target] - USING - @Collections AS [Source] - ON - [Target].[CollectionId] = [Source].[Id] - AND [Target].[GroupId] = @Id - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT - ( - [CollectionId], - [GroupId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - VALUES - ( - [Source].[Id], - @Id, - [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] - ) - WHEN MATCHED AND ( - [Target].[ReadOnly] != [Source].[ReadOnly] - OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] - ) THEN - UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] - WHEN NOT MATCHED BY SOURCE - AND [Target].[GroupId] = @Id THEN - DELETE - ; - - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadWithCollectionsById.sql b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadWithCollectionsById.sql index b5550a40d..b76e4b877 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadWithCollectionsById.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUserUserDetails_ReadWithCollectionsById.sql @@ -14,7 +14,7 @@ BEGIN FROM [dbo].[OrganizationUser] OU INNER JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = [OU].[Id] + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = [OU].[Id] WHERE [OrganizationUserId] = @Id END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql index 887f874b0..5db4d5f1f 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_Create.sql @@ -6,7 +6,7 @@ @Key VARCHAR(MAX), @Status SMALLINT, @Type TINYINT, - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql new file mode 100644 index 000000000..0b67e0c08 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql @@ -0,0 +1,56 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_CreateMany] + @jsonData NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationUser] + ( + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey], + [AccessSecretsManager] + ) + SELECT + OUI.[Id], + OUI.[OrganizationId], + OUI.[UserId], + OUI.[Email], + OUI.[Key], + OUI.[Status], + OUI.[Type], + 0, -- AccessAll will be removed shortly + OUI.[ExternalId], + OUI.[CreationDate], + OUI.[RevisionDate], + OUI.[Permissions], + OUI.[ResetPasswordKey], + OUI.[AccessSecretsManager] + FROM + OPENJSON(@jsonData) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [OrganizationId] UNIQUEIDENTIFIER '$.OrganizationId', + [UserId] UNIQUEIDENTIFIER '$.UserId', + [Email] NVARCHAR(256) '$.Email', + [Key] VARCHAR(MAX) '$.Key', + [Status] SMALLINT '$.Status', + [Type] TINYINT '$.Type', + [ExternalId] NVARCHAR(300) '$.ExternalId', + [CreationDate] DATETIME2(7) '$.CreationDate', + [RevisionDate] DATETIME2(7) '$.RevisionDate', + [Permissions] NVARCHAR (MAX) '$.Permissions', + [ResetPasswordKey] VARCHAR (MAX) '$.ResetPasswordKey', + [AccessSecretsManager] BIT '$.AccessSecretsManager' + ) OUI +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql index d8245fe1f..520480d01 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql @@ -6,7 +6,7 @@ CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] @Key VARCHAR(MAX), @Status SMALLINT, @Type TINYINT, - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql deleted file mode 100644 index 50b1fb5fc..000000000 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql +++ /dev/null @@ -1,49 +0,0 @@ -CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER, - @Email NVARCHAR(256), - @Key VARCHAR(MAX), - @Status SMALLINT, - @Type TINYINT, - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Permissions NVARCHAR(MAX), - @ResetPasswordKey VARCHAR(MAX), - @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, - @AccessSecretsManager BIT = 0 -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager - - ;WITH [AvailableCollectionsCTE] AS( - SELECT - [Id] - FROM - [dbo].[Collection] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - [Id], - @Id, - [ReadOnly], - [HidePasswords], - [Manage] - FROM - @Collections - WHERE - [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) -END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadWithCollectionsById.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadWithCollectionsById.sql index 56fb360a7..28b3100cf 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadWithCollectionsById.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadWithCollectionsById.sql @@ -14,7 +14,7 @@ BEGIN FROM [dbo].[OrganizationUser] OU INNER JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = [OU].[Id] + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = [OU].[Id] WHERE [OrganizationUserId] = @Id END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_Update.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_Update.sql index a4565d789..297033bad 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_Update.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_Update.sql @@ -6,7 +6,7 @@ @Key VARCHAR(MAX), @Status SMALLINT, @Type TINYINT, - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateDataForKeyRotation.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateDataForKeyRotation.sql new file mode 100644 index 000000000..3c7047042 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateDataForKeyRotation.sql @@ -0,0 +1,36 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_UpdateDataForKeyRotation] + @UserId UNIQUEIDENTIFIER, + @OrganizationUserJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse the JSON string and insert into a temporary table + DECLARE @OrganizationUserInput AS TABLE ( + [Id] UNIQUEIDENTIFIER, + [ResetPasswordKey] VARCHAR(MAX) + ) + + INSERT INTO @OrganizationUserInput + SELECT + [Id], + [ResetPasswordKey] + FROM OPENJSON(@OrganizationUserJson) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [ResetPasswordKey] VARCHAR(MAX) '$.ResetPasswordKey' + ) + + -- Perform the update + UPDATE + [dbo].[OrganizationUser] + SET + [ResetPasswordKey] = OUI.[ResetPasswordKey] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + @OrganizationUserInput OUI ON OU.Id = OUI.Id + WHERE + OU.[UserId] = @UserId + +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql new file mode 100644 index 000000000..3843c0e31 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql @@ -0,0 +1,84 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_UpdateMany] + @jsonData NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse the JSON string + DECLARE @OrganizationUserInput AS TABLE ( + [Id] UNIQUEIDENTIFIER, + [OrganizationId] UNIQUEIDENTIFIER, + [UserId] UNIQUEIDENTIFIER, + [Email] NVARCHAR(256), + [Key] VARCHAR(MAX), + [Status] SMALLINT, + [Type] TINYINT, + [ExternalId] NVARCHAR(300), + [CreationDate] DATETIME2(7), + [RevisionDate] DATETIME2(7), + [Permissions] NVARCHAR(MAX), + [ResetPasswordKey] VARCHAR(MAX), + [AccessSecretsManager] BIT + ) + + INSERT INTO @OrganizationUserInput + SELECT + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey], + [AccessSecretsManager] + FROM OPENJSON(@jsonData) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [OrganizationId] UNIQUEIDENTIFIER '$.OrganizationId', + [UserId] UNIQUEIDENTIFIER '$.UserId', + [Email] NVARCHAR(256) '$.Email', + [Key] VARCHAR(MAX) '$.Key', + [Status] SMALLINT '$.Status', + [Type] TINYINT '$.Type', + [ExternalId] NVARCHAR(300) '$.ExternalId', + [CreationDate] DATETIME2(7) '$.CreationDate', + [RevisionDate] DATETIME2(7) '$.RevisionDate', + [Permissions] NVARCHAR (MAX) '$.Permissions', + [ResetPasswordKey] VARCHAR (MAX) '$.ResetPasswordKey', + [AccessSecretsManager] BIT '$.AccessSecretsManager' + ) + + -- Perform the update + UPDATE + OU + SET + [OrganizationId] = OUI.[OrganizationId], + [UserId] = OUI.[UserId], + [Email] = OUI.[Email], + [Key] = OUI.[Key], + [Status] = OUI.[Status], + [Type] = OUI.[Type], + [AccessAll] = 0, -- AccessAll will be removed shortly + [ExternalId] = OUI.[ExternalId], + [CreationDate] = OUI.[CreationDate], + [RevisionDate] = OUI.[RevisionDate], + [Permissions] = OUI.[Permissions], + [ResetPasswordKey] = OUI.[ResetPasswordKey], + [AccessSecretsManager] = OUI.[AccessSecretsManager] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + @OrganizationUserInput OUI ON OU.Id = OUI.Id + + -- Bump account revision dates + EXEC [dbo].[User_BumpManyAccountRevisionDates] + ( + SELECT [UserId] + FROM @OrganizationUserInput + ) +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql index 20797b144..be3e7d10a 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql @@ -6,7 +6,7 @@ @Key VARCHAR(MAX), @Status SMALLINT, @Type TINYINT, - @AccessAll BIT, + @AccessAll BIT = 0, @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql deleted file mode 100644 index f152df3b1..000000000 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql +++ /dev/null @@ -1,86 +0,0 @@ -CREATE PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections_V2] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER, - @Email NVARCHAR(256), - @Key VARCHAR(MAX), - @Status SMALLINT, - @Type TINYINT, - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Permissions NVARCHAR(MAX), - @ResetPasswordKey VARCHAR(MAX), - @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, - @AccessSecretsManager BIT = 0 -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager - -- Update - UPDATE - [Target] - SET - [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] - FROM - [dbo].[CollectionUser] AS [Target] - INNER JOIN - @Collections AS [Source] ON [Source].[Id] = [Target].[CollectionId] - WHERE - [Target].[OrganizationUserId] = @Id - AND ( - [Target].[ReadOnly] != [Source].[ReadOnly] - OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] - ) - - -- Insert - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - [Source].[Id], - @Id, - [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] - FROM - @Collections AS [Source] - INNER JOIN - [dbo].[Collection] C ON C.[Id] = [Source].[Id] AND C.[OrganizationId] = @OrganizationId - WHERE - NOT EXISTS ( - SELECT - 1 - FROM - [dbo].[CollectionUser] - WHERE - [CollectionId] = [Source].[Id] - AND [OrganizationUserId] = @Id - ) - - -- Delete - DELETE - CU - FROM - [dbo].[CollectionUser] CU - WHERE - CU.[OrganizationUserId] = @Id - AND NOT EXISTS ( - SELECT - 1 - FROM - @Collections - WHERE - [Id] = CU.[CollectionId] - ) -END diff --git a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCipherId.sql b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCipherId.sql index 5b96db54a..1d0304028 100644 --- a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCipherId.sql +++ b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCipherId.sql @@ -16,20 +16,18 @@ BEGIN LEFT JOIN [dbo].[CollectionCipher] CC ON CC.[CipherId] = @CipherId LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = CC.[CollectionId] + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = CC.[CollectionId] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId] + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId] WHERE OU.[OrganizationId] = @OrganizationId AND OU.[Status] = 2 -- 2 = Confirmed AND ( CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL - OR OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 ) END diff --git a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionId.sql b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionId.sql index f001a54cd..bb6df18e8 100644 --- a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionId.sql +++ b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionId.sql @@ -14,20 +14,18 @@ BEGIN INNER JOIN [dbo].[OrganizationUser] OU ON OU.[UserId] = U.[Id] LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = @CollectionId + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = @CollectionId LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = @CollectionId + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = @CollectionId WHERE OU.[OrganizationId] = @OrganizationId AND OU.[Status] = 2 -- 2 = Confirmed AND ( CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL - OR OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 ) END diff --git a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionIds.sql b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionIds.sql index d027708a6..430dcec88 100644 --- a/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionIds.sql +++ b/src/Sql/dbo/Stored Procedures/User_BumpAccountRevisionDateByCollectionIds.sql @@ -16,20 +16,18 @@ SET INNER JOIN [dbo].[OrganizationUser] OU ON OU.[UserId] = U.[Id] LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = C.[Id] + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = C.[Id] LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] LEFT JOIN [dbo].[Group] G ON G.[Id] = GU.[GroupId] LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = C.[Id] + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = C.[Id] WHERE OU.[OrganizationId] = @OrganizationId AND OU.[Status] = 2 -- 2 = Confirmed AND ( CU.[CollectionId] IS NOT NULL OR CG.[CollectionId] IS NOT NULL - OR OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - ) + ) END diff --git a/src/Sql/dbo/Stored Procedures/User_ReadByIdsWithCalculatedPremium.sql b/src/Sql/dbo/Stored Procedures/User_ReadByIdsWithCalculatedPremium.sql new file mode 100644 index 000000000..a00a5fc5e --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/User_ReadByIdsWithCalculatedPremium.sql @@ -0,0 +1,41 @@ +CREATE PROCEDURE [dbo].[User_ReadByIdsWithCalculatedPremium] + @Ids NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON; + + -- Declare a table variable to hold the parsed JSON data + DECLARE @ParsedIds TABLE (Id UNIQUEIDENTIFIER); + + -- Parse the JSON input into the table variable + INSERT INTO @ParsedIds (Id) + SELECT value + FROM OPENJSON(@Ids); + + -- Check if the input table is empty + IF (SELECT COUNT(1) FROM @ParsedIds) < 1 + BEGIN + RETURN(-1); + END + + -- Main query to fetch user details and calculate premium access + SELECT + U.*, + CASE + WHEN U.[Premium] = 1 + OR EXISTS ( + SELECT 1 + FROM [dbo].[OrganizationUser] OU + JOIN [dbo].[Organization] O ON OU.[OrganizationId] = O.[Id] + WHERE OU.[UserId] = U.[Id] + AND O.[UsersGetPremium] = 1 + AND O.[Enabled] = 1 + ) + THEN 1 + ELSE 0 + END AS HasPremiumAccess + FROM + [dbo].[UserView] U + WHERE + U.[Id] IN (SELECT [Id] FROM @ParsedIds); +END; diff --git a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql index b1f768908..ae82a621c 100644 --- a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql @@ -11,7 +11,6 @@ SELECT U.[Premium], OU.[Status], OU.[Type], - OU.[AccessAll], OU.[AccessSecretsManager], OU.[ExternalId], SU.[ExternalId] SsoExternalId, diff --git a/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs b/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs index a4500046e..1bce9398e 100644 --- a/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs +++ b/test/Api.IntegrationTest/Controllers/AccountsControllerTest.cs @@ -24,6 +24,7 @@ public class AccountsControllerTest : IClassFixture response.EnsureSuccessStatusCode(); var content = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(content); Assert.Equal("integration-test@bitwarden.com", content.Email); Assert.Null(content.Name); Assert.False(content.EmailVerified); diff --git a/test/Api.IntegrationTest/Helpers/LoginHelper.cs b/test/Api.IntegrationTest/Helpers/LoginHelper.cs index f036970a0..d6ce911bd 100644 --- a/test/Api.IntegrationTest/Helpers/LoginHelper.cs +++ b/test/Api.IntegrationTest/Helpers/LoginHelper.cs @@ -32,6 +32,6 @@ public class LoginHelper var organizationApiKeyRepository = factory.GetService(); var apiKeys = await organizationApiKeyRepository.GetManyByOrganizationIdTypeAsync(organizationId); var clientId = $"organization.{organizationId}"; - return (clientId, apiKeys.SingleOrDefault().ApiKey); + return (clientId, apiKeys.Single().ApiKey); } } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/CountsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/CountsControllerTests.cs new file mode 100644 index 000000000..eb4b4de8f --- /dev/null +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/CountsControllerTests.cs @@ -0,0 +1,550 @@ +using System.Net; +using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.SecretsManager.Enums; +using Bit.Api.IntegrationTest.SecretsManager.Helpers; +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Xunit; + +namespace Bit.Api.IntegrationTest.SecretsManager.Controllers; + +public class CountsControllerTests : IClassFixture, IAsyncLifetime +{ + private readonly string _mockEncryptedString = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + private readonly IProjectRepository _projectRepository; + private readonly ISecretRepository _secretRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; + private readonly IApiKeyRepository _apiKeyRepository; + private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly IGroupRepository _groupRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly LoginHelper _loginHelper; + + private string _email = null!; + private SecretsManagerOrganizationHelper _organizationHelper = null!; + + + public CountsControllerTests(ApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + _projectRepository = _factory.GetService(); + _secretRepository = _factory.GetService(); + _serviceAccountRepository = _factory.GetService(); + _apiKeyRepository = _factory.GetService(); + _accessPolicyRepository = _factory.GetService(); + _groupRepository = _factory.GetService(); + _organizationUserRepository = _factory.GetService(); + _loginHelper = new LoginHelper(_factory, _client); + } + + + public async Task InitializeAsync() + { + _email = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_email); + _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + public async Task GetByOrganizationAsync_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, + bool organizationEnabled) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled); + await _loginHelper.LoginAsync(_email); + + var response = await _client.GetAsync($"/organizations/{org.Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByOrganizationAsync_RunAsServiceAccount_NotFound() + { + var (_, org, _) = await SetupProjectsWithAccessAsync(PermissionType.RunAsServiceAccountWithPermission); + + var response = await _client.GetAsync($"/organizations/{org.Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByOrganizationAsync_UserWithoutPermission_ZeroCounts() + { + var (_, org, _) = await SetupProjectsWithAccessAsync(PermissionType.RunAsUserWithPermission, 0); + + var projects = await CreateProjectsAsync(org.Id); + await CreateSecretsAsync(org.Id, projects[0]); + await CreateServiceAccountsAsync(org.Id); + + var response = await _client.GetAsync($"/organizations/{org.Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal(0, result.Projects); + Assert.Equal(0, result.Secrets); + Assert.Equal(0, result.ServiceAccounts); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetByOrganizationAsync_Success(PermissionType permissionType) + { + var (projects, org, user) = await SetupProjectsWithAccessAsync(permissionType); + var projectsWithoutAccess = await CreateProjectsAsync(org.Id); + + var secrets = await CreateSecretsAsync(org.Id, projects[0]); + var secretsWithoutAccess = await CreateSecretsAsync(org.Id, projectsWithoutAccess[0]); + var secretsWithoutProject = await CreateSecretsAsync(org.Id, null); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + await CreateUserServiceAccountAccessPolicyAsync(user.Id, serviceAccounts[0].Id); + + var response = await _client.GetAsync($"/organizations/{org.Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + if (permissionType == PermissionType.RunAsAdmin) + { + Assert.Equal(projects.Count + projectsWithoutAccess.Count, result.Projects); + Assert.Equal(secrets.Count + secretsWithoutAccess.Count + secretsWithoutProject.Count, + result.Secrets); + Assert.Equal(serviceAccounts.Count, result.ServiceAccounts); + } + else + { + Assert.Equal(projects.Count, result.Projects); + Assert.Equal(secrets.Count, result.Secrets); + Assert.Equal(1, result.ServiceAccounts); + } + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + public async Task GetByProjectAsync_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, + bool organizationEnabled) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled); + await _loginHelper.LoginAsync(_email); + + var projects = await CreateProjectsAsync(org.Id); + + var response = await _client.GetAsync($"/projects/{projects[0].Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByProjectAsync_RunAsServiceAccount_NotFound() + { + var (projects, _, _) = await SetupProjectsWithAccessAsync(PermissionType.RunAsServiceAccountWithPermission); + + var response = await _client.GetAsync($"/projects/{projects[0].Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetByProjectAsync_NonExistingProject_NotFound(PermissionType permissionType) + { + await SetupProjectsWithAccessAsync(permissionType); + + var response = await _client.GetAsync($"/projects/{Guid.NewGuid().ToString()}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByProjectAsync_UserWithoutPermission_ZeroCounts() + { + var (_, org, user) = await SetupProjectsWithAccessAsync(PermissionType.RunAsUserWithPermission, 0); + + var projects = await CreateProjectsAsync(org.Id); + + await CreateSecretsAsync(org.Id, projects[0]); + + var groups = await CreateGroupsAsync(org.Id, user); + await CreateGroupProjectAccessPolicyAsync(groups[0].Id, projects[0].Id); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[0].Id, serviceAccounts[0].Id); + + var response = await _client.GetAsync($"/projects/{projects[0].Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal(0, result.Secrets); + Assert.Equal(0, result.People); + Assert.Equal(0, result.ServiceAccounts); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin, true)] + [InlineData(PermissionType.RunAsUserWithPermission, false)] + [InlineData(PermissionType.RunAsUserWithPermission, true)] + public async Task GetByProjectAsync_Success(PermissionType permissionType, bool userProjectWriteAccess) + { + var (projects, org, user) = await SetupProjectsWithAccessAsync(permissionType, 3, userProjectWriteAccess); + + var secrets = await CreateSecretsAsync(org.Id, projects[0]); + await CreateSecretsAsync(org.Id, projects[1]); + + var groups = await CreateGroupsAsync(org.Id, user); + await CreateGroupProjectAccessPolicyAsync(groups[0].Id, projects[0].Id); + await CreateGroupProjectAccessPolicyAsync(groups[0].Id, projects[1].Id); + var (_, user2) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await CreateUserProjectAccessPolicyAsync(user2.Id, projects[0].Id); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + await CreateUserServiceAccountAccessPolicyAsync(user.Id, serviceAccounts[0].Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[0].Id, serviceAccounts[0].Id); + + var response = await _client.GetAsync($"/projects/{projects[0].Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal(secrets.Count, result.Secrets); + if (userProjectWriteAccess) + { + Assert.Equal(permissionType == PermissionType.RunAsAdmin ? 2 : 3, result.People); + Assert.Equal(1, result.ServiceAccounts); + } + else + { + Assert.Equal(0, result.People); + Assert.Equal(0, result.ServiceAccounts); + } + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + public async Task GetByServiceAccountAsync_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, + bool organizationEnabled) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled); + await _loginHelper.LoginAsync(_email); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccounts[0].Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByServiceAccountAsync_RunAsServiceAccount_NotFound() + { + var (_, org, _) = await SetupProjectsWithAccessAsync(PermissionType.RunAsServiceAccountWithPermission); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccounts[0].Id}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetByServiceAccountAsync_NonExistingServiceAccount_NotFound(PermissionType permissionType) + { + await SetupProjectsWithAccessAsync(permissionType); + + var response = await _client.GetAsync($"/service-accounts/{Guid.NewGuid().ToString()}/sm-counts"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetByServiceAccountAsync_UserWithoutPermission_ZeroCounts() + { + var (_, org, user) = await SetupProjectsWithAccessAsync(PermissionType.RunAsUserWithPermission, 0); + + var projects = await CreateProjectsAsync(org.Id); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[0].Id, serviceAccounts[0].Id); + + var groups = await CreateGroupsAsync(org.Id, user); + await CreateGroupServiceAccountAccessPolicyAsync(groups[0].Id, serviceAccounts[0].Id); + + await CreateApiKeysAsync(serviceAccounts[0]); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccounts[0].Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal(0, result.Projects); + Assert.Equal(0, result.People); + Assert.Equal(0, result.AccessTokens); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetByServiceAccountAsync_Success(PermissionType permissionType) + { + var (projects, org, user) = await SetupProjectsWithAccessAsync(permissionType); + + var serviceAccounts = await CreateServiceAccountsAsync(org.Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[0].Id, serviceAccounts[0].Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[0].Id, serviceAccounts[1].Id); + await CreateServiceAccountProjectAccessPolicyAsync(projects[1].Id, serviceAccounts[0].Id); + + await CreateUserServiceAccountAccessPolicyAsync(user.Id, serviceAccounts[0].Id); + var groups = await CreateGroupsAsync(org.Id, user); + await CreateGroupServiceAccountAccessPolicyAsync(groups[0].Id, serviceAccounts[0].Id); + await CreateGroupServiceAccountAccessPolicyAsync(groups[0].Id, serviceAccounts[1].Id); + var (_, user2) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await CreateUserServiceAccountAccessPolicyAsync(user2.Id, serviceAccounts[0].Id); + + var apiKeys = await CreateApiKeysAsync(serviceAccounts[0]); + await CreateApiKeysAsync(serviceAccounts[1]); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccounts[0].Id}/sm-counts"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(result); + Assert.Equal(2, result.Projects); + Assert.Equal(3, result.People); + Assert.Equal(apiKeys.Count, result.AccessTokens); + } + + private async Task> CreateProjectsAsync(Guid orgId, int numberToCreate = 3) + { + var projects = new List(); + for (var i = 0; i < numberToCreate; i++) + { + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = orgId, + Name = _mockEncryptedString, + }); + projects.Add(project); + } + + return projects; + } + + private async Task> CreateSecretsAsync(Guid organizationId, Project? project, int numberToCreate = 3) + { + var secrets = new List(); + for (var i = 0; i < numberToCreate; i++) + { + var secret = await _secretRepository.CreateAsync(new Secret + { + OrganizationId = organizationId, + Key = _mockEncryptedString, + Value = _mockEncryptedString, + Note = _mockEncryptedString, + Projects = project != null ? new List { project } : null + }); + secrets.Add(secret); + } + + return secrets; + } + + private async Task> CreateServiceAccountsAsync(Guid organizationId, int numberToCreate = 3) + { + var serviceAccounts = new List(); + for (var i = 0; i < numberToCreate; i++) + { + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = organizationId, + Name = _mockEncryptedString + }); + serviceAccounts.Add(serviceAccount); + } + + return serviceAccounts; + } + + private async Task> CreateGroupsAsync(Guid organizationId, OrganizationUser? user, + int numberToCreate = 3) + { + var groups = new List(); + + for (var i = 0; i < numberToCreate; i++) + { + var group = await _groupRepository.CreateAsync(new Group + { + OrganizationId = organizationId, + Name = _mockEncryptedString, + }); + groups.Add(group); + + if (user != null) + { + await _organizationUserRepository.UpdateGroupsAsync(user.Id, [group.Id]); + } + } + + return groups; + } + + private async Task> CreateApiKeysAsync(ServiceAccount serviceAccount, int numberToCreate = 3) + { + var apiKeys = new List(); + + for (var i = 0; i < numberToCreate; i++) + { + var apiKey = await _apiKeyRepository.CreateAsync(new ApiKey + { + Name = _mockEncryptedString, + ServiceAccountId = serviceAccount.Id, + Scope = "api.secrets", + Key = serviceAccount.OrganizationId.ToString(), + EncryptedPayload = _mockEncryptedString, + ClientSecretHash = "807613bbf6692e6809a571bc694a4719a5aa6863f7a62bd714003ab73de588e6" + }); + apiKeys.Add(apiKey); + } + + return apiKeys; + } + + private async Task<(List, Organization, OrganizationUser)> SetupProjectsWithAccessAsync( + PermissionType permissionType, + int projectsToCreate = 3, + bool writeAccess = false) + { + var (org, owner) = await _organizationHelper.Initialize(true, true, true); + var projects = await CreateProjectsAsync(org.Id, projectsToCreate); + var user = owner; + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + await _loginHelper.LoginAsync(_email); + break; + case PermissionType.RunAsUserWithPermission: + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + user = orgUser; + await _loginHelper.LoginAsync(email); + + foreach (var project in projects) + { + await CreateUserProjectAccessPolicyAsync(user.Id, project.Id, writeAccess); + } + + break; + } + case PermissionType.RunAsServiceAccountWithPermission: + { + var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync(); + await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails); + + foreach (var project in projects) + { + await CreateServiceAccountProjectAccessPolicyAsync(project.Id, apiKeyDetails.ApiKey.ServiceAccountId!.Value); + } + + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + + return (projects, org, user); + } + + private async Task CreateUserProjectAccessPolicyAsync(Guid userId, Guid projectId, bool write = false) + { + var policy = new UserProjectAccessPolicy + { + OrganizationUserId = userId, + GrantedProjectId = projectId, + Read = true, + Write = write, + }; + await _accessPolicyRepository.CreateManyAsync([policy]); + } + + private async Task CreateGroupProjectAccessPolicyAsync(Guid groupId, Guid projectId) + { + var policy = new GroupProjectAccessPolicy + { + GroupId = groupId, + GrantedProjectId = projectId, + Read = true, + Write = false, + }; + await _accessPolicyRepository.CreateManyAsync([policy]); + } + + + private async Task CreateUserServiceAccountAccessPolicyAsync(Guid userId, Guid serviceAccountId) + { + var policy = new UserServiceAccountAccessPolicy + { + OrganizationUserId = userId, + GrantedServiceAccountId = serviceAccountId, + Read = true, + Write = false, + }; + await _accessPolicyRepository.CreateManyAsync([policy]); + } + + private async Task CreateGroupServiceAccountAccessPolicyAsync(Guid groupId, Guid serviceAccountId) + { + var policy = new GroupServiceAccountAccessPolicy + { + GroupId = groupId, + GrantedServiceAccountId = serviceAccountId, + Read = true, + Write = false + }; + await _accessPolicyRepository.CreateManyAsync([policy]); + } + + private async Task CreateServiceAccountProjectAccessPolicyAsync(Guid projectId, Guid serviceAccountId) + { + var policy = new ServiceAccountProjectAccessPolicy + { + ServiceAccountId = serviceAccountId, + GrantedProjectId = projectId, + Read = true, + Write = false, + }; + await _accessPolicyRepository.CreateManyAsync([policy]); + } +} diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTests.cs index bfa2cc344..099dde512 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTests.cs @@ -295,11 +295,7 @@ public class ProjectsControllerTests : IClassFixture, IAs Name = _mockEncryptedString, }); - var mockEncryptedString2 = - "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; - var request = new ProjectCreateRequestModel { Name = mockEncryptedString2 }; - - var response = await _client.PutAsJsonAsync($"/projects/{project.Id}", request); + var response = await _client.GetAsync($"/projects/{project.Id}"); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs index 23adbff4e..be95c0dc1 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTests.cs @@ -741,44 +741,83 @@ public class SecretsControllerTests : IClassFixture, IAsy Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Theory] - [InlineData(PermissionType.RunAsAdmin)] - [InlineData(PermissionType.RunAsUserWithPermission)] - public async Task GetSecretsByIds_Success(PermissionType permissionType) + [Fact] + public async Task GetSecretsByIds_SecretsNotInTheSameOrganization_NotFound() { var (org, _) = await _organizationHelper.Initialize(true, true, true); await _loginHelper.LoginAsync(_email); - - var (project, secretIds) = await CreateSecretsAsync(org.Id); - - if (permissionType == PermissionType.RunAsUserWithPermission) - { - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); - await _loginHelper.LoginAsync(email); - - var accessPolicies = new List - { - new UserProjectAccessPolicy - { - GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, - }, - }; - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - } - else - { - var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.Admin, true); - await _loginHelper.LoginAsync(email); - } + var otherOrg = await _organizationHelper.CreateSmOrganizationAsync(); + var (_, secretIds) = await CreateSecretsAsync(org.Id); + var (_, diffOrgSecrets) = await CreateSecretsAsync(otherOrg.Id, 1); + secretIds.AddRange(diffOrgSecrets); var request = new GetSecretsRequestModel { Ids = secretIds }; + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GetSecretsByIds_SecretsNonExistent_NotFound(bool partial) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + await _loginHelper.LoginAsync(_email); + var ids = new List(); + + if (partial) + { + var (_, secretIds) = await CreateSecretsAsync(org.Id); + ids = secretIds; + ids.Add(Guid.NewGuid()); + } + + var request = new GetSecretsRequestModel { Ids = ids }; + + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(true, false)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(false, true)] + public async Task GetSecretsByIds_NoAccess_NotFound(bool runAsServiceAccount, bool partialAccess) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + + var request = await SetupNoAccessRequestAsync(org.Id, runAsServiceAccount, partialAccess); + + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + [InlineData(PermissionType.RunAsServiceAccountWithPermission)] + public async Task GetSecretsByIds_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true, true); + await _loginHelper.LoginAsync(_email); + var request = await SetupGetSecretsByIdsRequestAsync(org.Id, permissionType); + var response = await _client.PostAsJsonAsync("/secrets/get-by-ids", request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(result); Assert.NotEmpty(result.Data); - Assert.Equal(secretIds.Count, result.Data.Count()); + Assert.Equal(request.Ids.Count(), result.Data.Count()); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Value)); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Key)); + Assert.All(result.Data, data => Assert.Equal(_mockEncryptedString, data.Note)); + Assert.All(result.Data, data => Assert.Equal(org.Id, data.OrganizationId)); } @@ -1161,4 +1200,94 @@ public class SecretsControllerTests : IClassFixture, IAsy return (secret, request); } + + private async Task SetupGetSecretsByIdsRequestAsync(Guid organizationId, + PermissionType permissionType) + { + var (project, secretIds) = await CreateSecretsAsync(organizationId); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await _loginHelper.LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + if (permissionType == PermissionType.RunAsServiceAccountWithPermission) + { + var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync(); + await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails); + + var accessPolicies = new List + { + new ServiceAccountProjectAccessPolicy + { + GrantedProjectId = project.Id, + ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + return new GetSecretsRequestModel { Ids = secretIds }; + } + + private async Task SetupNoAccessRequestAsync(Guid organizationId, bool runAsServiceAccount, + bool partialAccess) + { + var (_, secretIds) = await CreateSecretsAsync(organizationId); + + if (runAsServiceAccount) + { + var apiKeyDetails = await _organizationHelper.CreateNewServiceAccountApiKeyAsync(); + await _loginHelper.LoginWithApiKeyAsync(apiKeyDetails); + + if (partialAccess) + { + var accessPolicies = new List + { + new ServiceAccountSecretAccessPolicy + { + GrantedSecretId = secretIds[0], + ServiceAccountId = apiKeyDetails.ApiKey.ServiceAccountId, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + } + else + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await _loginHelper.LoginAsync(email); + + if (partialAccess) + { + var accessPolicies = new List + { + new UserSecretAccessPolicy + { + GrantedSecretId = secretIds[0], + OrganizationUserId = orgUser.Id, + Read = true, + Write = true + } + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + } + + return new GetSecretsRequestModel { Ids = secretIds }; + } } diff --git a/test/Api.Test/AdminConsole/Controllers/GroupsControllerPutTests.cs b/test/Api.Test/AdminConsole/Controllers/GroupsControllerPutTests.cs new file mode 100644 index 000000000..3a8e4831a --- /dev/null +++ b/test/Api.Test/AdminConsole/Controllers/GroupsControllerPutTests.cs @@ -0,0 +1,321 @@ +using System.Security.Claims; +using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Request; +using Bit.Api.Models.Request; +using Bit.Api.Vault.AuthorizationHandlers.Collections; +using Bit.Core; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.AdminConsole.Controllers; + +[ControllerCustomize(typeof(GroupsController))] +[SutProviderCustomize] +public class GroupsControllerPutTests +{ + [Theory] + [BitAutoData] + public async Task Put_WithAdminAccess_Success(Organization organization, Group group, + GroupRequestModel groupRequestModel, List existingCollectionAccess, + OrganizationUser savingUser, SutProvider sutProvider) + { + Put_Setup(sutProvider, organization, true, group, savingUser, existingCollectionAccess, []); + + var requestModelCollectionIds = groupRequestModel.Collections.Select(c => c.Id).ToHashSet(); + + // Authorize all changes for basic happy path test + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) + .Returns(AuthorizationResult.Success()); + + var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); + + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), + Arg.Is(o => o.Id == organization.Id), + // Should overwrite any existing collections + Arg.Is>(access => + access.All(c => requestModelCollectionIds.Contains(c.Id))), + Arg.Is>(guids => guids.ToHashSet().SetEquals(groupRequestModel.Users.ToHashSet()))); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id, response.OrganizationId); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateMembers_NoAdminAccess_CannotAddSelfToGroup(Organization organization, Group group, + GroupRequestModel groupRequestModel, OrganizationUser savingUser, List currentGroupUsers, + SutProvider sutProvider) + { + // Not updating collections + groupRequestModel.Collections = []; + + Put_Setup(sutProvider, organization, false, group, savingUser, + currentCollectionAccess: [], currentGroupUsers); + + // Saving user is trying to add themselves to the group + var updatedUsers = groupRequestModel.Users.ToList(); + updatedUsers.Add(savingUser.Id); + groupRequestModel.Users = updatedUsers; + + var exception = await + Assert.ThrowsAsync(() => sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel)); + + Assert.Contains("You cannot add yourself to groups", exception.Message); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateMembers_NoAdminAccess_AlreadyInGroup_Success(Organization organization, Group group, + GroupRequestModel groupRequestModel, OrganizationUser savingUser, List currentGroupUsers, + SutProvider sutProvider) + { + // Not changing collection access + groupRequestModel.Collections = []; + + // Saving user is trying to add themselves to the group + var updatedUsers = groupRequestModel.Users.ToList(); + updatedUsers.Add(savingUser.Id); + groupRequestModel.Users = updatedUsers; + + // But! they are already a member of the group + currentGroupUsers.Add(savingUser.Id); + + Put_Setup(sutProvider, organization, false, group, savingUser, currentCollectionAccess: [], currentGroupUsers); + + var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); + + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), + Arg.Is(o => o.Id == organization.Id), + Arg.Any>(), + Arg.Any>()); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id, response.OrganizationId); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateMembers_WithAdminAccess_CanAddSelfToGroup(Organization organization, Group group, + GroupRequestModel groupRequestModel, OrganizationUser savingUser, List currentGroupUsers, + SutProvider sutProvider) + { + // Not updating collections + groupRequestModel.Collections = []; + + Put_Setup(sutProvider, organization, true, group, savingUser, + currentCollectionAccess: [], currentGroupUsers); + + // Saving user is trying to add themselves to the group + var updatedUsers = groupRequestModel.Users.ToList(); + updatedUsers.Add(savingUser.Id); + groupRequestModel.Users = updatedUsers; + + var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); + + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), + Arg.Is(o => o.Id == organization.Id), + Arg.Any>(), + Arg.Is>(guids => guids.ToHashSet().SetEquals(groupRequestModel.Users.ToHashSet()))); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id, response.OrganizationId); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateMembers_NoAdminAccess_ProviderUser_Success(Organization organization, Group group, + GroupRequestModel groupRequestModel, List currentGroupUsers, SutProvider sutProvider) + { + // Make collection authorization pass, it's not being tested here + groupRequestModel.Collections = Array.Empty(); + + Put_Setup(sutProvider, organization, false, group, null, currentCollectionAccess: [], currentGroupUsers); + + var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); + + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), + Arg.Is(o => o.Id == organization.Id), + Arg.Any>(), + Arg.Any>()); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id, response.OrganizationId); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_DoesNotOverwriteUnauthorizedCollections(GroupRequestModel groupRequestModel, + Group group, Organization organization, + SutProvider sutProvider, OrganizationUser savingUser) + { + var editedCollectionId = CoreHelpers.GenerateComb(); + var readonlyCollectionId1 = CoreHelpers.GenerateComb(); + var readonlyCollectionId2 = CoreHelpers.GenerateComb(); + + var currentCollectionAccess = new List + { + new() + { + Id = editedCollectionId, + HidePasswords = true, + Manage = false, + ReadOnly = true + }, + new() + { + Id = readonlyCollectionId1, + HidePasswords = false, + Manage = true, + ReadOnly = false + }, + new() + { + Id = readonlyCollectionId2, + HidePasswords = false, + Manage = false, + ReadOnly = false + }, + }; + + Put_Setup(sutProvider, organization, false, group, savingUser, currentCollectionAccess, currentGroupUsers: []); + + // User is upgrading editedCollectionId to manage + groupRequestModel.Collections = new List + { + new() { Id = editedCollectionId, HidePasswords = false, Manage = true, ReadOnly = false } + }; + + // Authorize the editedCollection + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == editedCollectionId), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) + .Returns(AuthorizationResult.Success()); + + // Do not authorize the readonly collections + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) + .Returns(AuthorizationResult.Failed()); + + var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); + + // Expect all collection access (modified and unmodified) to be saved + await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); + await sutProvider.GetDependency().Received(1).UpdateGroupAsync( + Arg.Is(g => + g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), + Arg.Is(o => o.Id == organization.Id), + Arg.Is>(cas => + cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && + cas.First(c => c.Id == editedCollectionId).Manage == true && + cas.First(c => c.Id == editedCollectionId).ReadOnly == false && + cas.First(c => c.Id == editedCollectionId).HidePasswords == false), + Arg.Any>()); + Assert.Equal(groupRequestModel.Name, response.Name); + Assert.Equal(organization.Id, response.OrganizationId); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotUpdateCollections(GroupRequestModel groupRequestModel, + Group group, Organization organization, + SutProvider sutProvider, OrganizationUser savingUser) + { + // Group is currently assigned to the POSTed collections + Put_Setup(sutProvider, organization, false, group, savingUser, + groupRequestModel.Collections.Select(cas => cas.ToSelectionReadOnly()).ToList(), + []); + + var postedCollectionIds = groupRequestModel.Collections.Select(c => c.Id).ToHashSet(); + + // But the saving user does not have permission to update them + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => postedCollectionIds.Contains(c.Id)), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) + .Returns(AuthorizationResult.Failed()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel)); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotAddCollections(GroupRequestModel groupRequestModel, + Group group, Organization organization, + SutProvider sutProvider, OrganizationUser savingUser) + { + // Group is not assigned to the POSTed collections + Put_Setup(sutProvider, organization, false, group, savingUser, [], []); + + var postedCollectionIds = groupRequestModel.Collections.Select(c => c.Id).ToHashSet(); + + // But the saving user does not have permission to update them + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => postedCollectionIds.Contains(c.Id)), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) + .Returns(AuthorizationResult.Failed()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel)); + } + + private void Put_Setup(SutProvider sutProvider, Organization organization, + bool adminAccess, Group group, OrganizationUser? savingUser, List currentCollectionAccess, + List currentGroupUsers) + { + // FCv1 is now fully enabled + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); + + var orgId = organization.Id = group.OrganizationId; + + // Arrange org and orgAbility + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency().GetOrganizationAbilityAsync(orgId) + .Returns(new OrganizationAbility + { + Id = organization.Id, + AllowAdminAccessToAllCollectionItems = adminAccess + }); + + // Arrange user + // If no savingUser provided, they're not an org user, just return a random guid + sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(savingUser?.UserId ?? CoreHelpers.GenerateComb()); + sutProvider.GetDependency().ManageGroups(orgId).Returns(true); + + // Arrange repositories + sutProvider.GetDependency().GetManyUserIdsByIdAsync(group.Id).Returns(currentGroupUsers ?? []); + sutProvider.GetDependency().GetByIdWithCollectionsAsync(group.Id) + .Returns(new Tuple>(group, currentCollectionAccess ?? [])); + if (savingUser != null) + { + sutProvider.GetDependency().GetByOrganizationAsync(orgId, savingUser.UserId.Value) + .Returns(savingUser); + } + + // Collection repository: return mock Collection objects for any ids passed in + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>().Select(guid => new Collection { Id = guid }).ToList()); + } +} diff --git a/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs index 6f077fbd3..2885c3318 100644 --- a/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs @@ -1,12 +1,9 @@ using System.Security.Claims; using Bit.Api.AdminConsole.Controllers; using Bit.Api.AdminConsole.Models.Request; -using Bit.Api.Models.Request; using Bit.Api.Vault.AuthorizationHandlers.Collections; -using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Exceptions; @@ -14,7 +11,6 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations; using Bit.Core.Repositories; using Bit.Core.Services; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Authorization; @@ -27,35 +23,14 @@ namespace Bit.Api.Test.AdminConsole.Controllers; [SutProviderCustomize] public class GroupsControllerTests { - [Theory] - [BitAutoData] - public async Task Post_PreFCv1_Success(Organization organization, GroupRequestModel groupRequestModel, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); - - var response = await sutProvider.Sut.Post(organization.Id, groupRequestModel); - - await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); - await sutProvider.GetDependency().Received(1).CreateGroupAsync( - Arg.Is(g => - g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), - organization, - Arg.Any>(), - Arg.Any>()); - Assert.Equal(groupRequestModel.Name, response.Name); - Assert.Equal(organization.Id, response.OrganizationId); - } - [Theory] [BitAutoData] public async Task Post_AuthorizedToGiveAccessToCollections_Success(Organization organization, GroupRequestModel groupRequestModel, SutProvider sutProvider) { - // Enable FC and v1 + // Enable FC sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( new OrganizationAbility { Id = organization.Id, AllowAdminAccessToAllCollectionItems = false }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), @@ -96,10 +71,9 @@ public class GroupsControllerTests [BitAutoData] public async Task Post_NotAuthorizedToGiveAccessToCollections_Throws(Organization organization, GroupRequestModel groupRequestModel, SutProvider sutProvider) { - // Enable FC and v1 + // Enable FC sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( new OrganizationAbility { Id = organization.Id, AllowAdminAccessToAllCollectionItems = false }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); @@ -111,321 +85,9 @@ public class GroupsControllerTests Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) .Returns(AuthorizationResult.Failed()); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.Post(organization.Id, groupRequestModel)); - - Assert.Contains("You are not authorized to grant access to these collections.", exception.Message); + await Assert.ThrowsAsync(() => sutProvider.Sut.Post(organization.Id, groupRequestModel)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateGroupAsync(default, default, default, default); } - - [Theory] - [BitAutoData] - public async Task Put_AdminsCanAccessAllCollections_Success(Organization organization, Group group, - GroupRequestModel groupRequestModel, List existingCollectionAccess, - SutProvider sutProvider) - { - group.OrganizationId = organization.Id; - - // Enable FC and v1, set Collection Management Setting - sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( - new OrganizationAbility { Id = organization.Id, AllowAdminAccessToAllCollectionItems = true }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, existingCollectionAccess)); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(existingCollectionAccess.Select(c => c.Id)) - .Returns(existingCollectionAccess.Select(c => new Collection { Id = c.Id }).ToList()); - sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); - - var requestModelCollectionIds = groupRequestModel.Collections.Select(c => c.Id).ToHashSet(); - - var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); - - await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); - await sutProvider.GetDependency().Received(1).UpdateGroupAsync( - Arg.Is(g => - g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), - Arg.Is(o => o.Id == organization.Id), - // Should overwrite any existing collections - Arg.Is>(access => - access.All(c => requestModelCollectionIds.Contains(c.Id))), - Arg.Any>()); - Assert.Equal(groupRequestModel.Name, response.Name); - Assert.Equal(organization.Id, response.OrganizationId); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateMembers_AdminsCannotAccessAllCollections_CannotAddSelfToGroup(Organization organization, Group group, - GroupRequestModel groupRequestModel, OrganizationUser savingOrganizationUser, List currentGroupUsers, - SutProvider sutProvider) - { - group.OrganizationId = organization.Id; - - // Enable FC and v1 - sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( - new OrganizationAbility - { - Id = organization.Id, - AllowAdminAccessToAllCollectionItems = false, - }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - // Saving user is trying to add themselves to the group - var updatedUsers = groupRequestModel.Users.ToList(); - updatedUsers.Add(savingOrganizationUser.Id); - groupRequestModel.Users = updatedUsers; - - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, new List())); - sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); - sutProvider.GetDependency() - .GetByOrganizationAsync(organization.Id, Arg.Any()) - .Returns(savingOrganizationUser); - sutProvider.GetDependency().GetProperUserId(Arg.Any()) - .Returns(savingOrganizationUser.UserId); - sutProvider.GetDependency().GetManyUserIdsByIdAsync(group.Id) - .Returns(currentGroupUsers); - - var exception = await - Assert.ThrowsAsync(() => sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel)); - - Assert.Contains("You cannot add yourself to groups", exception.Message); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateMembers_AdminsCannotAccessAllCollections_AlreadyInGroup_Success(Organization organization, Group group, - GroupRequestModel groupRequestModel, OrganizationUser savingOrganizationUser, List currentGroupUsers, - SutProvider sutProvider) - { - group.OrganizationId = organization.Id; - - // Enable FC and v1 - sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( - new OrganizationAbility - { - Id = organization.Id, - AllowAdminAccessToAllCollectionItems = false, - }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - // Saving user is trying to add themselves to the group - var updatedUsers = groupRequestModel.Users.ToList(); - updatedUsers.Add(savingOrganizationUser.Id); - groupRequestModel.Users = updatedUsers; - - // But! they are already a member of the group - currentGroupUsers.Add(savingOrganizationUser.Id); - - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, new List())); - sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); - sutProvider.GetDependency() - .GetByOrganizationAsync(organization.Id, Arg.Any()) - .Returns(savingOrganizationUser); - sutProvider.GetDependency().GetProperUserId(Arg.Any()) - .Returns(savingOrganizationUser.UserId); - sutProvider.GetDependency().GetManyUserIdsByIdAsync(group.Id) - .Returns(currentGroupUsers); - - // Make collection authorization pass, it's not being tested here - groupRequestModel.Collections = Array.Empty(); - - var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); - - await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); - await sutProvider.GetDependency().Received(1).UpdateGroupAsync( - Arg.Is(g => - g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), - Arg.Is(o => o.Id == organization.Id), - Arg.Any>(), - Arg.Any>()); - Assert.Equal(groupRequestModel.Name, response.Name); - Assert.Equal(organization.Id, response.OrganizationId); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateMembers_AdminsCannotAccessAllCollections_ProviderUser_Success(Organization organization, Group group, - GroupRequestModel groupRequestModel, List currentGroupUsers, Guid savingUserId, - SutProvider sutProvider) - { - group.OrganizationId = organization.Id; - - // Enable FC and v1 - sutProvider.GetDependency().GetOrganizationAbilityAsync(organization.Id).Returns( - new OrganizationAbility - { - Id = organization.Id, - AllowAdminAccessToAllCollectionItems = false, - }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, new List())); - sutProvider.GetDependency().ManageGroups(organization.Id).Returns(true); - sutProvider.GetDependency() - .GetByOrganizationAsync(organization.Id, Arg.Any()) - .Returns((OrganizationUser)null); // Provider is not an OrganizationUser, so it will always return null - sutProvider.GetDependency().GetProperUserId(Arg.Any()) - .Returns(savingUserId); - sutProvider.GetDependency().GetManyUserIdsByIdAsync(group.Id) - .Returns(currentGroupUsers); - - // Make collection authorization pass, it's not being tested here - groupRequestModel.Collections = Array.Empty(); - - var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); - - await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); - await sutProvider.GetDependency().Received(1).UpdateGroupAsync( - Arg.Is(g => - g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), - Arg.Is(o => o.Id == organization.Id), - Arg.Any>(), - Arg.Any>()); - Assert.Equal(groupRequestModel.Name, response.Name); - Assert.Equal(organization.Id, response.OrganizationId); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateCollections_OnlyUpdatesCollectionsTheSavingUserCanUpdate(GroupRequestModel groupRequestModel, - Group group, Organization organization, - SutProvider sutProvider, Guid savingUserId) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - Put_Setup(sutProvider, organization, group, savingUserId); - - var editedCollectionId = CoreHelpers.GenerateComb(); - var readonlyCollectionId1 = CoreHelpers.GenerateComb(); - var readonlyCollectionId2 = CoreHelpers.GenerateComb(); - - var currentCollectionAccess = new List - { - new() - { - Id = editedCollectionId, - HidePasswords = true, - Manage = false, - ReadOnly = true - }, - new() - { - Id = readonlyCollectionId1, - HidePasswords = false, - Manage = true, - ReadOnly = false - }, - new() - { - Id = readonlyCollectionId2, - HidePasswords = false, - Manage = false, - ReadOnly = false - }, - }; - - // User is upgrading editedCollectionId to manage - groupRequestModel.Collections = new List - { - new() { Id = editedCollectionId, HidePasswords = false, Manage = true, ReadOnly = false } - }; - - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, - currentCollectionAccess)); - - var currentCollections = currentCollectionAccess - .Select(cas => new Collection { Id = cas.Id }).ToList(); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Any>()) - .Returns(currentCollections); - - // Authorize the editedCollection - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == editedCollectionId), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) - .Returns(AuthorizationResult.Success()); - - // Do not authorize the readonly collections - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) - .Returns(AuthorizationResult.Failed()); - - var response = await sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel); - - // Expect all collection access (modified and unmodified) to be saved - await sutProvider.GetDependency().Received(1).ManageGroups(organization.Id); - await sutProvider.GetDependency().Received(1).UpdateGroupAsync( - Arg.Is(g => - g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name), - Arg.Is(o => o.Id == organization.Id), - Arg.Is>(cas => - cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && - cas.First(c => c.Id == editedCollectionId).Manage == true && - cas.First(c => c.Id == editedCollectionId).ReadOnly == false && - cas.First(c => c.Id == editedCollectionId).HidePasswords == false), - Arg.Any>()); - Assert.Equal(groupRequestModel.Name, response.Name); - Assert.Equal(organization.Id, response.OrganizationId); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotUpdateCollections(GroupRequestModel groupRequestModel, - Group group, Organization organization, - SutProvider sutProvider, Guid savingUserId) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - Put_Setup(sutProvider, organization, group, savingUserId); - - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(group.Id) - .Returns(new Tuple>(group, - groupRequestModel.Collections.Select(cas => cas.ToSelectionReadOnly()).ToList())); - var collections = groupRequestModel.Collections.Select(cas => new Collection { Id = cas.Id }).ToList(); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Is>(guids => guids.SequenceEqual(collections.Select(c => c.Id)))) - .Returns(collections); - - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => collections.Contains(c)), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyGroupAccess))) - .Returns(AuthorizationResult.Failed()); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organization.Id, group.Id, groupRequestModel)); - Assert.Contains("You must have Can Manage permission", exception.Message); - } - - private void Put_Setup(SutProvider sutProvider, Organization organization, - Group group, Guid savingUserId) - { - var orgId = organization.Id = group.OrganizationId; - - sutProvider.GetDependency().ManageGroups(orgId).Returns(true); - sutProvider.GetDependency().GetOrganizationAbilityAsync(orgId) - .Returns(new OrganizationAbility - { - Id = organization.Id, - AllowAdminAccessToAllCollectionItems = false - }); - - sutProvider.GetDependency().GetManyUserIdsByIdAsync(group.Id).Returns(new List()); - sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(savingUserId); - sutProvider.GetDependency().GetByOrganizationAsync(orgId, savingUserId).Returns(new OrganizationUser - { - Id = savingUserId - }); - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - } } diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs new file mode 100644 index 000000000..0b9c18c57 --- /dev/null +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUserControllerPutTests.cs @@ -0,0 +1,284 @@ +using System.Security.Claims; +using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.Models.Request; +using Bit.Api.Vault.AuthorizationHandlers.Collections; +using Bit.Core; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.AdminConsole.Controllers; + +[ControllerCustomize(typeof(OrganizationUsersController))] +[SutProviderCustomize] +public class OrganizationUserControllerPutTests +{ + [Theory] + [BitAutoData] + public async Task Put_Success(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); + + // Authorize all changes for basic happy path test + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Any(), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) + .Returns(AuthorizationResult.Success()); + + // Save these for later - organizationUser object will be mutated + var orgUserId = organizationUser.Id; + var orgUserEmail = organizationUser.Email; + + await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + + 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), + savingUserId, + Arg.Is>(cas => + cas.All(c => model.Collections.Any(m => m.Id == c.Id))), + model.Groups); + } + + [Theory] + [BitAutoData] + public async Task Put_NoAdminAccess_CannotAddSelfToCollections(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + // Updating self + organizationUser.UserId = savingUserId; + organizationAbility.AllowAdminAccessToAllCollectionItems = false; + + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); + + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); + Assert.Contains("You cannot add yourself to a collection.", exception.Message); + } + [Theory] + [BitAutoData] + public async Task Put_NoAdminAccess_CannotAddSelfToGroups(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + // Updating self + organizationUser.UserId = savingUserId; + organizationAbility.AllowAdminAccessToAllCollectionItems = false; + + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); + + // Not changing any collection access + model.Collections = new List(); + + var orgUserId = organizationUser.Id; + var orgUserEmail = organizationUser.Email; + + await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + + 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), + savingUserId, + Arg.Is>(cas => + cas.All(c => model.Collections.Any(m => m.Id == c.Id))), + // Main assertion: groups are not updated (are null) + null); + } + + [Theory] + [BitAutoData] + public async Task Put_WithAdminAccess_CanAddSelfToGroups(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + // Updating self + organizationUser.UserId = savingUserId; + organizationAbility.AllowAdminAccessToAllCollectionItems = true; + + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); + + // Not changing any collection access + model.Collections = new List(); + + var orgUserId = organizationUser.Id; + var orgUserEmail = organizationUser.Email; + + await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + + 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), + savingUserId, + Arg.Is>(cas => + cas.All(c => model.Collections.Any(m => m.Id == c.Id))), + model.Groups); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_DoesNotOverwriteUnauthorizedCollections(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + var editedCollectionId = CoreHelpers.GenerateComb(); + var readonlyCollectionId1 = CoreHelpers.GenerateComb(); + var readonlyCollectionId2 = CoreHelpers.GenerateComb(); + + var currentCollectionAccess = new List + { + new() + { + Id = editedCollectionId, + HidePasswords = true, + Manage = false, + ReadOnly = true + }, + new() + { + Id = readonlyCollectionId1, + HidePasswords = false, + Manage = true, + ReadOnly = false + }, + new() + { + Id = readonlyCollectionId2, + HidePasswords = false, + Manage = false, + ReadOnly = false + }, + }; + + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess); + + // User is upgrading editedCollectionId to manage + model.Collections = new List + { + new() { Id = editedCollectionId, HidePasswords = false, Manage = true, ReadOnly = false } + }; + + // Save these for later - organizationUser object will be mutated + var orgUserId = organizationUser.Id; + var orgUserEmail = organizationUser.Email; + + // Authorize the editedCollection + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == editedCollectionId), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) + .Returns(AuthorizationResult.Success()); + + // Do not authorize the readonly collections + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) + .Returns(AuthorizationResult.Failed()); + + await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); + + // 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), + savingUserId, + Arg.Is>(cas => + cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && + cas.First(c => c.Id == editedCollectionId).Manage == true && + cas.First(c => c.Id == editedCollectionId).ReadOnly == false && + cas.First(c => c.Id == editedCollectionId).HidePasswords == false), + model.Groups); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotUpdateCollections(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + // Target user is currently assigned to the POSTed collections + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, + currentCollectionAccess: model.Collections.Select(cas => cas.ToSelectionReadOnly()).ToList()); + + var postedCollectionIds = model.Collections.Select(c => c.Id).ToHashSet(); + + // But the saving user does not have permission to update them + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => postedCollectionIds.Contains(c.Id)), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) + .Returns(AuthorizationResult.Failed()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); + } + + [Theory] + [BitAutoData] + public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotAddCollections(OrganizationUserUpdateRequestModel model, + OrganizationUser organizationUser, OrganizationAbility organizationAbility, + SutProvider sutProvider, Guid savingUserId) + { + // The target user is not currently assigned to any collections, so we're granting access for the first time + Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []); + + var postedCollectionIds = model.Collections.Select(c => c.Id).ToHashSet(); + // But the saving user does not have permission to assign access to the collections + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), Arg.Is(c => postedCollectionIds.Contains(c.Id)), + Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) + .Returns(AuthorizationResult.Failed()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); + } + + private void Put_Setup(SutProvider sutProvider, + OrganizationAbility organizationAbility, OrganizationUser organizationUser, Guid savingUserId, + List currentCollectionAccess) + { + // FCv1 is now fully enabled + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); + + var orgId = organizationAbility.Id = organizationUser.OrganizationId; + + sutProvider.GetDependency().ManageUsers(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationUser.Id) + .Returns(organizationUser); + sutProvider.GetDependency().GetOrganizationAbilityAsync(orgId) + .Returns(organizationAbility); + sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(savingUserId); + + // OrganizationUserRepository: return the user with current collection access + sutProvider.GetDependency() + .GetByIdWithCollectionsAsync(organizationUser.Id) + .Returns(new Tuple>(organizationUser, + currentCollectionAccess ?? [])); + + // Collection repository: return mock Collection objects for any ids passed in + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>().Select(guid => new Collection { Id = guid }).ToList()); + } +} diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs index f2898db2e..deb4c6818 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs @@ -1,13 +1,11 @@ using System.Security.Claims; using Bit.Api.AdminConsole.Controllers; using Bit.Api.AdminConsole.Models.Request.Organizations; -using Bit.Api.Models.Request; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; -using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Repositories; @@ -183,241 +181,7 @@ public class OrganizationUsersControllerTests .Returns(AuthorizationResult.Failed()); sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(userId); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.Invite(organizationAbility.Id, model)); - Assert.Contains("You are not authorized", exception.Message); - } - - [Theory] - [BitAutoData] - public async Task Put_Success(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(false); - - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, true); - - // Save these for later - organizationUser object will be mutated - var orgUserId = organizationUser.Id; - var orgUserEmail = organizationUser.Email; - - await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); - - 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), - savingUserId, - Arg.Is>(cas => - cas.All(c => model.Collections.Any(m => m.Id == c.Id))), - model.Groups); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateSelf_WithoutAllowAdminAccessToAllCollectionItems_CannotAddSelfToCollections(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - // Updating self - organizationUser.UserId = savingUserId; - organizationAbility.AllowAdminAccessToAllCollectionItems = false; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, false); - - // User is not currently assigned to any collections, which means they're adding themselves - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(organizationUser.Id) - .Returns(new Tuple>(organizationUser, - new List())); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Any>()) - .Returns(new List()); - - var orgUserId = organizationUser.Id; - var orgUserEmail = organizationUser.Email; - - var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); - Assert.Contains("You cannot add yourself to a collection.", exception.Message); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateSelf_WithoutAllowAdminAccessToAllCollectionItems_DoesNotUpdateGroups(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - // Updating self - organizationUser.UserId = savingUserId; - organizationAbility.AllowAdminAccessToAllCollectionItems = false; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, true); - - var orgUserId = organizationUser.Id; - var orgUserEmail = organizationUser.Email; - - await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); - - 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), - savingUserId, - Arg.Is>(cas => - cas.All(c => model.Collections.Any(m => m.Id == c.Id))), - null); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateSelf_WithAllowAdminAccessToAllCollectionItems_DoesUpdateGroups(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - // Updating self - organizationUser.UserId = savingUserId; - organizationAbility.AllowAdminAccessToAllCollectionItems = true; - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, true); - - var orgUserId = organizationUser.Id; - var orgUserEmail = organizationUser.Email; - - await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); - - 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), - savingUserId, - Arg.Is>(cas => - cas.All(c => model.Collections.Any(m => m.Id == c.Id))), - model.Groups); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateCollections_OnlyUpdatesCollectionsTheSavingUserCanUpdate(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, false); - - var editedCollectionId = CoreHelpers.GenerateComb(); - var readonlyCollectionId1 = CoreHelpers.GenerateComb(); - var readonlyCollectionId2 = CoreHelpers.GenerateComb(); - - var currentCollectionAccess = new List - { - new() - { - Id = editedCollectionId, - HidePasswords = true, - Manage = false, - ReadOnly = true - }, - new() - { - Id = readonlyCollectionId1, - HidePasswords = false, - Manage = true, - ReadOnly = false - }, - new() - { - Id = readonlyCollectionId2, - HidePasswords = false, - Manage = false, - ReadOnly = false - }, - }; - - // User is upgrading editedCollectionId to manage - model.Collections = new List - { - new() { Id = editedCollectionId, HidePasswords = false, Manage = true, ReadOnly = false } - }; - - // Save these for later - organizationUser object will be mutated - var orgUserId = organizationUser.Id; - var orgUserEmail = organizationUser.Email; - - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(organizationUser.Id) - .Returns(new Tuple>(organizationUser, - currentCollectionAccess)); - - var currentCollections = currentCollectionAccess - .Select(cas => new Collection { Id = cas.Id }).ToList(); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Any>()) - .Returns(currentCollections); - - // Authorize the editedCollection - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == editedCollectionId), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) - .Returns(AuthorizationResult.Success()); - - // Do not authorize the readonly collections - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) - .Returns(AuthorizationResult.Failed()); - - await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model); - - // 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), - savingUserId, - Arg.Is>(cas => - cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) && - cas.First(c => c.Id == editedCollectionId).Manage == true && - cas.First(c => c.Id == editedCollectionId).ReadOnly == false && - cas.First(c => c.Id == editedCollectionId).HidePasswords == false), - model.Groups); - } - - [Theory] - [BitAutoData] - public async Task Put_UpdateCollections_ThrowsIfSavingUserCannotUpdateCollections(OrganizationUserUpdateRequestModel model, - OrganizationUser organizationUser, OrganizationAbility organizationAbility, - SutProvider sutProvider, Guid savingUserId) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); - Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, model, false); - - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(organizationUser.Id) - .Returns(new Tuple>(organizationUser, - model.Collections.Select(cas => cas.ToSelectionReadOnly()).ToList())); - var collections = model.Collections.Select(cas => new Collection { Id = cas.Id }).ToList(); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Is>(guids => guids.SequenceEqual(collections.Select(c => c.Id)))) - .Returns(collections); - - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => collections.Contains(c)), - Arg.Is>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess))) - .Returns(AuthorizationResult.Failed()); - - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model)); - Assert.Contains("You must have Can Manage permission", exception.Message); + await Assert.ThrowsAsync(() => sutProvider.Sut.Invite(organizationAbility.Id, model)); } [Theory] @@ -536,36 +300,6 @@ public class OrganizationUsersControllerTests await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetAccountRecoveryDetails(organizationId, bulkRequestModel)); } - private void Put_Setup(SutProvider sutProvider, OrganizationAbility organizationAbility, - OrganizationUser organizationUser, Guid savingUserId, OrganizationUserUpdateRequestModel model, bool authorizeAll) - { - var orgId = organizationAbility.Id = organizationUser.OrganizationId; - - sutProvider.GetDependency().ManageUsers(orgId).Returns(true); - sutProvider.GetDependency().GetByIdAsync(organizationUser.Id).Returns(organizationUser); - sutProvider.GetDependency().GetOrganizationAbilityAsync(orgId) - .Returns(organizationAbility); - sutProvider.GetDependency().GetProperUserId(Arg.Any()).Returns(savingUserId); - - if (authorizeAll) - { - // Simple case: saving user can edit all collections, all collection access is replaced - sutProvider.GetDependency() - .GetByIdWithCollectionsAsync(organizationUser.Id) - .Returns(new Tuple>(organizationUser, - model.Collections.Select(cas => cas.ToSelectionReadOnly()).ToList())); - var collections = model.Collections.Select(cas => new Collection { Id = cas.Id }).ToList(); - sutProvider.GetDependency() - .GetManyByManyIdsAsync(Arg.Is>(guids => guids.SequenceEqual(collections.Select(c => c.Id)))) - .Returns(collections); - - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), Arg.Is(c => collections.Contains(c)), - Arg.Is>(r => r.Contains(BulkCollectionOperations.ModifyUserAccess))) - .Returns(AuthorizationResult.Success()); - } - } - private void Get_Setup(OrganizationAbility organizationAbility, ICollection organizationUsers, SutProvider sutProvider) diff --git a/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs b/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs index 99964c266..ce98f37c7 100644 --- a/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs @@ -23,9 +23,6 @@ public class GroupsControllerTests [BitAutoData] public async Task Post_Success(Organization organization, GroupCreateUpdateRequestModel groupRequestModel, SutProvider sutProvider) { - // Organization has migrated - organization.FlexibleCollections = true; - // Contains at least one can manage groupRequestModel.Collections.First().Manage = true; @@ -50,9 +47,6 @@ public class GroupsControllerTests [BitAutoData] public async Task Put_Success(Organization organization, Group group, GroupCreateUpdateRequestModel groupRequestModel, SutProvider sutProvider) { - // Organization has migrated - organization.FlexibleCollections = true; - // Contains at least one can manage groupRequestModel.Collections.First().Manage = true; diff --git a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs index d1911a0dc..a16a9cb55 100644 --- a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs @@ -12,6 +12,7 @@ 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.UserKey; using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces; using Bit.Core.Billing.Services; @@ -44,6 +45,7 @@ public class AccountsControllerTests : IDisposable private readonly IPolicyService _policyService; private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand; private readonly IRotateUserKeyCommand _rotateUserKeyCommand; + private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand; private readonly IFeatureService _featureService; private readonly ISubscriberService _subscriberService; private readonly IReferenceEventService _referenceEventService; @@ -72,6 +74,7 @@ public class AccountsControllerTests : IDisposable _policyService = Substitute.For(); _setInitialMasterPasswordCommand = Substitute.For(); _rotateUserKeyCommand = Substitute.For(); + _tdeOffboardingPasswordCommand = Substitute.For(); _featureService = Substitute.For(); _subscriberService = Substitute.For(); _referenceEventService = Substitute.For(); @@ -97,6 +100,7 @@ public class AccountsControllerTests : IDisposable _userService, _policyService, _setInitialMasterPasswordCommand, + _tdeOffboardingPasswordCommand, _rotateUserKeyCommand, _featureService, _subscriberService, diff --git a/test/Api.Test/Auth/Models/Response/OrganizationTwoFactorDuoResponseModelTests.cs b/test/Api.Test/Auth/Models/Response/OrganizationTwoFactorDuoResponseModelTests.cs index 7cf0ea1b1..dea76b2cd 100644 --- a/test/Api.Test/Auth/Models/Response/OrganizationTwoFactorDuoResponseModelTests.cs +++ b/test/Api.Test/Auth/Models/Response/OrganizationTwoFactorDuoResponseModelTests.cs @@ -21,9 +21,9 @@ public class OrganizationTwoFactorDuoResponseModelTests // Assert if v4 data Ikey and Skey are set to clientId and clientSecret Assert.NotNull(model); Assert.Equal("clientId", model.ClientId); - Assert.Equal("clientSecret", model.ClientSecret); + Assert.Equal("secret************", model.ClientSecret); Assert.Equal("clientId", model.IntegrationKey); - Assert.Equal("clientSecret", model.SecretKey); + Assert.Equal("secret************", model.SecretKey); } [Theory] @@ -57,9 +57,9 @@ public class OrganizationTwoFactorDuoResponseModelTests /// Assert Even if both versions are present priority is given to v4 data Assert.NotNull(model); Assert.Equal("clientId", model.ClientId); - Assert.Equal("clientSecret", model.ClientSecret); + Assert.Equal("secret************", model.ClientSecret); Assert.Equal("clientId", model.IntegrationKey); - Assert.Equal("clientSecret", model.SecretKey); + Assert.Equal("secret************", model.SecretKey); } [Theory] @@ -92,12 +92,14 @@ public class OrganizationTwoFactorDuoResponseModelTests private string GetTwoFactorOrganizationDuoProvidersJson() { - return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; + return + "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; } private string GetTwoFactorOrganizationDuoV4ProvidersJson() { - return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; + return + "{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; } private string GetTwoFactorOrganizationDuoV2ProvidersJson() diff --git a/test/Api.Test/Auth/Models/Response/UserTwoFactorDuoResponseModelTests.cs b/test/Api.Test/Auth/Models/Response/UserTwoFactorDuoResponseModelTests.cs index c236ac2ff..cb46273a6 100644 --- a/test/Api.Test/Auth/Models/Response/UserTwoFactorDuoResponseModelTests.cs +++ b/test/Api.Test/Auth/Models/Response/UserTwoFactorDuoResponseModelTests.cs @@ -21,9 +21,9 @@ public class UserTwoFactorDuoResponseModelTests // Assert if v4 data Ikey and Skey are set to clientId and clientSecret Assert.NotNull(model); Assert.Equal("clientId", model.ClientId); - Assert.Equal("clientSecret", model.ClientSecret); + Assert.Equal("secret************", model.ClientSecret); Assert.Equal("clientId", model.IntegrationKey); - Assert.Equal("clientSecret", model.SecretKey); + Assert.Equal("secret************", model.SecretKey); } [Theory] @@ -57,9 +57,9 @@ public class UserTwoFactorDuoResponseModelTests // Assert Even if both versions are present priority is given to v4 data Assert.NotNull(model); Assert.Equal("clientId", model.ClientId); - Assert.Equal("clientSecret", model.ClientSecret); + Assert.Equal("secret************", model.ClientSecret); Assert.Equal("clientId", model.IntegrationKey); - Assert.Equal("clientSecret", model.SecretKey); + Assert.Equal("secret************", model.SecretKey); } [Theory] @@ -92,12 +92,14 @@ public class UserTwoFactorDuoResponseModelTests private string GetTwoFactorDuoProvidersJson() { - return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; + return + "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; } private string GetTwoFactorDuoV4ProvidersJson() { - return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; + return + "{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}"; } private string GetTwoFactorDuoV2ProvidersJson() diff --git a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs index 021705bed..7b8b00462 100644 --- a/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/OrganizationBillingControllerTests.cs @@ -1,7 +1,11 @@ using Bit.Api.Billing.Controllers; using Bit.Api.Billing.Models.Responses; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Models; using Bit.Core.Billing.Services; +using Bit.Core.Context; +using Bit.Core.Repositories; +using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Http.HttpResults; @@ -14,11 +18,26 @@ namespace Bit.Api.Test.Billing.Controllers; [SutProviderCustomize] public class OrganizationBillingControllerTests { + [Theory, BitAutoData] + public async Task GetMetadataAsync_Unauthorized_ReturnsUnauthorized( + Guid organizationId, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessMembersTab(organizationId).Returns(false); + + var result = await sutProvider.Sut.GetMetadataAsync(organizationId); + + Assert.IsType(result); + } + [Theory, BitAutoData] public async Task GetMetadataAsync_MetadataNull_NotFound( Guid organizationId, SutProvider sutProvider) { + sutProvider.GetDependency().AccessMembersTab(organizationId).Returns(true); + sutProvider.GetDependency().GetMetadata(organizationId).Returns((OrganizationMetadataDTO)null); + var result = await sutProvider.Sut.GetMetadataAsync(organizationId); Assert.IsType(result); @@ -29,6 +48,7 @@ public class OrganizationBillingControllerTests Guid organizationId, SutProvider sutProvider) { + sutProvider.GetDependency().AccessMembersTab(organizationId).Returns(true); sutProvider.GetDependency().GetMetadata(organizationId) .Returns(new OrganizationMetadataDTO(true)); @@ -40,4 +60,53 @@ public class OrganizationBillingControllerTests Assert.True(organizationMetadataResponse.IsOnSecretsManagerStandalone); } + + [Theory, BitAutoData] + public async Task GetHistoryAsync_Unauthorized_ReturnsUnauthorized( + Guid organizationId, + SutProvider sutProvider) + { + sutProvider.GetDependency().ViewBillingHistory(organizationId).Returns(false); + + var result = await sutProvider.Sut.GetHistoryAsync(organizationId); + + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task GetHistoryAsync_OrganizationNotFound_ReturnsNotFound( + Guid organizationId, + SutProvider sutProvider) + { + sutProvider.GetDependency().ViewBillingHistory(organizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationId).Returns((Organization)null); + + var result = await sutProvider.Sut.GetHistoryAsync(organizationId); + + Assert.IsType(result); + } + + [Theory] + [BitAutoData] + public async Task GetHistoryAsync_OK( + Guid organizationId, + Organization organization, + SutProvider sutProvider) + { + // Arrange + sutProvider.GetDependency().ViewBillingHistory(organizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationId).Returns(organization); + + // Manually create a BillingHistoryInfo object to avoid requiring AutoFixture to create HttpResponseHeaders + var billingInfo = new BillingHistoryInfo(); + + sutProvider.GetDependency().GetBillingHistoryAsync(organization).Returns(billingInfo); + + // Act + var result = await sutProvider.Sut.GetHistoryAsync(organizationId); + + // Assert + var okResult = Assert.IsType>(result); + Assert.Equal(billingInfo, okResult.Value); + } } diff --git a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs index acd6721a5..03f486342 100644 --- a/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/ProviderBillingControllerTests.cs @@ -6,15 +6,19 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Billing.Constants; +using Bit.Core.Billing.Entities; using Bit.Core.Billing.Enums; using Bit.Core.Billing.Models; +using Bit.Core.Billing.Repositories; using Bit.Core.Billing.Services; using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Models.Api; +using Bit.Core.Models.BitStripe; using Bit.Core.Services; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using NSubstitute; using NSubstitute.ReturnsExtensions; @@ -29,7 +33,74 @@ namespace Bit.Api.Test.Billing.Controllers; [SutProviderCustomize] public class ProviderBillingControllerTests { - #region GetInvoicesAsync + #region GetInvoicesAsync & TryGetBillableProviderForAdminOperations + + [Theory, BitAutoData] + public async Task GetInvoicesAsync_FFDisabled_NotFound( + Guid providerId, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) + .Returns(false); + + var result = await sutProvider.Sut.GetInvoicesAsync(providerId); + + AssertNotFound(result); + } + + [Theory, BitAutoData] + public async Task GetInvoicesAsync_NullProvider_NotFound( + Guid providerId, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) + .Returns(true); + + sutProvider.GetDependency().GetByIdAsync(providerId).ReturnsNull(); + + var result = await sutProvider.Sut.GetInvoicesAsync(providerId); + + AssertNotFound(result); + } + + [Theory, BitAutoData] + public async Task GetInvoicesAsync_NotProviderUser_Unauthorized( + Provider provider, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) + .Returns(true); + + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + + sutProvider.GetDependency().ProviderProviderAdmin(provider.Id) + .Returns(false); + + var result = await sutProvider.Sut.GetInvoicesAsync(provider.Id); + + AssertUnauthorized(result); + } + + [Theory, BitAutoData] + public async Task GetInvoicesAsync_ProviderNotBillable_Unauthorized( + Provider provider, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) + .Returns(true); + + provider.Type = ProviderType.Reseller; + provider.Status = ProviderStatusType.Created; + + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + + sutProvider.GetDependency().ProviderProviderAdmin(provider.Id) + .Returns(true); + + var result = await sutProvider.Sut.GetInvoicesAsync(provider.Id); + + AssertUnauthorized(result); + } [Theory, BitAutoData] public async Task GetInvoices_Ok( @@ -73,7 +144,9 @@ public class ProviderBillingControllerTests } }; - sutProvider.GetDependency().GetInvoices(provider).Returns(invoices); + sutProvider.GetDependency().InvoiceListAsync(Arg.Is( + options => + options.Customer == provider.GatewayCustomerId)).Returns(invoices); var result = await sutProvider.Sut.GetInvoicesAsync(provider.Id); @@ -108,6 +181,27 @@ public class ProviderBillingControllerTests #region GenerateClientInvoiceReportAsync + [Theory, BitAutoData] + public async Task GenerateClientInvoiceReportAsync_NullReportContent_ServerError( + Provider provider, + string invoiceId, + SutProvider sutProvider) + { + ConfigureStableAdminInputs(provider, sutProvider); + + sutProvider.GetDependency().GenerateClientInvoiceReport(invoiceId) + .ReturnsNull(); + + var result = await sutProvider.Sut.GenerateClientInvoiceReportAsync(provider.Id, invoiceId); + + Assert.IsType>(result); + + var response = (JsonHttpResult)result; + + Assert.Equal(StatusCodes.Status500InternalServerError, response.StatusCode); + Assert.Equal("We had a problem generating your invoice CSV. Please contact support.", response.Value.Message); + } + [Theory, BitAutoData] public async Task GenerateClientInvoiceReportAsync_Ok( Provider provider, @@ -133,158 +227,6 @@ public class ProviderBillingControllerTests #endregion - #region GetPaymentInformationAsync & TryGetBillableProviderForAdminOperation - - [Theory, BitAutoData] - public async Task GetPaymentInformationAsync_FFDisabled_NotFound( - Guid providerId, - SutProvider sutProvider) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) - .Returns(false); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(providerId); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentInformationAsync_NullProvider_NotFound( - Guid providerId, - SutProvider sutProvider) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) - .Returns(true); - - sutProvider.GetDependency().GetByIdAsync(providerId).ReturnsNull(); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(providerId); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentInformationAsync_NotProviderUser_Unauthorized( - Provider provider, - SutProvider sutProvider) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) - .Returns(true); - - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - - sutProvider.GetDependency().ProviderProviderAdmin(provider.Id) - .Returns(false); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentInformationAsync_ProviderNotBillable_Unauthorized( - Provider provider, - SutProvider sutProvider) - { - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.EnableConsolidatedBilling) - .Returns(true); - - provider.Type = ProviderType.Reseller; - provider.Status = ProviderStatusType.Created; - - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - - sutProvider.GetDependency().ProviderProviderAdmin(provider.Id) - .Returns(true); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentInformation_PaymentInformationNull_NotFound( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - sutProvider.GetDependency().GetPaymentInformation(provider).ReturnsNull(); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentInformation_Ok( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - var maskedPaymentMethod = new MaskedPaymentMethodDTO(PaymentMethodType.Card, "VISA *1234", false); - - var taxInformation = - new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY"); - - sutProvider.GetDependency().GetPaymentInformation(provider).Returns(new PaymentInformationDTO( - 100, - maskedPaymentMethod, - taxInformation)); - - var result = await sutProvider.Sut.GetPaymentInformationAsync(provider.Id); - - Assert.IsType>(result); - - var response = ((Ok)result).Value; - - Assert.Equal(100, response.AccountCredit); - Assert.Equal(maskedPaymentMethod.Description, response.PaymentMethod.Description); - Assert.Equal(taxInformation.TaxId, response.TaxInformation.TaxId); - } - - #endregion - - #region GetPaymentMethodAsync - - [Theory, BitAutoData] - public async Task GetPaymentMethod_PaymentMethodNull_NotFound( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - sutProvider.GetDependency().GetPaymentMethod(provider).ReturnsNull(); - - var result = await sutProvider.Sut.GetPaymentMethodAsync(provider.Id); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetPaymentMethod_Ok( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - sutProvider.GetDependency().GetPaymentMethod(provider).Returns(new MaskedPaymentMethodDTO( - PaymentMethodType.Card, "Description", false)); - - var result = await sutProvider.Sut.GetPaymentMethodAsync(provider.Id); - - Assert.IsType>(result); - - var response = ((Ok)result).Value; - - Assert.Equal(PaymentMethodType.Card, response.Type); - Assert.Equal("Description", response.Description); - Assert.False(response.NeedsVerification); - } - - #endregion - #region GetSubscriptionAsync & TryGetBillableProviderForServiceUserOperation [Theory, BitAutoData] @@ -297,7 +239,7 @@ public class ProviderBillingControllerTests var result = await sutProvider.Sut.GetSubscriptionAsync(providerId); - Assert.IsType(result); + AssertNotFound(result); } [Theory, BitAutoData] @@ -312,7 +254,7 @@ public class ProviderBillingControllerTests var result = await sutProvider.Sut.GetSubscriptionAsync(providerId); - Assert.IsType(result); + AssertNotFound(result); } [Theory, BitAutoData] @@ -330,7 +272,7 @@ public class ProviderBillingControllerTests var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id); - Assert.IsType(result); + AssertUnauthorized(result); } [Theory, BitAutoData] @@ -351,21 +293,7 @@ public class ProviderBillingControllerTests var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id); - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetSubscriptionAsync_NullConsolidatedBillingSubscription_NotFound( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableServiceUserInputs(provider, sutProvider); - - sutProvider.GetDependency().GetConsolidatedBillingSubscription(provider).ReturnsNull(); - - var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id); - - Assert.IsType(result); + AssertUnauthorized(result); } [Theory, BitAutoData] @@ -375,51 +303,83 @@ public class ProviderBillingControllerTests { ConfigureStableServiceUserInputs(provider, sutProvider); - var configuredProviderPlans = new List - { - new (Guid.NewGuid(), provider.Id, PlanType.TeamsMonthly, 50, 10, 30), - new (Guid.NewGuid(), provider.Id , PlanType.EnterpriseMonthly, 100, 0, 90) - }; + var stripeAdapter = sutProvider.GetDependency(); + + var (thisYear, thisMonth, _) = DateTime.UtcNow; + var daysInThisMonth = DateTime.DaysInMonth(thisYear, thisMonth); var subscription = new Subscription { - Status = "unpaid", - CurrentPeriodEnd = new DateTime(2024, 6, 30), + CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically, + CurrentPeriodEnd = new DateTime(thisYear, thisMonth, daysInThisMonth), Customer = new Customer { - Balance = 100000, - Discount = new Discount + Address = new Address { - Coupon = new Coupon - { - PercentOff = 10 - } - } + Country = "US", + PostalCode = "12345", + Line1 = "123 Example St.", + Line2 = "Unit 1", + City = "Example Town", + State = "NY" + }, + Balance = -100000, + Discount = new Discount { Coupon = new Coupon { PercentOff = 10 } }, + TaxIds = new StripeList { Data = [new TaxId { Value = "123456789" }] } + }, + Status = "unpaid", + }; + + stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId, Arg.Is( + options => + options.Expand.Contains("customer.tax_ids") && + options.Expand.Contains("test_clock"))).Returns(subscription); + + var lastMonth = thisMonth - 1; + var daysInLastMonth = DateTime.DaysInMonth(thisYear, lastMonth); + + var overdueInvoice = new Invoice + { + Id = "invoice_id", + Status = "open", + Created = new DateTime(thisYear, lastMonth, 1), + PeriodEnd = new DateTime(thisYear, lastMonth, daysInLastMonth), + Attempted = true + }; + + stripeAdapter.InvoiceSearchAsync(Arg.Is( + options => options.Query == $"subscription:'{subscription.Id}' status:'open'")) + .Returns([overdueInvoice]); + + var providerPlans = new List + { + new () + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.TeamsMonthly, + SeatMinimum = 50, + PurchasedSeats = 10, + AllocatedSeats = 60 + }, + new () + { + Id = Guid.NewGuid(), + ProviderId = provider.Id, + PlanType = PlanType.EnterpriseMonthly, + SeatMinimum = 100, + PurchasedSeats = 0, + AllocatedSeats = 90 } }; - var taxInformation = - new TaxInformationDTO("US", "12345", "123456789", "123 Example St.", null, "Example Town", "NY"); - - var suspension = new SubscriptionSuspensionDTO( - new DateTime(2024, 7, 30), - new DateTime(2024, 5, 30), - 30); - - var consolidatedBillingSubscription = new ConsolidatedBillingSubscriptionDTO( - configuredProviderPlans, - subscription, - taxInformation, - suspension); - - sutProvider.GetDependency().GetConsolidatedBillingSubscription(provider) - .Returns(consolidatedBillingSubscription); + sutProvider.GetDependency().GetByProviderId(provider.Id).Returns(providerPlans); var result = await sutProvider.Sut.GetSubscriptionAsync(provider.Id); - Assert.IsType>(result); + Assert.IsType>(result); - var response = ((Ok)result).Value; + var response = ((Ok)result).Value; Assert.Equal(subscription.Status, response.Status); Assert.Equal(subscription.CurrentPeriodEnd, response.CurrentPeriodEndDate); @@ -431,7 +391,7 @@ public class ProviderBillingControllerTests Assert.NotNull(providerTeamsPlan); Assert.Equal(50, providerTeamsPlan.SeatMinimum); Assert.Equal(10, providerTeamsPlan.PurchasedSeats); - Assert.Equal(30, providerTeamsPlan.AssignedSeats); + Assert.Equal(60, providerTeamsPlan.AssignedSeats); Assert.Equal(60 * teamsPlan.PasswordManager.ProviderPortalSeatPrice, providerTeamsPlan.Cost); Assert.Equal("Monthly", providerTeamsPlan.Cadence); @@ -444,88 +404,47 @@ public class ProviderBillingControllerTests Assert.Equal(100 * enterprisePlan.PasswordManager.ProviderPortalSeatPrice, providerEnterprisePlan.Cost); Assert.Equal("Monthly", providerEnterprisePlan.Cadence); - Assert.Equal(100000, response.AccountCredit); - Assert.Equal(taxInformation, response.TaxInformation); + Assert.Equal(1000.00M, response.AccountCredit); + + var customer = subscription.Customer; + Assert.Equal(customer.Address.Country, response.TaxInformation.Country); + Assert.Equal(customer.Address.PostalCode, response.TaxInformation.PostalCode); + Assert.Equal(customer.TaxIds.First().Value, response.TaxInformation.TaxId); + Assert.Equal(customer.Address.Line1, response.TaxInformation.Line1); + Assert.Equal(customer.Address.Line2, response.TaxInformation.Line2); + Assert.Equal(customer.Address.City, response.TaxInformation.City); + Assert.Equal(customer.Address.State, response.TaxInformation.State); + Assert.Null(response.CancelAt); - Assert.Equal(suspension, response.Suspension); - } - #endregion - - #region GetTaxInformationAsync - - [Theory, BitAutoData] - public async Task GetTaxInformation_TaxInformationNull_NotFound( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - sutProvider.GetDependency().GetTaxInformation(provider).ReturnsNull(); - - var result = await sutProvider.Sut.GetTaxInformationAsync(provider.Id); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task GetTaxInformation_Ok( - Provider provider, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - sutProvider.GetDependency().GetTaxInformation(provider).Returns(new TaxInformationDTO( - "US", - "12345", - "123456789", - "123 Example St.", - null, - "Example Town", - "NY")); - - var result = await sutProvider.Sut.GetTaxInformationAsync(provider.Id); - - Assert.IsType>(result); - - var response = ((Ok)result).Value; - - Assert.Equal("US", response.Country); - Assert.Equal("12345", response.PostalCode); - Assert.Equal("123456789", response.TaxId); - Assert.Equal("123 Example St.", response.Line1); - Assert.Null(response.Line2); - Assert.Equal("Example Town", response.City); - Assert.Equal("NY", response.State); - } - - #endregion - - #region UpdatePaymentMethodAsync - - [Theory, BitAutoData] - public async Task UpdatePaymentMethod_Ok( - Provider provider, - TokenizedPaymentMethodRequestBody requestBody, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - await sutProvider.Sut.UpdatePaymentMethodAsync(provider.Id, requestBody); - - await sutProvider.GetDependency().Received(1).UpdatePaymentMethod( - provider, Arg.Is( - options => options.Type == requestBody.Type && options.Token == requestBody.Token)); - - await sutProvider.GetDependency().Received(1).SubscriptionUpdateAsync( - provider.GatewaySubscriptionId, Arg.Is( - options => options.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically)); + Assert.Equal(overdueInvoice.Created.AddDays(14), response.Suspension.SuspensionDate); + Assert.Equal(overdueInvoice.PeriodEnd, response.Suspension.UnpaidPeriodEndDate); + Assert.Equal(14, response.Suspension.GracePeriod); } #endregion #region UpdateTaxInformationAsync + [Theory, BitAutoData] + public async Task UpdateTaxInformation_NoCountry_BadRequest( + Provider provider, + TaxInformationRequestBody requestBody, + SutProvider sutProvider) + { + ConfigureStableAdminInputs(provider, sutProvider); + + requestBody.Country = null; + + var result = await sutProvider.Sut.UpdateTaxInformationAsync(provider.Id, requestBody); + + Assert.IsType>(result); + + var response = (BadRequest)result; + + Assert.Equal("Country and postal code are required to update your tax information.", response.Value.Message); + } + [Theory, BitAutoData] public async Task UpdateTaxInformation_Ok( Provider provider, @@ -537,7 +456,7 @@ public class ProviderBillingControllerTests await sutProvider.Sut.UpdateTaxInformationAsync(provider.Id, requestBody); await sutProvider.GetDependency().Received(1).UpdateTaxInformation( - provider, Arg.Is( + provider, Arg.Is( options => options.Country == requestBody.Country && options.PostalCode == requestBody.PostalCode && @@ -549,25 +468,4 @@ public class ProviderBillingControllerTests } #endregion - - #region VerifyBankAccount - - [Theory, BitAutoData] - public async Task VerifyBankAccount_Ok( - Provider provider, - VerifyBankAccountRequestBody requestBody, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - var result = await sutProvider.Sut.VerifyBankAccountAsync(provider.Id, requestBody); - - Assert.IsType(result); - - await sutProvider.GetDependency().Received(1).VerifyBankAccount( - provider, - (requestBody.Amount1, requestBody.Amount2)); - } - - #endregion } diff --git a/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs b/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs index 92d03f1e9..d0a79e15c 100644 --- a/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs +++ b/test/Api.Test/Billing/Controllers/ProviderClientsControllerTests.cs @@ -39,38 +39,7 @@ public class ProviderClientsControllerTests var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task CreateAsync_MissingClientOrganization_ServerError( - Provider provider, - CreateClientOrganizationRequestBody requestBody, - SutProvider sutProvider) - { - ConfigureStableAdminInputs(provider, sutProvider); - - var user = new User(); - - sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); - - var clientOrganizationId = Guid.NewGuid(); - - sutProvider.GetDependency().CreateOrganizationAsync( - provider.Id, - Arg.Any(), - requestBody.OwnerEmail, - user) - .Returns(new ProviderOrganization - { - OrganizationId = clientOrganizationId - }); - - sutProvider.GetDependency().GetByIdAsync(clientOrganizationId).ReturnsNull(); - - var result = await sutProvider.Sut.CreateAsync(provider.Id, requestBody); - - Assert.IsType(result); + AssertUnauthorized(result); } [Theory, BitAutoData] @@ -137,32 +106,11 @@ public class ProviderClientsControllerTests var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody); - Assert.IsType(result); + AssertNotFound(result); } [Theory, BitAutoData] - public async Task UpdateAsync_NoOrganization_ServerError( - Provider provider, - Guid providerOrganizationId, - UpdateClientOrganizationRequestBody requestBody, - ProviderOrganization providerOrganization, - SutProvider sutProvider) - { - ConfigureStableServiceUserInputs(provider, sutProvider); - - sutProvider.GetDependency().GetByIdAsync(providerOrganizationId) - .Returns(providerOrganization); - - sutProvider.GetDependency().GetByIdAsync(providerOrganization.OrganizationId) - .ReturnsNull(); - - var result = await sutProvider.Sut.UpdateAsync(provider.Id, providerOrganizationId, requestBody); - - Assert.IsType(result); - } - - [Theory, BitAutoData] - public async Task UpdateAsync_AssignedSeats_NoContent( + public async Task UpdateAsync_AssignedSeats_Ok( Provider provider, Guid providerOrganizationId, UpdateClientOrganizationRequestBody requestBody, @@ -193,7 +141,7 @@ public class ProviderClientsControllerTests } [Theory, BitAutoData] - public async Task UpdateAsync_Name_NoContent( + public async Task UpdateAsync_Name_Ok( Provider provider, Guid providerOrganizationId, UpdateClientOrganizationRequestBody requestBody, diff --git a/test/Api.Test/Billing/Utilities.cs b/test/Api.Test/Billing/Utilities.cs index 7c361b760..ce528477d 100644 --- a/test/Api.Test/Billing/Utilities.cs +++ b/test/Api.Test/Billing/Utilities.cs @@ -4,14 +4,37 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; +using Bit.Core.Models.Api; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.HttpResults; using NSubstitute; +using Xunit; namespace Bit.Api.Test.Billing; public static class Utilities { + public static void AssertNotFound(IResult result) + { + Assert.IsType>(result); + + var response = ((NotFound)result).Value; + + Assert.Equal("Resource not found.", response.Message); + } + + public static void AssertUnauthorized(IResult result) + { + Assert.IsType>(result); + + var response = (JsonHttpResult)result; + + Assert.Equal(StatusCodes.Status401Unauthorized, response.StatusCode); + Assert.Equal("Unauthorized.", response.Value.Message); + } + public static void ConfigureStableAdminInputs( Provider provider, SutProvider sutProvider) where T : BaseProviderController diff --git a/test/Api.Test/Controllers/CollectionsControllerTests.cs b/test/Api.Test/Controllers/CollectionsControllerTests.cs index 3a59edffe..09e93d15f 100644 --- a/test/Api.Test/Controllers/CollectionsControllerTests.cs +++ b/test/Api.Test/Controllers/CollectionsControllerTests.cs @@ -214,13 +214,13 @@ public class CollectionsControllerTests .Returns(AuthorizationResult.Success()); sutProvider.GetDependency() - .GetManyByUserIdAsync(userId, false) + .GetManyByUserIdAsync(userId) .Returns(collections); var result = await sutProvider.Sut.Get(organization.Id); await sutProvider.GetDependency().DidNotReceive().GetManyByOrganizationIdAsync(organization.Id); - await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(userId, false); + await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(userId); Assert.Single(result.Data); Assert.All(result.Data, c => Assert.Equal(organization.Id, c.OrganizationId)); diff --git a/test/Api.Test/SecretsManager/Controllers/CountsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/CountsControllerTests.cs new file mode 100644 index 000000000..330c6e02b --- /dev/null +++ b/test/Api.Test/SecretsManager/Controllers/CountsControllerTests.cs @@ -0,0 +1,212 @@ +#nullable enable +using System.Security.Claims; +using Bit.Api.SecretsManager.Controllers; +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Models.Data; +using Bit.Core.SecretsManager.Queries.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.SecretsManager.Controllers; + +[ControllerCustomize(typeof(CountsController))] +[SutProviderCustomize] +[ProjectCustomize] +[JsonDocumentCustomize] +public class CountsControllerTests +{ + [Theory] + [BitAutoData] + public async Task GetByOrganizationAsync_NoAccess_Throws(SutProvider sutProvider, + Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByOrganizationAsync(organizationId)); + } + + [Theory] + [BitAutoData] + public async Task GetByOrganizationAsync_ServiceAccountAccess_Throws(SutProvider sutProvider, + Guid organizationId, Guid userId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), organizationId) + .Returns((AccessClientType.ServiceAccount, userId)); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByOrganizationAsync(organizationId)); + } + + [Theory] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.User)] + public async Task GetByOrganizationAsync_HasAccess_Success(AccessClientType accessClientType, + SutProvider sutProvider, Guid organizationId, Guid userId, + OrganizationCountsResponseModel expectedCountsResponseModel) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), organizationId).Returns((accessClientType, userId)); + + sutProvider.GetDependency() + .GetProjectCountByOrganizationIdAsync(organizationId, userId, accessClientType) + .Returns(expectedCountsResponseModel.Projects); + + sutProvider.GetDependency() + .GetSecretsCountByOrganizationIdAsync(organizationId, userId, accessClientType) + .Returns(expectedCountsResponseModel.Secrets); + + sutProvider.GetDependency() + .GetServiceAccountCountByOrganizationIdAsync(organizationId, userId, accessClientType) + .Returns(expectedCountsResponseModel.ServiceAccounts); + + var response = await sutProvider.Sut.GetByOrganizationAsync(organizationId); + + Assert.Equal(expectedCountsResponseModel.Projects, response.Projects); + Assert.Equal(expectedCountsResponseModel.Secrets, response.Secrets); + Assert.Equal(expectedCountsResponseModel.ServiceAccounts, response.ServiceAccounts); + } + + [Theory] + [BitAutoData] + public async Task GetByProjectAsync_ProjectNotFound_Throws(SutProvider sutProvider, + Guid projectId) + { + sutProvider.GetDependency().GetByIdAsync(projectId).Returns(default(Project)); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByProjectAsync(projectId)); + } + + [Theory] + [BitAutoData] + public async Task GetByProjectAsync_NoAccess_Throws(SutProvider sutProvider, Project project) + { + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByProjectAsync(project.Id)); + } + + [Theory] + [BitAutoData] + public async Task GetByProjectAsync_ServiceAccountAccess_Throws(SutProvider sutProvider, + Guid userId, Project project) + { + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(true); + + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), project.OrganizationId) + .Returns((AccessClientType.ServiceAccount, userId)); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByProjectAsync(project.Id)); + } + + [Theory] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.User)] + public async Task GetByProjectAsync_HasAccess_Success(AccessClientType accessClientType, + SutProvider sutProvider, Guid userId, Project project, + ProjectCountsResponseModel expectedProjectCountsResponseModel) + { + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().AccessSecretsManager(project.OrganizationId).Returns(true); + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), project.OrganizationId) + .Returns((accessClientType, userId)); + + sutProvider.GetDependency() + .GetProjectCountsByIdAsync(project.Id, userId, accessClientType) + .Returns(new ProjectCounts + { + Secrets = expectedProjectCountsResponseModel.Secrets, + People = expectedProjectCountsResponseModel.People, + ServiceAccounts = expectedProjectCountsResponseModel.ServiceAccounts + }); + + var response = await sutProvider.Sut.GetByProjectAsync(project.Id); + + Assert.Equal(expectedProjectCountsResponseModel.Secrets, response.Secrets); + Assert.Equal(expectedProjectCountsResponseModel.People, response.People); + Assert.Equal(expectedProjectCountsResponseModel.ServiceAccounts, response.ServiceAccounts); + } + + [Theory] + [BitAutoData] + public async Task GetByServiceAccountAsync_ServiceAccountNotFound_Throws(SutProvider sutProvider, + Guid serviceAccountId) + { + sutProvider.GetDependency().GetByIdAsync(serviceAccountId) + .Returns(default(ServiceAccount)); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccountId)); + } + + [Theory] + [BitAutoData] + public async Task GetByServiceAccountAsync_NoAccess_Throws(SutProvider sutProvider, + ServiceAccount serviceAccount) + { + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id) + .Returns(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(serviceAccount.OrganizationId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id)); + } + + [Theory] + [BitAutoData] + public async Task GetByServiceAccountAsync_ServiceAccountAccess_Throws(SutProvider sutProvider, + Guid userId, ServiceAccount serviceAccount) + { + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(serviceAccount.OrganizationId).Returns(true); + + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), serviceAccount.OrganizationId) + .Returns((AccessClientType.ServiceAccount, userId)); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id)); + } + + [Theory] + [BitAutoData(AccessClientType.NoAccessCheck)] + [BitAutoData(AccessClientType.User)] + public async Task GetByServiceAccountAsync_HasAccess_Success(AccessClientType accessClientType, + SutProvider sutProvider, Guid userId, ServiceAccount serviceAccount, + ServiceAccountCountsResponseModel expectedServiceAccountCountsResponseModel) + { + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id) + .Returns(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(serviceAccount.OrganizationId).Returns(true); + sutProvider.GetDependency() + .GetAccessClientAsync(Arg.Any(), serviceAccount.OrganizationId) + .Returns((accessClientType, userId)); + + sutProvider.GetDependency() + .GetServiceAccountCountsByIdAsync(serviceAccount.Id, userId, accessClientType) + .Returns(new ServiceAccountCounts + { + Projects = expectedServiceAccountCountsResponseModel.Projects, + People = expectedServiceAccountCountsResponseModel.People, + AccessTokens = expectedServiceAccountCountsResponseModel.AccessTokens + }); + + var response = await sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id); + + Assert.Equal(expectedServiceAccountCountsResponseModel.Projects, response.Projects); + Assert.Equal(expectedServiceAccountCountsResponseModel.People, response.People); + Assert.Equal(expectedServiceAccountCountsResponseModel.AccessTokens, response.AccessTokens); + } +} diff --git a/test/Api.Test/SecretsManager/Controllers/RequestSMAccessControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/RequestSMAccessControllerTests.cs new file mode 100644 index 000000000..3c76246a0 --- /dev/null +++ b/test/Api.Test/SecretsManager/Controllers/RequestSMAccessControllerTests.cs @@ -0,0 +1,86 @@ +using System.Security.Claims; +using Bit.Api.SecretsManager.Controllers; +using Bit.Api.SecretsManager.Models.Request; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Commands.Requests.Interfaces; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Api.Test.SecretsManager.Controllers; + +[ControllerCustomize(typeof(RequestSMAccessController))] +[SutProviderCustomize] +public class RequestSMAccessControllerTests +{ + [Theory] + [BitAutoData] + public async Task RequestSMAccessFromAdmins_WhenSendingNoModel_ShouldThrowNotFoundException( + User user, SutProvider sutProvider) + { + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); + sutProvider.GetDependency().GetByIdentifierAsync(Arg.Any()).ReturnsNullForAnyArgs(); + + await Assert.ThrowsAsync(() => sutProvider.Sut.RequestSMAccessFromAdmins(new RequestSMAccessRequestModel())); + } + + [Theory] + [BitAutoData] + public async Task RequestSMAccessFromAdmins_WhenSendingValidData_ShouldSucceed( + User user, + RequestSMAccessRequestModel model, + Core.AdminConsole.Entities.Organization org, + ICollection orgUsers, + SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(model.OrganizationId).Returns(org); + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).Returns(user); + sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(org.Id).Returns(orgUsers); + sutProvider.GetDependency().OrganizationUser(model.OrganizationId).Returns(true); + + await sutProvider.Sut.RequestSMAccessFromAdmins(model); + + //Also check that the command was called + await sutProvider.GetDependency() + .Received(1) + .SendRequestAccessToSM(org, orgUsers, user, model.EmailContent); + } + + [Theory] + [BitAutoData] + public async Task RequestSMAccessFromAdmins_WhenUserInvalid_ShouldThrowBadRequestException(RequestSMAccessRequestModel model, SutProvider sutProvider) + { + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).ReturnsNullForAnyArgs(); + + await Assert.ThrowsAsync(() => sutProvider.Sut.RequestSMAccessFromAdmins(model)); + } + + [Theory] + [BitAutoData] + public async Task RequestSMAccessFromAdmins_WhenOrgInvalid_ShouldThrowNotFoundException(RequestSMAccessRequestModel model, User user, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdentifierAsync(Arg.Any()).ReturnsNullForAnyArgs(); + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + sutProvider.GetDependency().OrganizationUser(model.OrganizationId).Returns(true); + + await Assert.ThrowsAsync(() => sutProvider.Sut.RequestSMAccessFromAdmins(model)); + } + + [Theory] + [BitAutoData] + public async Task RequestSMAccessFromAdmins_WhenOrgUserInvalid_ShouldThrowNotFoundException(RequestSMAccessRequestModel model, User user, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdentifierAsync(Arg.Any()).ReturnsNullForAnyArgs(); + sutProvider.GetDependency().GetUserByPrincipalAsync(Arg.Any()).ReturnsForAnyArgs(user); + sutProvider.GetDependency().OrganizationUser(model.OrganizationId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.RequestSMAccessFromAdmins(model)); + } +} diff --git a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs index 3eea25b39..4fb2c4b7f 100644 --- a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs @@ -412,30 +412,6 @@ public class SecretsControllerTests await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); } - [Theory] - [BitAutoData] - public async Task GetSecretsByIds_OrganizationMisMatch_ThrowsNotFound(SutProvider sutProvider, - List data) - { - var (ids, request) = BuildGetSecretsRequestModel(data); - sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); - } - - [Theory] - [BitAutoData] - public async Task GetSecretsByIds_NoAccessToSecretsManager_ThrowsNotFound( - SutProvider sutProvider, List data) - { - var (ids, request) = BuildGetSecretsRequestModel(data); - var organizationId = SetOrganizations(ref data); - - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(false); - sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); - } - [Theory] [BitAutoData] public async Task GetSecretsByIds_AccessDenied_ThrowsNotFound(SutProvider sutProvider, @@ -445,10 +421,8 @@ public class SecretsControllerTests var organizationId = SetOrganizations(ref data); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(true); sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), data.First(), + .AuthorizeAsync(Arg.Any(), data, Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); await Assert.ThrowsAsync(() => sutProvider.Sut.GetSecretsByIdsAsync(request)); @@ -462,10 +436,8 @@ public class SecretsControllerTests var organizationId = SetOrganizations(ref data); sutProvider.GetDependency().GetManyByIds(Arg.Is(ids)).ReturnsForAnyArgs(data); - sutProvider.GetDependency().AccessSecretsManager(Arg.Is(organizationId)) - .ReturnsForAnyArgs(true); sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), data.First(), + .AuthorizeAsync(Arg.Any(), data, Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); var results = await sutProvider.Sut.GetSecretsByIdsAsync(request); diff --git a/test/Api.Test/Vault/AuthorizationHandlers/BulkCollectionAuthorizationHandlerTests.cs b/test/Api.Test/Vault/AuthorizationHandlers/BulkCollectionAuthorizationHandlerTests.cs index dbc076bf7..ad3d58b02 100644 --- a/test/Api.Test/Vault/AuthorizationHandlers/BulkCollectionAuthorizationHandlerTests.cs +++ b/test/Api.Test/Vault/AuthorizationHandlers/BulkCollectionAuthorizationHandlerTests.cs @@ -236,7 +236,7 @@ public class BulkCollectionAuthorizationHandlerTests { sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); var context = new AuthorizationHandlerContext( new[] { BulkCollectionOperations.Read }, @@ -439,7 +439,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); var context = new AuthorizationHandlerContext( new[] { BulkCollectionOperations.ReadWithAccess }, @@ -469,7 +469,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); var context = new AuthorizationHandlerContext( new[] { BulkCollectionOperations.ReadWithAccess }, @@ -534,44 +534,6 @@ public class BulkCollectionAuthorizationHandlerTests Assert.False(context.HasSucceeded); } - [Theory, CollectionCustomization] - [BitAutoData(OrganizationUserType.Admin)] - [BitAutoData(OrganizationUserType.Owner)] - public async Task CanUpdateCollection_WhenAdminOrOwner_WithoutV1Enabled_Success( - OrganizationUserType userType, - Guid userId, SutProvider sutProvider, - ICollection collections, - CurrentContextOrganization organization) - { - organization.Type = userType; - organization.Permissions = new Permissions(); - - var operationsToTest = new[] - { - BulkCollectionOperations.Update, - BulkCollectionOperations.ModifyUserAccess, - BulkCollectionOperations.ModifyGroupAccess, - }; - - foreach (var op in operationsToTest) - { - sutProvider.GetDependency().UserId.Returns(userId); - sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - - var context = new AuthorizationHandlerContext( - new[] { op }, - new ClaimsPrincipal(), - collections); - - await sutProvider.Sut.HandleAsync(context); - - Assert.True(context.HasSucceeded); - - // Recreate the SUT to reset the mocks/dependencies between tests - sutProvider.Recreate(); - } - } - [Theory, CollectionCustomization] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.Owner)] @@ -725,7 +687,7 @@ public class BulkCollectionAuthorizationHandlerTests { sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); var context = new AuthorizationHandlerContext( new[] { op }, @@ -827,32 +789,6 @@ public class BulkCollectionAuthorizationHandlerTests } } - [Theory, BitAutoData, CollectionCustomization] - public async Task CanUpdateUsers_WithManageUsersCustomPermission_V1Disabled_Success( - SutProvider sutProvider, ICollection collections, - CurrentContextOrganization organization, Guid actingUserId) - { - organization.Type = OrganizationUserType.Custom; - organization.Permissions = new Permissions - { - ManageUsers = true - }; - - sutProvider.GetDependency().UserId.Returns(actingUserId); - sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) - .Returns(false); - - var context = new AuthorizationHandlerContext( - new[] { BulkCollectionOperations.ModifyUserAccess }, - new ClaimsPrincipal(), - collections); - - await sutProvider.Sut.HandleAsync(context); - - Assert.True(context.HasSucceeded); - } - [Theory, BitAutoData, CollectionCustomization] public async Task CanUpdateUsers_WithManageUsersCustomPermission_AllowAdminAccessIsTrue_Success( SutProvider sutProvider, ICollection collections, @@ -909,32 +845,6 @@ public class BulkCollectionAuthorizationHandlerTests Assert.False(context.HasSucceeded); } - [Theory, BitAutoData, CollectionCustomization] - public async Task CanUpdateGroups_WithManageGroupsCustomPermission_V1Disabled_Success( - SutProvider sutProvider, ICollection collections, - CurrentContextOrganization organization, Guid actingUserId) - { - organization.Type = OrganizationUserType.Custom; - organization.Permissions = new Permissions - { - ManageGroups = true - }; - - sutProvider.GetDependency().UserId.Returns(actingUserId); - sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1) - .Returns(false); - - var context = new AuthorizationHandlerContext( - new[] { BulkCollectionOperations.ModifyGroupAccess }, - new ClaimsPrincipal(), - collections); - - await sutProvider.Sut.HandleAsync(context); - - Assert.True(context.HasSucceeded); - } - [Theory, BitAutoData, CollectionCustomization] public async Task CanUpdateGroups_WithManageGroupsCustomPermission_AllowAdminAccessIsTrue_Success( SutProvider sutProvider, ICollection collections, @@ -1047,34 +957,6 @@ public class BulkCollectionAuthorizationHandlerTests Assert.True(context.HasSucceeded); } - [Theory, CollectionCustomization] - [BitAutoData(OrganizationUserType.Admin)] - [BitAutoData(OrganizationUserType.Owner)] - public async Task CanDeleteAsync_WhenAdminOrOwner_V1FlagDisabled_Success( - OrganizationUserType userType, - Guid userId, SutProvider sutProvider, - ICollection collections, - CurrentContextOrganization organization) - { - organization.Type = userType; - organization.Permissions = new Permissions(); - - ArrangeOrganizationAbility(sutProvider, organization, true, false); - - var context = new AuthorizationHandlerContext( - new[] { BulkCollectionOperations.Delete }, - new ClaimsPrincipal(), - collections); - - sutProvider.GetDependency().UserId.Returns(userId); - sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(false); - - await sutProvider.Sut.HandleAsync(context); - - Assert.True(context.HasSucceeded); - } - [Theory, BitAutoData, CollectionCustomization] public async Task CanDeleteAsync_WhenUser_LimitCollectionCreationDeletionFalse_WithCanManagePermission_Success( SutProvider sutProvider, @@ -1090,7 +972,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); foreach (var c in collections) { @@ -1126,7 +1008,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); foreach (var c in collections) @@ -1162,7 +1044,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); foreach (var c in collections) @@ -1198,7 +1080,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); sutProvider.GetDependency().ProviderUserForOrgAsync(Arg.Any()).Returns(false); @@ -1232,7 +1114,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); sutProvider.GetDependency().ProviderUserForOrgAsync(Arg.Any()).Returns(false); @@ -1266,7 +1148,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); - sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId, Arg.Any()).Returns(collections); + sutProvider.GetDependency().GetManyByUserIdAsync(actingUserId).Returns(collections); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(true); sutProvider.GetDependency().ProviderUserForOrgAsync(Arg.Any()).Returns(false); @@ -1449,7 +1331,7 @@ public class BulkCollectionAuthorizationHandlerTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().GetOrganization(organization.Id).Returns(organization); sutProvider.GetDependency() - .GetManyByUserIdAsync(actingUserId, Arg.Any()) + .GetManyByUserIdAsync(actingUserId) .Returns(new List() { collection1, collection2 }); var context1 = new AuthorizationHandlerContext( @@ -1467,7 +1349,7 @@ public class BulkCollectionAuthorizationHandlerTests await sutProvider.Sut.HandleAsync(context2); // Expect: only calls the database once - await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(Arg.Any(), Arg.Any()); + await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(Arg.Any()); } private static void ArrangeOrganizationAbility( @@ -1477,7 +1359,6 @@ public class BulkCollectionAuthorizationHandlerTests { var organizationAbility = new OrganizationAbility(); organizationAbility.Id = organization.Id; - organizationAbility.FlexibleCollections = true; organizationAbility.LimitCollectionCreationDeletion = limitCollectionCreationDeletion; organizationAbility.AllowAdminAccessToAllCollectionItems = allowAdminAccessToAllCollectionItems; diff --git a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs index a28c52a68..8c92984ff 100644 --- a/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/CiphersControllerTests.cs @@ -187,11 +187,11 @@ public class CiphersControllerTests } [Theory] - [BitAutoData(false, false)] - [BitAutoData(true, false)] - [BitAutoData(true, true)] + [BitAutoData(false)] + [BitAutoData(false)] + [BitAutoData(true)] public async Task CanEditCiphersAsAdminAsync_Providers( - bool fcV1Enabled, bool restrictProviders, Cipher cipher, CurrentContextOrganization organization, Guid userId, SutProvider sutProvider + bool restrictProviders, Cipher cipher, CurrentContextOrganization organization, Guid userId, SutProvider sutProvider ) { cipher.OrganizationId = organization.Id; @@ -210,11 +210,10 @@ public class CiphersControllerTests Id = organization.Id, AllowAdminAccessToAllCollectionItems = false }); - sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1).Returns(fcV1Enabled); sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.RestrictProviderAccess).Returns(restrictProviders); - // Non V1 FC or non restricted providers should succeed - if (!fcV1Enabled || !restrictProviders) + // Non restricted providers should succeed + if (!restrictProviders) { await sutProvider.Sut.DeleteAdmin(cipher.Id.ToString()); await sutProvider.GetDependency().ReceivedWithAnyArgs() @@ -227,13 +226,6 @@ public class CiphersControllerTests .DeleteAsync(default, default); } - if (fcV1Enabled) - { - await sutProvider.GetDependency().Received().ProviderUserForOrgAsync(organization.Id); - } - else - { - await sutProvider.GetDependency().Received().EditAnyCollection(organization.Id); - } + await sutProvider.GetDependency().Received().ProviderUserForOrgAsync(organization.Id); } } diff --git a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs index 6e8578df0..66a59ef23 100644 --- a/test/Api.Test/Vault/Controllers/SyncControllerTests.cs +++ b/test/Api.Test/Vault/Controllers/SyncControllerTests.cs @@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.Models; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -107,7 +108,7 @@ public class SyncControllerTests .Returns(providerUserOrganizationDetails); folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders); - cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any()).Returns(ciphers); + cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers); sendRepository .GetManyByUserIdAsync(user.Id).Returns(sends); @@ -115,7 +116,7 @@ public class SyncControllerTests policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies); // Returns for methods only called if we have enabled orgs - collectionRepository.GetManyByUserIdAsync(user.Id, Arg.Any()).Returns(collections); + collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections); collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List()); // Back to standard test setup userService.TwoFactorIsEnabledAsync(user).Returns(false); @@ -197,7 +198,7 @@ public class SyncControllerTests .Returns(providerUserOrganizationDetails); folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders); - cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any()).Returns(ciphers); + cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers); sendRepository .GetManyByUserIdAsync(user.Id).Returns(sends); @@ -271,7 +272,7 @@ public class SyncControllerTests .Returns(providerUserOrganizationDetails); folderRepository.GetManyByUserIdAsync(user.Id).Returns(folders); - cipherRepository.GetManyByUserIdAsync(user.Id, useFlexibleCollections: Arg.Any()).Returns(ciphers); + cipherRepository.GetManyByUserIdAsync(user.Id).Returns(ciphers); sendRepository .GetManyByUserIdAsync(user.Id).Returns(sends); @@ -279,7 +280,7 @@ public class SyncControllerTests policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies); // Returns for methods only called if we have enabled orgs - collectionRepository.GetManyByUserIdAsync(user.Id, Arg.Any()).Returns(collections); + collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections); collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List()); // Back to standard test setup userService.TwoFactorIsEnabledAsync(user).Returns(false); @@ -333,7 +334,7 @@ public class SyncControllerTests .GetManyByUserIdAsync(default); await cipherRepository.ReceivedWithAnyArgs(1) - .GetManyByUserIdAsync(default, useFlexibleCollections: default); + .GetManyByUserIdAsync(default); await sendRepository.ReceivedWithAnyArgs(1) .GetManyByUserIdAsync(default); @@ -342,7 +343,7 @@ public class SyncControllerTests if (hasEnabledOrgs) { await collectionRepository.ReceivedWithAnyArgs(1) - .GetManyByUserIdAsync(default, default); + .GetManyByUserIdAsync(default); await collectionCipherRepository.ReceivedWithAnyArgs(1) .GetManyByUserIdAsync(default); } @@ -350,13 +351,13 @@ public class SyncControllerTests { // all disabled orgs await collectionRepository.ReceivedWithAnyArgs(0) - .GetManyByUserIdAsync(default, default); + .GetManyByUserIdAsync(default); await collectionCipherRepository.ReceivedWithAnyArgs(0) .GetManyByUserIdAsync(default); } await userService.ReceivedWithAnyArgs(1) - .TwoFactorIsEnabledAsync(default); + .TwoFactorIsEnabledAsync(default(ITwoFactorProvidersUser)); await userService.ReceivedWithAnyArgs(1) .HasPremiumFromOrganization(default); } diff --git a/test/Billing.Test/Services/ProviderEventServiceTests.cs b/test/Billing.Test/Services/ProviderEventServiceTests.cs index 47c2f8450..e76cf0d28 100644 --- a/test/Billing.Test/Services/ProviderEventServiceTests.cs +++ b/test/Billing.Test/Services/ProviderEventServiceTests.cs @@ -169,10 +169,14 @@ public class ProviderEventServiceTests _stripeFacade.GetSubscription(subscriptionId).Returns(subscription); + var client1Id = Guid.NewGuid(); + var client2Id = Guid.NewGuid(); + var clients = new List { new () { + OrganizationId = client1Id, OrganizationName = "Client 1", Plan = "Teams (Monthly)", Seats = 50, @@ -181,6 +185,7 @@ public class ProviderEventServiceTests }, new () { + OrganizationId = client2Id, OrganizationName = "Client 2", Plan = "Enterprise (Monthly)", Seats = 50, @@ -228,6 +233,7 @@ public class ProviderEventServiceTests options.InvoiceId == invoice.Id && options.InvoiceNumber == invoice.Number && options.ClientName == "Client 1" && + options.ClientId == client1Id && options.PlanName == "Teams (Monthly)" && options.AssignedSeats == 50 && options.UsedSeats == 30 && @@ -239,6 +245,7 @@ public class ProviderEventServiceTests options.InvoiceId == invoice.Id && options.InvoiceNumber == invoice.Number && options.ClientName == "Client 2" && + options.ClientId == client2Id && options.PlanName == "Enterprise (Monthly)" && options.AssignedSeats == 50 && options.UsedSeats == 30 && diff --git a/test/Bitwarden.Tests.proj b/test/Bitwarden.Tests.proj new file mode 100644 index 000000000..94da2116c --- /dev/null +++ b/test/Bitwarden.Tests.proj @@ -0,0 +1,5 @@ + + + + + diff --git a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs index 6ed7eb85f..e906862e3 100644 --- a/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AdminConsole/AutoFixture/OrganizationFixtures.cs @@ -19,7 +19,6 @@ namespace Bit.Core.Test.AutoFixture.OrganizationFixtures; public class OrganizationCustomization : ICustomization { public bool UseGroups { get; set; } - public bool FlexibleCollections { get; set; } public PlanType PlanType { get; set; } public void Customize(IFixture fixture) @@ -36,7 +35,6 @@ public class OrganizationCustomization : ICustomization .With(o => o.Id, organizationId) .With(o => o.MaxCollections, maxCollections) .With(o => o.UseGroups, UseGroups) - .With(o => o.FlexibleCollections, FlexibleCollections) .With(o => o.PlanType, PlanType) .With(o => o.Seats, seats) .With(o => o.SmSeats, smSeats)); @@ -138,6 +136,9 @@ internal class OrganizationInvite : ICustomization .With(ou => ou.Permissions, PermissionsBlob)); fixture.Customize(composer => composer .With(oi => oi.Type, InviteeUserType)); + // Set Manage to false, this ensures it doesn't conflict with the other properties during validation + fixture.Customize(composer => composer + .With(c => c.Manage, false)); } } @@ -195,12 +196,10 @@ internal class TeamsMonthlyWithAddOnsOrganizationCustomization : ICustomization public class OrganizationCustomizeAttribute : BitCustomizeAttribute { public bool UseGroups { get; set; } - public bool FlexibleCollections { get; set; } public PlanType PlanType { get; set; } = PlanType.EnterpriseAnnually; public override ICustomization GetCustomization() => new OrganizationCustomization() { UseGroups = UseGroups, - FlexibleCollections = FlexibleCollections, PlanType = PlanType }; } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs index bac2630ed..a892a4bdb 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs @@ -20,9 +20,12 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class CreateGroupCommandTests { - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] public async Task CreateGroup_Success(SutProvider sutProvider, Organization organization, Group group) { + // Deprecated with Flexible Collections + group.AccessAll = false; + await sutProvider.Sut.CreateGroupAsync(group, organization); await sutProvider.GetDependency().Received(1).CreateAsync(group); @@ -32,9 +35,21 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] public async Task CreateGroup_WithCollections_Success(SutProvider sutProvider, Organization organization, Group group, List collections) { + // Deprecated with Flexible Collections + group.AccessAll = false; + + // Arrange list of collections to make sure Manage is mutually exclusive + for (var i = 0; i < collections.Count; i++) + { + var cas = collections[i]; + cas.Manage = i != collections.Count - 1; + cas.HidePasswords = i == collections.Count - 1; + cas.ReadOnly = i == collections.Count - 1; + } + await sutProvider.Sut.CreateGroupAsync(group, organization, collections); await sutProvider.GetDependency().Received(1).CreateAsync(group, collections); @@ -44,9 +59,12 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] public async Task CreateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) { + // Deprecated with Flexible Collections + group.AccessAll = false; + await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser); await sutProvider.GetDependency().Received(1).CreateAsync(group); @@ -56,9 +74,12 @@ public class CreateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] public async Task CreateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) { + // Deprecated with Flexible Collections + group.AccessAll = false; + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, null, eventSystemUser)); Assert.Contains("Organization not found", exception.Message); @@ -68,9 +89,12 @@ public class CreateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); } - [Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData] + [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] public async Task CreateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) { + // Deprecated with Flexible Collections + group.AccessAll = false; + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, organization, eventSystemUser)); Assert.Contains("This organization cannot use groups", exception.Message); @@ -80,12 +104,11 @@ public class CreateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RaiseEventAsync(default); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] - public async Task CreateGroup_WithFlexibleCollections_WithAccessAll_Throws( + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task CreateGroup_WithAccessAll_Throws( SutProvider sutProvider, Organization organization, Group group) { group.AccessAll = true; - organization.FlexibleCollections = true; var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.CreateGroupAsync(group, organization)); diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs index 1b21574fd..add4e2081 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs @@ -1,11 +1,14 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.Groups; using Bit.Core.AdminConsole.Repositories; +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.OrganizationFixtures; +using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -17,9 +20,14 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class UpdateGroupCommandTests { - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] - public async Task UpdateGroup_Success(SutProvider sutProvider, Group group, Organization organization) + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_Success(SutProvider sutProvider, Group group, Group oldGroup, + Organization organization) { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + await sutProvider.Sut.UpdateGroupAsync(group, organization); await sutProvider.GetDependency().Received(1).ReplaceAsync(group); @@ -27,9 +35,23 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] - public async Task UpdateGroup_WithCollections_Success(SutProvider sutProvider, Group group, Organization organization, List collections) + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithCollections_Success(SutProvider sutProvider, Group group, + Group oldGroup, Organization organization, List collections) { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + + // Arrange list of collections to make sure Manage is mutually exclusive + for (var i = 0; i < collections.Count; i++) + { + var cas = collections[i]; + cas.Manage = i != collections.Count - 1; + cas.HidePasswords = i == collections.Count - 1; + cas.ReadOnly = i == collections.Count - 1; + } + await sutProvider.Sut.UpdateGroupAsync(group, organization, collections); await sutProvider.GetDependency().Received(1).ReplaceAsync(group, collections); @@ -37,9 +59,14 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] - public async Task UpdateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Group group, Organization organization, EventSystemUser eventSystemUser) + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithEventSystemUser_Success(SutProvider sutProvider, Group group, + Group oldGroup, Organization organization, EventSystemUser eventSystemUser) { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser); await sutProvider.GetDependency().Received(1).ReplaceAsync(group); @@ -47,20 +74,28 @@ public class UpdateGroupCommandTests AssertHelper.AssertRecent(group.RevisionDate); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = false), BitAutoData] - public async Task UpdateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, EventSystemUser eventSystemUser) + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithNullOrganization_Throws(SutProvider sutProvider, Group group, + Group oldGroup, EventSystemUser eventSystemUser) { - var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, null, eventSystemUser)); + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); - Assert.Contains("Organization not found", exception.Message); + await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, null, eventSystemUser)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); } - [Theory, OrganizationCustomize(UseGroups = false, FlexibleCollections = false), BitAutoData] - public async Task UpdateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, Organization organization, Group group, EventSystemUser eventSystemUser) + [Theory, OrganizationCustomize(UseGroups = false), BitAutoData] + public async Task UpdateGroup_WithUseGroupsAsFalse_Throws(SutProvider sutProvider, + Organization organization, Group group, Group oldGroup, EventSystemUser eventSystemUser) { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization, eventSystemUser)); Assert.Contains("This organization cannot use groups", exception.Message); @@ -69,12 +104,15 @@ public class UpdateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); } - [Theory, OrganizationCustomize(UseGroups = true, FlexibleCollections = true), BitAutoData] - public async Task UpdateGroup_WithFlexibleCollections_WithAccessAll_Throws( - SutProvider sutProvider, Organization organization, Group group) + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_WithAccessAll_Throws( + SutProvider sutProvider, Organization organization, Group group, Group oldGroup) { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + group.AccessAll = true; - organization.FlexibleCollections = true; var exception = await Assert.ThrowsAsync(async () => await sutProvider.Sut.UpdateGroupAsync(group, organization)); @@ -83,4 +121,123 @@ public class UpdateGroupCommandTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogGroupEventAsync(default, default, default); } + + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_GroupBelongsToDifferentOrganization_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization) + { + group.AccessAll = false; + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + ArrangeCollections(sutProvider, group); + + // Mismatching orgId + oldGroup.OrganizationId = CoreHelpers.GenerateComb(); + + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateGroupAsync(group, organization)); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_CollectionsBelongsToDifferentOrganization_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization, List collectionAccess) + { + group.AccessAll = false; + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateGroupAsync(group, organization, collectionAccess)); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_CollectionsDoNotExist_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization, List collectionAccess) + { + group.AccessAll = false; + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeUsers(sutProvider, group); + + // Return result is missing a collection + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => + { + var result = callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = group.OrganizationId }).ToList(); + result.RemoveAt(0); + return result; + }); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateGroupAsync(group, organization, collectionAccess)); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_MemberBelongsToDifferentOrganization_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization, IEnumerable userAccess) + { + group.AccessAll = false; + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeCollections(sutProvider, group); + + sutProvider.GetDependency() + .GetManyAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new OrganizationUser { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateGroupAsync(group, organization, null, userAccess)); + } + + [Theory, OrganizationCustomize(UseGroups = true), BitAutoData] + public async Task UpdateGroup_MemberDoesNotExist_Throws(SutProvider sutProvider, + Group group, Group oldGroup, Organization organization, IEnumerable userAccess) + { + ArrangeGroup(sutProvider, group, oldGroup); + ArrangeCollections(sutProvider, group); + + sutProvider.GetDependency() + .GetManyAsync(Arg.Any>()) + .Returns(callInfo => + { + var result = callInfo.Arg>() + .Select(guid => new OrganizationUser { Id = guid, OrganizationId = group.OrganizationId }) + .ToList(); + result.RemoveAt(0); + return result; + }); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateGroupAsync(group, organization, null, userAccess)); + } + + private void ArrangeGroup(SutProvider sutProvider, Group group, Group oldGroup) + { + group.AccessAll = false; + oldGroup.OrganizationId = group.OrganizationId; + oldGroup.Id = group.Id; + sutProvider.GetDependency().GetByIdAsync(group.Id).Returns(oldGroup); + } + + private void ArrangeCollections(SutProvider sutProvider, Group group) + { + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection() { Id = guid, OrganizationId = group.OrganizationId }).ToList()); + } + + private void ArrangeUsers(SutProvider sutProvider, Group group) + { + sutProvider.GetDependency() + .GetManyAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new OrganizationUser { Id = guid, OrganizationId = group.OrganizationId }).ToList()); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs index ed0a1cdff..4aed8192a 100644 --- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/UpdateOrganizationUserCommandTests.cs @@ -1,6 +1,7 @@ using System.Text.Json; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -21,7 +22,7 @@ public class UpdateOrganizationUserCommandTests { [Theory, BitAutoData] public async Task UpdateUserAsync_NoUserId_Throws(OrganizationUser user, Guid? savingUserId, - ICollection collections, IEnumerable groups, SutProvider sutProvider) + List collections, List groups, SutProvider sutProvider) { user.Id = default(Guid); var exception = await Assert.ThrowsAsync( @@ -29,22 +30,119 @@ public class UpdateOrganizationUserCommandTests Assert.Contains("invite the user first", exception.Message.ToLowerInvariant()); } + [Theory, BitAutoData] + public async Task UpdateUserAsync_DifferentOrganizationId_Throws(OrganizationUser user, OrganizationUser originalUser, + Guid? savingUserId, SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(user.Id).Returns(originalUser); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, null)); + } + + [Theory, BitAutoData] + public async Task UpdateUserAsync_CollectionsBelongToDifferentOrganization_Throws(OrganizationUser user, OrganizationUser originalUser, + List collectionAccess, Guid? savingUserId, SutProvider sutProvider, + Organization organization) + { + Setup(sutProvider, organization, user, originalUser); + + // Return collections with different organizationIds from the repository + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); + } + + [Theory, BitAutoData] + public async Task UpdateUserAsync_CollectionsDoNotExist_Throws(OrganizationUser user, OrganizationUser originalUser, + List collectionAccess, Guid? savingUserId, SutProvider sutProvider, + Organization organization) + { + Setup(sutProvider, organization, user, originalUser); + + // Return matching collections, except that 1 is missing + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => + { + var result = callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = user.OrganizationId }).ToList(); + result.RemoveAt(0); + return result; + }); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null)); + } + + [Theory, BitAutoData] + public async Task UpdateUserAsync_GroupsBelongToDifferentOrganization_Throws(OrganizationUser user, OrganizationUser originalUser, + ICollection groupAccess, Guid? savingUserId, SutProvider sutProvider, + Organization organization) + { + Setup(sutProvider, organization, user, originalUser); + + // Return collections with different organizationIds from the repository + sutProvider.GetDependency() + .GetManyByManyIds(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList()); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); + } + + [Theory, BitAutoData] + public async Task UpdateUserAsync_GroupsDoNotExist_Throws(OrganizationUser user, OrganizationUser originalUser, + ICollection groupAccess, Guid? savingUserId, SutProvider sutProvider, + Organization organization) + { + Setup(sutProvider, organization, user, originalUser); + + // Return matching collections, except that 1 is missing + sutProvider.GetDependency() + .GetManyByManyIds(Arg.Any>()) + .Returns(callInfo => + { + var result = callInfo.Arg>() + .Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList(); + result.RemoveAt(0); + return result; + }); + + await Assert.ThrowsAsync( + () => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess)); + } + [Theory, BitAutoData] public async Task UpdateUserAsync_Passes( Organization organization, OrganizationUser oldUserData, OrganizationUser newUserData, - ICollection collections, - IEnumerable groups, + List collections, + List groups, Permissions permissions, [OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser savingUser, SutProvider sutProvider) { - var organizationRepository = sutProvider.GetDependency(); - var organizationUserRepository = sutProvider.GetDependency(); - var organizationService = sutProvider.GetDependency(); + Setup(sutProvider, organization, newUserData, oldUserData); - organizationRepository.GetByIdAsync(organization.Id).Returns(organization); + // Deprecated with Flexible Collections + oldUserData.AccessAll = false; + newUserData.AccessAll = false; + + // Arrange list of collections to make sure Manage is mutually exclusive + for (var i = 0; i < collections.Count; i++) + { + var cas = collections[i]; + cas.Manage = i != collections.Count - 1; + cas.HidePasswords = i == collections.Count - 1; + cas.ReadOnly = i == collections.Count - 1; + } newUserData.Id = oldUserData.Id; newUserData.UserId = oldUserData.UserId; @@ -53,17 +151,20 @@ public class UpdateOrganizationUserCommandTests { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); - organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData); - organizationUserRepository.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner) - .Returns(new List { savingUser }); - organizationService - .HasConfirmedOwnersExceptAsync( - newUserData.OrganizationId, - Arg.Is>(i => i.Contains(newUserData.Id))) - .Returns(true); + + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = oldUserData.OrganizationId }).ToList()); + + sutProvider.GetDependency() + .GetManyByManyIds(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Group { Id = guid, OrganizationId = oldUserData.OrganizationId }).ToList()); await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups); + var organizationService = sutProvider.GetDependency(); await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions( newUserData.OrganizationId, newUserData.Type, @@ -78,60 +179,31 @@ public class UpdateOrganizationUserCommandTests } [Theory, BitAutoData] - public async Task UpdateUserAsync_WithFlexibleCollections_WhenUpgradingToManager_Throws( - Organization organization, - [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData, - [OrganizationUser(type: OrganizationUserType.Manager)] OrganizationUser newUserData, - [OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser, - ICollection collections, - IEnumerable groups, - SutProvider sutProvider) - { - organization.FlexibleCollections = true; - newUserData.Id = oldUserData.Id; - newUserData.UserId = oldUserData.UserId; - newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organization.Id; - newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions()); - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency() - .HasConfirmedOwnersExceptAsync(newUserData.OrganizationId, Arg.Is>(i => i.Contains(newUserData.Id))) - .Returns(true); - - sutProvider.GetDependency() - .GetByIdAsync(oldUserData.Id) - .Returns(oldUserData); - - sutProvider.GetDependency() - .GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner) - .Returns(new List { savingUser }); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.UpdateUserAsync(newUserData, oldUserData.UserId, collections, groups)); - - Assert.Contains("manager role has been deprecated", exception.Message.ToLowerInvariant()); - } - - [Theory, BitAutoData] - public async Task UpdateUserAsync_WithFlexibleCollections_WithAccessAll_Throws( + public async Task UpdateUserAsync_WithAccessAll_Throws( Organization organization, [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser oldUserData, [OrganizationUser(type: OrganizationUserType.User)] OrganizationUser newUserData, [OrganizationUser(type: OrganizationUserType.Owner, status: OrganizationUserStatusType.Confirmed)] OrganizationUser savingUser, - ICollection collections, - IEnumerable groups, + List collections, + List groups, SutProvider sutProvider) { - organization.FlexibleCollections = true; newUserData.Id = oldUserData.Id; newUserData.UserId = oldUserData.UserId; newUserData.OrganizationId = oldUserData.OrganizationId = savingUser.OrganizationId = organization.Id; newUserData.Permissions = CoreHelpers.ClassToJsonData(new Permissions()); newUserData.AccessAll = true; + sutProvider.GetDependency() + .GetManyByManyIdsAsync(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Collection { Id = guid, OrganizationId = oldUserData.OrganizationId }).ToList()); + + sutProvider.GetDependency() + .GetManyByManyIds(Arg.Any>()) + .Returns(callInfo => callInfo.Arg>() + .Select(guid => new Group { Id = guid, OrganizationId = oldUserData.OrganizationId }).ToList()); + sutProvider.GetDependency() .GetByIdAsync(organization.Id) .Returns(organization); @@ -155,4 +227,24 @@ public class UpdateOrganizationUserCommandTests Assert.Contains("the accessall property has been deprecated", exception.Message.ToLowerInvariant()); } + + private void Setup(SutProvider sutProvider, Organization organization, + OrganizationUser newUser, OrganizationUser oldUser) + { + var organizationRepository = sutProvider.GetDependency(); + var organizationUserRepository = sutProvider.GetDependency(); + var organizationService = sutProvider.GetDependency(); + + organizationRepository.GetByIdAsync(organization.Id).Returns(organization); + + newUser.Id = oldUser.Id; + newUser.UserId = oldUser.UserId; + newUser.OrganizationId = oldUser.OrganizationId = organization.Id; + organizationUserRepository.GetByIdAsync(oldUser.Id).Returns(oldUser); + organizationService + .HasConfirmedOwnersExceptAsync( + oldUser.OrganizationId, + Arg.Is>(i => i.Contains(oldUser.Id))) + .Returns(true); + } } diff --git a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs index 06a8e6abe..a8078ed01 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs @@ -7,9 +7,11 @@ using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models; using Bit.Core.Auth.Models.Business.Tokenables; using Bit.Core.Auth.Models.Data; using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; using Bit.Core.Billing.Enums; using Bit.Core.Context; using Bit.Core.Entities; @@ -269,10 +271,6 @@ public class OrganizationServiceTests var result = await sutProvider.Sut.SignUpAsync(signup); - // Assert: Organization.FlexibleCollections is enabled - await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Is(o => o.FlexibleCollections)); - // Assert: AccessAll is not used await sutProvider.GetDependency().Received(1).CreateAsync( Arg.Is(o => @@ -464,7 +462,7 @@ public class OrganizationServiceTests [Theory] [OrganizationInviteCustomize(InviteeUserType = OrganizationUserType.User, - InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + InvitorUserType = OrganizationUserType.Owner), OrganizationCustomize, BitAutoData] public async Task InviteUsers_NoEmails_Throws(Organization organization, OrganizationUser invitor, OrganizationUserInvite invite, SutProvider sutProvider) { @@ -477,7 +475,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize, BitAutoData] public async Task InviteUsers_DuplicateEmails_PassesWithoutDuplicates(Organization organization, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -520,7 +518,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize, BitAutoData] public async Task InviteUsers_SsoOrgWithNullSsoConfig_Passes(Organization organization, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -568,7 +566,7 @@ public class OrganizationServiceTests } [Theory] - [OrganizationInviteCustomize, OrganizationCustomize(FlexibleCollections = false), BitAutoData] + [OrganizationInviteCustomize, OrganizationCustomize, BitAutoData] public async Task InviteUsers_SsoOrgWithNeverEnabledRequireSsoPolicy_Passes(Organization organization, SsoConfig ssoConfig, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, OrganizationUserInvite invite, SutProvider sutProvider) @@ -621,7 +619,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Admin, InvitorUserType = OrganizationUserType.Owner - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_NoOwner_Throws(Organization organization, OrganizationUser invitor, OrganizationUserInvite invite, SutProvider sutProvider) { @@ -637,7 +635,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Owner, InvitorUserType = OrganizationUserType.Admin - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -656,7 +654,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.User - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -677,7 +675,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.Admin - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_WithCustomType_WhenUseCustomPermissionsIsFalse_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -704,7 +702,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Custom, InvitorUserType = OrganizationUserType.Admin - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_WithCustomType_WhenUseCustomPermissionsIsTrue_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -730,9 +728,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) } [Theory] - [OrganizationCustomize(FlexibleCollections = false)] [BitAutoData(OrganizationUserType.Admin)] - [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.User)] public async Task InviteUsers_WithNonCustomType_WhenUseCustomPermissionsIsFalse_Passes(OrganizationUserType inviteUserType, Organization organization, OrganizationUserInvite invite, @@ -762,9 +758,9 @@ OrganizationUserInvite invite, SutProvider sutProvider) [Theory] [OrganizationInviteCustomize( - InviteeUserType = OrganizationUserType.Manager, + InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -792,7 +788,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.Admin, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -818,7 +814,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Owner - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_NoPermissionsObject_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { @@ -844,7 +840,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -894,7 +890,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUser_InvitingMoreThanOneUser_Throws(Organization organization, OrganizationUserInvite invite, string externalId, OrganizationUser invitor, SutProvider sutProvider) @@ -914,7 +910,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUser_UserAlreadyInvited_Throws(Organization organization, OrganizationUserInvite invite, string externalId, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -979,9 +975,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) currentContext.ManageSso(organization.Id).Returns(true); currentContext.AccessEventLogs(organization.Id).Returns(true); currentContext.AccessImportExport(organization.Id).Returns(true); - currentContext.DeleteAssignedCollections(organization.Id).Returns(true); currentContext.EditAnyCollection(organization.Id).Returns(true); - currentContext.EditAssignedCollections(organization.Id).Returns(true); currentContext.ManageResetPassword(organization.Id).Returns(true); currentContext.GetOrganization(organization.Id) .Returns(new CurrentContextOrganization() @@ -998,7 +992,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -1045,7 +1039,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) [OrganizationInviteCustomize( InviteeUserType = OrganizationUserType.User, InvitorUserType = OrganizationUserType.Custom - ), OrganizationCustomize(FlexibleCollections = false), BitAutoData] + ), OrganizationCustomize, BitAutoData] public async Task InviteUsers_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser invitor, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, @@ -1095,7 +1089,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) await sutProvider.GetDependency().Received(1).LogOrganizationUserEventsAsync(Arg.Any>()); } - [Theory, BitAutoData, OrganizationCustomize(FlexibleCollections = false), OrganizationInviteCustomize] + [Theory, BitAutoData, OrganizationCustomize, OrganizationInviteCustomize] public async Task InviteUsers_WithSecretsManager_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser savingUser, SutProvider sutProvider) @@ -1129,7 +1123,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) !update.MaxAutoscaleSmSeatsChanged)); } - [Theory, BitAutoData, OrganizationCustomize(FlexibleCollections = false), OrganizationInviteCustomize] + [Theory, BitAutoData, OrganizationCustomize, OrganizationInviteCustomize] public async Task InviteUsers_WithSecretsManager_WhenErrorIsThrown_RevertsAutoscaling(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites, OrganizationUser savingUser, SutProvider sutProvider) @@ -1155,9 +1149,11 @@ OrganizationUserInvite invite, SutProvider sutProvider) .ReturnsForAnyArgs(Task.FromResult(0)).AndDoes(x => organization.SmSeats += invitedSmUsers); // Throw error at the end of the try block - sutProvider.GetDependency().RaiseEventAsync(default).ThrowsForAnyArgs(); + sutProvider.GetDependency().RaiseEventAsync(default) + .ThrowsForAnyArgs(); - await Assert.ThrowsAsync(async () => await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites)); + await Assert.ThrowsAsync(async () => + await sutProvider.Sut.InviteUsersAsync(organization.Id, savingUser.Id, systemUser: null, invites)); // OrgUser is reverted // Note: we don't know what their guids are so comparing length is the best we can do @@ -1185,50 +1181,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) }); } - [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] - public async Task InviteUsers_WithFlexibleCollections_WhenInvitingManager_Throws(Organization organization, - OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) - { - invite.Type = OrganizationUserType.Manager; - organization.FlexibleCollections = true; - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency() - .ManageUsers(organization.Id) - .Returns(true); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, - new (OrganizationUserInvite, string)[] { (invite, null) })); - - Assert.Contains("manager role has been deprecated", exception.Message.ToLowerInvariant()); - } - - [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] - public async Task InviteUsers_WithFlexibleCollections_WithAccessAll_Throws(Organization organization, - OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) - { - invite.Type = OrganizationUserType.User; - invite.AccessAll = true; - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency() - .ManageUsers(organization.Id) - .Returns(true); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.InviteUsersAsync(organization.Id, invitor.UserId, systemUser: null, - new (OrganizationUserInvite, string)[] { (invite, null) })); - - Assert.Contains("accessall property has been deprecated", exception.Message.ToLowerInvariant()); - } - private void InviteUserHelper_ArrangeValidPermissions(Organization organization, OrganizationUser savingUser, SutProvider sutProvider) { @@ -1680,6 +1632,68 @@ OrganizationUserInvite invite, SutProvider sutProvider) await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService); } + [Theory, BitAutoData] + public async Task ConfirmUser_vNext_TwoFactorPolicy_NotEnabled_Throws(Organization org, OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, UserWithCalculatedPremium user, + OrganizationUser orgUserAnotherOrg, + [OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, + string key, SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + var organizationUserRepository = sutProvider.GetDependency(); + var organizationRepository = sutProvider.GetDependency(); + var userRepository = sutProvider.GetDependency(); + var policyService = sutProvider.GetDependency(); + var userService = Substitute.For(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; + orgUser.UserId = orgUserAnotherOrg.UserId = user.Id; + organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); + organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg }); + organizationRepository.GetByIdAsync(org.Id).Returns(org); + userRepository.GetManyWithCalculatedPremiumAsync(default).ReturnsForAnyArgs(new[] { user }); + twoFactorPolicy.OrganizationId = org.Id; + policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); + twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is>(ids => ids.Contains(user.Id))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, false) }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService)); + Assert.Contains("User does not have two-step login enabled.", exception.Message); + } + + [Theory, BitAutoData] + public async Task ConfirmUser_vNext_TwoFactorPolicy_Enabled_Success(Organization org, OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, UserWithCalculatedPremium user, + [OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, + string key, SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + var organizationUserRepository = sutProvider.GetDependency(); + var organizationRepository = sutProvider.GetDependency(); + var userRepository = sutProvider.GetDependency(); + var policyService = sutProvider.GetDependency(); + var userService = Substitute.For(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + + org.PlanType = PlanType.EnterpriseAnnually; + orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; + orgUser.UserId = user.Id; + organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); + organizationRepository.GetByIdAsync(org.Id).Returns(org); + userRepository.GetManyWithCalculatedPremiumAsync(default).ReturnsForAnyArgs(new[] { user }); + twoFactorPolicy.OrganizationId = org.Id; + policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); + twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is>(ids => ids.Contains(user.Id))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, true) }); + + await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService); + } + [Theory, BitAutoData] public async Task ConfirmUsers_Success(Organization org, OrganizationUser confirmingUser, @@ -1725,6 +1739,56 @@ OrganizationUserInvite invite, SutProvider sutProvider) Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", result[2].Item2); } + [Theory, BitAutoData] + public async Task ConfirmUsers_vNext_Success(Organization org, + OrganizationUser confirmingUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser1, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser2, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser3, + OrganizationUser anotherOrgUser, UserWithCalculatedPremium user1, UserWithCalculatedPremium user2, UserWithCalculatedPremium user3, + [OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, + [OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy, + string key, SutProvider sutProvider) + { + var organizationUserRepository = sutProvider.GetDependency(); + var organizationRepository = sutProvider.GetDependency(); + var userRepository = sutProvider.GetDependency(); + var policyService = sutProvider.GetDependency(); + var userService = Substitute.For(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + + org.PlanType = PlanType.EnterpriseAnnually; + orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = confirmingUser.OrganizationId = org.Id; + orgUser1.UserId = user1.Id; + orgUser2.UserId = user2.Id; + orgUser3.UserId = user3.Id; + anotherOrgUser.UserId = user3.Id; + var orgUsers = new[] { orgUser1, orgUser2, orgUser3 }; + organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(orgUsers); + organizationRepository.GetByIdAsync(org.Id).Returns(org); + userRepository.GetManyWithCalculatedPremiumAsync(default).ReturnsForAnyArgs(new[] { user1, user2, user3 }); + twoFactorPolicy.OrganizationId = org.Id; + policyService.GetPoliciesApplicableToUserAsync(Arg.Any(), PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); + twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is>(ids => ids.Contains(user1.Id) && ids.Contains(user2.Id) && ids.Contains(user3.Id))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() + { + (user1.Id, true), + (user2.Id, false), + (user3.Id, true) + }); + singleOrgPolicy.OrganizationId = org.Id; + policyService.GetPoliciesApplicableToUserAsync(user3.Id, PolicyType.SingleOrg) + .Returns(new[] { singleOrgPolicy }); + organizationUserRepository.GetManyByManyUsersAsync(default) + .ReturnsForAnyArgs(new[] { orgUser1, orgUser2, orgUser3, anotherOrgUser }); + + var keys = orgUsers.ToDictionary(ou => ou.Id, _ => key); + var result = await sutProvider.Sut.ConfirmUsersAsync_vNext(confirmingUser.OrganizationId, keys, confirmingUser.Id); + Assert.Contains("", result[0].Item2); + Assert.Contains("User does not have two-step login enabled.", result[1].Item2); + Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", result[2].Item2); + } + [Theory, BitAutoData] public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey, string privateKey, SutProvider sutProvider) @@ -1892,15 +1956,17 @@ OrganizationUserInvite invite, SutProvider sutProvider) await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default); } - private void RestoreRevokeUser_Setup(Organization organization, OrganizationUser owner, OrganizationUser organizationUser, SutProvider sutProvider) + private void RestoreRevokeUser_Setup(Organization organization, OrganizationUser restoringUser, + OrganizationUser organizationUser, SutProvider sutProvider, + OrganizationUserType restoringUserType = OrganizationUserType.Owner) { sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); sutProvider.GetDependency().GetByIdAsync(organizationUser.OrganizationId).Returns(organization); sutProvider.GetDependency().OrganizationOwner(organization.Id).Returns(true); sutProvider.GetDependency().ManageUsers(organization.Id).Returns(true); var organizationUserRepository = sutProvider.GetDependency(); - organizationUserRepository.GetManyByOrganizationAsync(organizationUser.OrganizationId, OrganizationUserType.Owner) - .Returns(new[] { owner }); + organizationUserRepository.GetManyByOrganizationAsync(organizationUser.OrganizationId, restoringUserType) + .Returns(new[] { restoringUser }); } [Theory, BitAutoData] @@ -1965,6 +2031,320 @@ OrganizationUserInvite invite, SutProvider sutProvider) .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored, eventSystemUser); } + [Theory, BitAutoData] + public async Task RestoreUser_RestoreThemselves_Fails(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, SutProvider sutProvider) + { + organizationUser.UserId = owner.Id; + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore yourself", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(OrganizationUserType.Admin)] + [BitAutoData(OrganizationUserType.Custom)] + public async Task RestoreUser_AdminRestoreOwner_Fails(OrganizationUserType restoringUserType, Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser restoringUser, + [OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.Owner)] OrganizationUser organizationUser, SutProvider sutProvider) + { + restoringUser.Type = restoringUserType; + RestoreRevokeUser_Setup(organization, restoringUser, organizationUser, sutProvider, OrganizationUserType.Admin); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, restoringUser.Id, userService)); + + Assert.Contains("only owners can restore other owners", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(OrganizationUserStatusType.Invited)] + [BitAutoData(OrganizationUserStatusType.Accepted)] + [BitAutoData(OrganizationUserStatusType.Confirmed)] + public async Task RestoreUser_WithStatusOtherThanRevoked_Fails(OrganizationUserStatusType userStatus, Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser] OrganizationUser organizationUser, SutProvider sutProvider) + { + organizationUser.Status = userStatus; + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("already active", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_WithSingleOrgPolicyEnabled_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser, + SutProvider sutProvider) + { + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + secondOrganizationUser.UserId = organizationUser.UserId; + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser }); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg } }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user until " + + "they leave or remove all other organizations.", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_WithOtherOrganizationSingleOrgPolicyEnabled_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + SutProvider sutProvider) + { + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) + .Returns(true); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user because they are a member of " + + "another organization which forbids it", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_With2FAPolicyEnabled_WithoutUser2FAConfigured_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + SutProvider sutProvider) + { + organizationUser.Email = null; + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } }); + + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + + userService.TwoFactorIsEnabledAsync(Arg.Any()).Returns(false); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user until they enable " + + "two-step login on their user account.", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_With2FAPolicyEnabled_WithUser2FAConfigured_Success( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + SutProvider sutProvider) + { + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } }); + userService.TwoFactorIsEnabledAsync(Arg.Any()).Returns(true); + + await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService); + + await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); + await eventService.Received() + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); + } + + [Theory, BitAutoData] + public async Task RestoreUser_vNext_WithSingleOrgPolicyEnabled_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + secondOrganizationUser.UserId = organizationUser.UserId; + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser }); + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) + .Returns(new[] + { + new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked } + }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user until " + + "they leave or remove all other organizations.", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_vNext_WithOtherOrganizationSingleOrgPolicyEnabled_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + [OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + secondOrganizationUser.UserId = organizationUser.UserId; + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + + twoFactorIsEnabledQuery + .TwoFactorIsEnabledAsync(Arg.Is>(i => i.Contains(organizationUser.UserId.Value))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, true) }); + + sutProvider.GetDependency() + .AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any()) + .Returns(true); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user because they are a member of " + + "another organization which forbids it", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_vNext_With2FAPolicyEnabled_WithoutUser2FAConfigured_Fails( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + organizationUser.Email = null; + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } }); + + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService)); + + Assert.Contains("you cannot restore this user until they enable " + + "two-step login on their user account.", exception.Message.ToLowerInvariant()); + + await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any(), Arg.Any()); + await eventService.DidNotReceiveWithAnyArgs() + .LogOrganizationUserEventAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory, BitAutoData] + public async Task RestoreUser_vNext_With2FAPolicyEnabled_WithUser2FAConfigured_Success( + Organization organization, + [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, + [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, + SutProvider sutProvider) + { + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.MembersTwoFAQueryOptimization).Returns(true); + + organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke + RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider); + var userService = Substitute.For(); + var organizationUserRepository = sutProvider.GetDependency(); + var eventService = sutProvider.GetDependency(); + var twoFactorIsEnabledQuery = sutProvider.GetDependency(); + + sutProvider.GetDependency() + .GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any()) + .Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } }); + + twoFactorIsEnabledQuery + .TwoFactorIsEnabledAsync(Arg.Is>(i => i.Contains(organizationUser.UserId.Value))) + .Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, true) }); + + await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id, userService); + + await organizationUserRepository.Received().RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed); + await eventService.Received() + .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored); + } + [Theory, BitAutoData] public async Task HasConfirmedOwnersExcept_WithConfirmedOwner_ReturnsTrue(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider sutProvider) { @@ -2299,7 +2679,6 @@ OrganizationUserInvite invite, SutProvider sutProvider) [BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.User)] - [BitAutoData(OrganizationUserType.Manager)] public async Task ValidateOrganizationCustomPermissionsEnabledAsync_WithNotCustomType_IsValid( OrganizationUserType newType, Guid organizationId, @@ -2362,6 +2741,15 @@ OrganizationUserInvite invite, SutProvider sutProvider) return Task.FromResult>(orgUsers.Select(u => u.Id).ToList()); } ); + + organizationUserRepository.CreateAsync(Arg.Any(), Arg.Any>()).Returns( + info => + { + var orgUser = info.Arg(); + orgUser.Id = Guid.NewGuid(); + return Task.FromResult(orgUser.Id); + } + ); } // Must set real guids in order for dictionary of guids to not throw aggregate exceptions diff --git a/test/Core.Test/Auth/Identity/EmailTokenProviderTests.cs b/test/Core.Test/Auth/Identity/EmailTwoFactorTokenProviderTests.cs similarity index 88% rename from test/Core.Test/Auth/Identity/EmailTokenProviderTests.cs rename to test/Core.Test/Auth/Identity/EmailTwoFactorTokenProviderTests.cs index 4e85380d7..c5855c234 100644 --- a/test/Core.Test/Auth/Identity/EmailTokenProviderTests.cs +++ b/test/Core.Test/Auth/Identity/EmailTwoFactorTokenProviderTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace Bit.Core.Test.Auth.Identity; -public class EmailTokenProviderTests : BaseTokenProviderTests +public class EmailTwoFactorTokenProviderTests : BaseTokenProviderTests { public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Email; @@ -38,7 +38,7 @@ public class EmailTokenProviderTests : BaseTokenProviderTests metaData, bool expectedResponse, - User user, SutProvider sutProvider) + User user, SutProvider sutProvider) { await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider); } diff --git a/test/Core.Test/Auth/UserFeatures/Registration/RegisterUserCommandTests.cs b/test/Core.Test/Auth/UserFeatures/Registration/RegisterUserCommandTests.cs index 05e4e1ca8..d61a34541 100644 --- a/test/Core.Test/Auth/UserFeatures/Registration/RegisterUserCommandTests.cs +++ b/test/Core.Test/Auth/UserFeatures/Registration/RegisterUserCommandTests.cs @@ -367,4 +367,18 @@ public class RegisterUserCommandTests } + [Theory] + [BitAutoData] + public async Task RegisterUserViaEmailVerificationToken_DisabledOpenRegistration_ThrowsBadRequestException(SutProvider sutProvider, User user, string masterPasswordHash, string emailVerificationToken) + { + // Arrange + sutProvider.GetDependency() + .DisableUserRegistration = true; + + // Act & Assert + var result = await Assert.ThrowsAsync(() => sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken)); + Assert.Equal("Open registration has been disabled by the system administrator.", result.Message); + + } + } diff --git a/test/Core.Test/Auth/UserFeatures/Registration/SendVerificationEmailForRegistrationCommandTests.cs b/test/Core.Test/Auth/UserFeatures/Registration/SendVerificationEmailForRegistrationCommandTests.cs index 627350483..f4f620f8a 100644 --- a/test/Core.Test/Auth/UserFeatures/Registration/SendVerificationEmailForRegistrationCommandTests.cs +++ b/test/Core.Test/Auth/UserFeatures/Registration/SendVerificationEmailForRegistrationCommandTests.cs @@ -31,6 +31,9 @@ public class SendVerificationEmailForRegistrationCommandTests sutProvider.GetDependency() .EnableEmailVerification = true; + sutProvider.GetDependency() + .DisableUserRegistration = false; + sutProvider.GetDependency() .SendRegistrationVerificationEmailAsync(email, Arg.Any()) .Returns(Task.CompletedTask); @@ -63,6 +66,9 @@ public class SendVerificationEmailForRegistrationCommandTests sutProvider.GetDependency() .EnableEmailVerification = true; + sutProvider.GetDependency() + .DisableUserRegistration = false; + var mockedToken = "token"; sutProvider.GetDependency>() .Protect(Arg.Any()) @@ -91,6 +97,9 @@ public class SendVerificationEmailForRegistrationCommandTests sutProvider.GetDependency() .EnableEmailVerification = false; + sutProvider.GetDependency() + .DisableUserRegistration = false; + var mockedToken = "token"; sutProvider.GetDependency>() .Protect(Arg.Any()) @@ -103,6 +112,19 @@ public class SendVerificationEmailForRegistrationCommandTests Assert.Equal(mockedToken, result); } + [Theory] + [BitAutoData] + public async Task SendVerificationEmailForRegistrationCommand_WhenOpenRegistrationDisabled_ThrowsBadRequestException(SutProvider sutProvider, + string email, string name, bool receiveMarketingEmails) + { + // Arrange + sutProvider.GetDependency() + .DisableUserRegistration = true; + + // Act & Assert + await Assert.ThrowsAsync(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails)); + } + [Theory] [BitAutoData] public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationFalse_ThrowsBadRequestException(SutProvider sutProvider, @@ -125,6 +147,9 @@ public class SendVerificationEmailForRegistrationCommandTests public async Task SendVerificationEmailForRegistrationCommand_WhenNullEmail_ThrowsArgumentNullException(SutProvider sutProvider, string name, bool receiveMarketingEmails) { + sutProvider.GetDependency() + .DisableUserRegistration = false; + await Assert.ThrowsAsync(async () => await sutProvider.Sut.Run(null, name, receiveMarketingEmails)); } @@ -133,6 +158,8 @@ public class SendVerificationEmailForRegistrationCommandTests public async Task SendVerificationEmailForRegistrationCommand_WhenEmptyEmail_ThrowsArgumentNullException(SutProvider sutProvider, string name, bool receiveMarketingEmails) { + sutProvider.GetDependency() + .DisableUserRegistration = false; await Assert.ThrowsAsync(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails)); } } diff --git a/test/Core.Test/Auth/UserFeatures/TdeOffboarding/TdeOffboardingPasswordCommandTests.cs b/test/Core.Test/Auth/UserFeatures/TdeOffboarding/TdeOffboardingPasswordCommandTests.cs new file mode 100644 index 000000000..49558783f --- /dev/null +++ b/test/Core.Test/Auth/UserFeatures/TdeOffboarding/TdeOffboardingPasswordCommandTests.cs @@ -0,0 +1,99 @@ +using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Repositories; +using Bit.Core.Auth.UserFeatures.UserMasterPassword; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Repositories; +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.Auth.UserFeatures.UserMasterPassword; + +[SutProviderCustomize] +public class TdeOffboardingPasswordTests +{ + [Theory] + [BitAutoData] + public async Task TdeOffboardingPasswordCommand_Success(SutProvider sutProvider, + User user, string masterPassword, string key, string hint, OrganizationUserOrganizationDetails orgUserDetails, SsoUser ssoUser) + { + // Arrange + user.MasterPassword = null; + + sutProvider.GetDependency() + .UpdatePasswordHash(Arg.Any(), Arg.Any()) + .Returns(IdentityResult.Success); + + orgUserDetails.UseSso = true; + sutProvider.GetDependency() + .GetManyDetailsByUserAsync(user.Id) + .Returns(new List { orgUserDetails }); + + sutProvider.GetDependency() + .GetByUserIdOrganizationIdAsync(orgUserDetails.OrganizationId, user.Id) + .Returns(ssoUser); + + var ssoConfig = new SsoConfig(); + var ssoConfigData = ssoConfig.GetData(); + ssoConfigData.MemberDecryptionType = MemberDecryptionType.MasterPassword; + ssoConfig.SetData(ssoConfigData); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(orgUserDetails.OrganizationId) + .Returns(ssoConfig); + + // Act + var result = await sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint); + + // Assert + Assert.Equal(IdentityResult.Success, result); + } + + [Theory] + [BitAutoData] + public async Task TdeOffboardingPasswordCommand_RejectWithTdeEnabled(SutProvider sutProvider, + User user, string masterPassword, string key, string hint, OrganizationUserOrganizationDetails orgUserDetails, SsoUser ssoUser) + { + // Arrange + user.MasterPassword = null; + + sutProvider.GetDependency() + .UpdatePasswordHash(Arg.Any(), Arg.Any(), true, false) + .Returns(IdentityResult.Success); + + orgUserDetails.UseSso = true; + sutProvider.GetDependency() + .GetManyDetailsByUserAsync(user.Id) + .Returns(new List { orgUserDetails }); + + sutProvider.GetDependency() + .GetByUserIdOrganizationIdAsync(orgUserDetails.OrganizationId, user.Id) + .Returns(ssoUser); + + var ssoConfig = new SsoConfig(); + var ssoConfigData = ssoConfig.GetData(); + ssoConfigData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption; + ssoConfig.SetData(ssoConfigData); + sutProvider.GetDependency() + .GetByOrganizationIdAsync(orgUserDetails.OrganizationId) + .Returns(ssoConfig); + + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint)); + } + + + [Theory] + [BitAutoData] + public async Task TdeOffboardingPasswordCommand_RejectWithMasterPassword(SutProvider sutProvider, + User user, string masterPassword, string key, string hint) + { + // the user already has a master password, so the off-boarding request should fail, since off-boarding only applies to passwordless TDE users + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint)); + } + +} diff --git a/test/Core.Test/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQueryTests.cs b/test/Core.Test/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQueryTests.cs new file mode 100644 index 000000000..8011c52ea --- /dev/null +++ b/test/Core.Test/Auth/UserFeatures/TwoFactorAuth/TwoFactorIsEnabledQueryTests.cs @@ -0,0 +1,337 @@ +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models; +using Bit.Core.Auth.UserFeatures.TwoFactorAuth; +using Bit.Core.Entities; +using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Auth.UserFeatures.TwoFactorAuth; + +[SutProviderCustomize] +public class TwoFactorIsEnabledQueryTests +{ + [Theory] + [BitAutoData(TwoFactorProviderType.Authenticator)] + [BitAutoData(TwoFactorProviderType.Email)] + [BitAutoData(TwoFactorProviderType.Remember)] + [BitAutoData(TwoFactorProviderType.OrganizationDuo)] + [BitAutoData(TwoFactorProviderType.WebAuthn)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeNotRequiringPremium_ReturnsAllTwoFactorEnabled( + TwoFactorProviderType freeProviderType, + SutProvider sutProvider, + List usersWithCalculatedPremium) + { + // Arrange + var userIds = usersWithCalculatedPremium.Select(u => u.Id).ToList(); + var twoFactorProviders = new Dictionary + { + { freeProviderType, new TwoFactorProvider { Enabled = true } } // Does not require premium + }; + + foreach (var user in usersWithCalculatedPremium) + { + user.HasPremiumAccess = false; + user.SetTwoFactorProviders(twoFactorProviders); + } + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.All(userIds.Contains))) + .Returns(usersWithCalculatedPremium); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds); + + // Assert + foreach (var userDetail in usersWithCalculatedPremium) + { + Assert.Contains(result, res => res.userId == userDetail.Id && res.twoFactorIsEnabled == true); + } + } + + [Theory] + [BitAutoData] + public async Task TwoFactorIsEnabledQuery_WithNoTwoFactorEnabled_ReturnsAllTwoFactorDisabled( + SutProvider sutProvider, + List usersWithCalculatedPremium) + { + // Arrange + var userIds = usersWithCalculatedPremium.Select(u => u.Id).ToList(); + var twoFactorProviders = new Dictionary + { + { TwoFactorProviderType.Email, new TwoFactorProvider { Enabled = false } } + }; + + foreach (var user in usersWithCalculatedPremium) + { + user.SetTwoFactorProviders(twoFactorProviders); + } + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.All(userIds.Contains))) + .Returns(usersWithCalculatedPremium); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds); + + // Assert + foreach (var userDetail in usersWithCalculatedPremium) + { + Assert.Contains(result, res => res.userId == userDetail.Id && res.twoFactorIsEnabled == false); + } + } + + [Theory] + [BitAutoData(TwoFactorProviderType.Duo)] + [BitAutoData(TwoFactorProviderType.YubiKey)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_ReturnsMixedResults( + TwoFactorProviderType premiumProviderType, + SutProvider sutProvider, + List usersWithCalculatedPremium) + { + // Arrange + var userIds = usersWithCalculatedPremium.Select(u => u.Id).ToList(); + var twoFactorProviders = new Dictionary + { + { TwoFactorProviderType.Email, new TwoFactorProvider { Enabled = false } }, + { premiumProviderType, new TwoFactorProvider { Enabled = true } } + }; + + foreach (var user in usersWithCalculatedPremium) + { + user.HasPremiumAccess = usersWithCalculatedPremium.IndexOf(user) == 0; // Only the first user has premium access + user.SetTwoFactorProviders(twoFactorProviders); + } + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.All(userIds.Contains))) + .Returns(usersWithCalculatedPremium); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds); + + // Assert + foreach (var userDetail in usersWithCalculatedPremium) + { + Assert.Contains(result, res => res.userId == userDetail.Id && res.twoFactorIsEnabled == userDetail.HasPremiumAccess); + } + } + + [Theory] + [BitAutoData] + public async Task TwoFactorIsEnabledQuery_WithNullTwoFactorProviders_ReturnsAllTwoFactorDisabled( + SutProvider sutProvider, + List usersWithCalculatedPremium) + { + // Arrange + var userIds = usersWithCalculatedPremium.Select(u => u.Id).ToList(); + + foreach (var user in usersWithCalculatedPremium) + { + user.TwoFactorProviders = null; // No two-factor providers configured + } + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.All(userIds.Contains))) + .Returns(usersWithCalculatedPremium); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds); + + // Assert + foreach (var userDetail in usersWithCalculatedPremium) + { + Assert.Contains(result, res => res.userId == userDetail.Id && res.twoFactorIsEnabled == false); + } + } + + [Theory] + [BitAutoData] + public async Task TwoFactorIsEnabledQuery_WithNoUserIds_ReturnsAllTwoFactorDisabled( + SutProvider sutProvider, + List users) + { + // Arrange + foreach (var user in users) + { + user.UserId = null; + } + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(users); + + // Assert + foreach (var user in users) + { + Assert.Contains(result, res => res.user.Equals(user) && res.twoFactorIsEnabled == false); + } + + // No UserIds were supplied so no calls to the UserRepository should have been made + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .GetManyWithCalculatedPremiumAsync(default); + } + + [Theory] + [BitAutoData(TwoFactorProviderType.Authenticator)] + [BitAutoData(TwoFactorProviderType.Email)] + [BitAutoData(TwoFactorProviderType.Remember)] + [BitAutoData(TwoFactorProviderType.OrganizationDuo)] + [BitAutoData(TwoFactorProviderType.WebAuthn)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeNotRequiringPremium_ReturnsTrue( + TwoFactorProviderType freeProviderType, + SutProvider sutProvider, + User user) + { + // Arrange + var twoFactorProviders = new Dictionary + { + { freeProviderType, new TwoFactorProvider { Enabled = true } } + }; + + user.Premium = false; + user.SetTwoFactorProviders(twoFactorProviders); + + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.True(result); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .GetManyWithCalculatedPremiumAsync(default); + } + + [Theory] + [BitAutoData] + public async Task TwoFactorIsEnabledQuery_WithNoTwoFactorEnabled_ReturnsFalse( + SutProvider sutProvider, + User user) + { + // Arrange + var twoFactorProviders = new Dictionary + { + { TwoFactorProviderType.Email, new TwoFactorProvider { Enabled = false } } + }; + + user.SetTwoFactorProviders(twoFactorProviders); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.False(result); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .GetManyWithCalculatedPremiumAsync(default); + } + + [Theory] + [BitAutoData(TwoFactorProviderType.Duo)] + [BitAutoData(TwoFactorProviderType.YubiKey)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_WithoutPremium_ReturnsFalse( + TwoFactorProviderType premiumProviderType, + SutProvider sutProvider, + UserWithCalculatedPremium user) + { + // Arrange + var twoFactorProviders = new Dictionary + { + { premiumProviderType, new TwoFactorProvider { Enabled = true } } + }; + + user.Premium = false; + user.HasPremiumAccess = false; + user.SetTwoFactorProviders(twoFactorProviders); + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.Contains(user.Id))) + .Returns(new List { user }); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.False(result); + } + + [Theory] + [BitAutoData(TwoFactorProviderType.Duo)] + [BitAutoData(TwoFactorProviderType.YubiKey)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_WithUserPremium_ReturnsTrue( + TwoFactorProviderType premiumProviderType, + SutProvider sutProvider, + User user) + { + // Arrange + var twoFactorProviders = new Dictionary + { + { premiumProviderType, new TwoFactorProvider { Enabled = true } } + }; + + user.Premium = true; + user.SetTwoFactorProviders(twoFactorProviders); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.True(result); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .GetManyWithCalculatedPremiumAsync(default); + } + + [Theory] + [BitAutoData(TwoFactorProviderType.Duo)] + [BitAutoData(TwoFactorProviderType.YubiKey)] + public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_WithOrgPremium_ReturnsTrue( + TwoFactorProviderType premiumProviderType, + SutProvider sutProvider, + UserWithCalculatedPremium user) + { + // Arrange + var twoFactorProviders = new Dictionary + { + { premiumProviderType, new TwoFactorProvider { Enabled = true } } + }; + + user.Premium = false; + user.HasPremiumAccess = true; + user.SetTwoFactorProviders(twoFactorProviders); + + sutProvider.GetDependency() + .GetManyWithCalculatedPremiumAsync(Arg.Is>(i => i.Contains(user.Id))) + .Returns(new List { user }); + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.True(result); + } + + [Theory] + [BitAutoData] + public async Task TwoFactorIsEnabledQuery_WithNullTwoFactorProviders_ReturnsFalse( + SutProvider sutProvider, + User user) + { + // Arrange + user.TwoFactorProviders = null; // No two-factor providers configured + + // Act + var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user); + + // Assert + Assert.False(result); + } +} diff --git a/test/Core.Test/AutoFixture/CollectionAccessSelectionFixtures.cs b/test/Core.Test/AutoFixture/CollectionAccessSelectionFixtures.cs index 54b7fb034..923939b47 100644 --- a/test/Core.Test/AutoFixture/CollectionAccessSelectionFixtures.cs +++ b/test/Core.Test/AutoFixture/CollectionAccessSelectionFixtures.cs @@ -8,16 +8,22 @@ namespace Bit.Core.Test.AutoFixture; public class CollectionAccessSelectionCustomization : ICustomization { public bool Manage { get; set; } + public bool ReadOnly { get; set; } + public bool HidePasswords { get; set; } public CollectionAccessSelectionCustomization(bool manage) { Manage = manage; + ReadOnly = !manage; + HidePasswords = !manage; } public void Customize(IFixture fixture) { fixture.Customize(composer => composer - .With(o => o.Manage, Manage)); + .With(o => o.Manage, Manage) + .With(o => o.ReadOnly, ReadOnly) + .With(o => o.HidePasswords, HidePasswords)); } } diff --git a/test/Core.Test/Billing/Services/SubscriberServiceTests.cs b/test/Core.Test/Billing/Services/SubscriberServiceTests.cs index 6c2fdcd9f..6cbb3fb67 100644 --- a/test/Core.Test/Billing/Services/SubscriberServiceTests.cs +++ b/test/Core.Test/Billing/Services/SubscriberServiceTests.cs @@ -5,7 +5,6 @@ using Bit.Core.Billing.Constants; using Bit.Core.Billing.Models; using Bit.Core.Billing.Services.Implementations; using Bit.Core.Enums; -using Bit.Core.Models.BitStripe; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Test.Common.AutoFixture; @@ -29,8 +28,9 @@ namespace Bit.Core.Test.Billing.Services; public class SubscriberServiceTests { #region CancelSubscription + [Theory, BitAutoData] - public async Task CancelSubscription_SubscriptionInactive_ContactSupport( + public async Task CancelSubscription_SubscriptionInactive_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -45,7 +45,7 @@ public class SubscriberServiceTests .SubscriptionGetAsync(organization.GatewaySubscriptionId) .Returns(subscription); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.CancelSubscription(organization, new OffboardingSurveyResponse(), false)); await stripeAdapter @@ -192,9 +192,11 @@ public class SubscriberServiceTests .DidNotReceiveWithAnyArgs() .SubscriptionCancelAsync(Arg.Any(), Arg.Any()); ; } + #endregion #region GetCustomer + [Theory, BitAutoData] public async Task GetCustomer_NullSubscriber_ThrowsArgumentNullException( SutProvider sutProvider) @@ -256,9 +258,11 @@ public class SubscriberServiceTests Assert.Equivalent(customer, gotCustomer); } + #endregion #region GetCustomerOrThrow + [Theory, BitAutoData] public async Task GetCustomerOrThrow_NullSubscriber_ThrowsArgumentNullException( SutProvider sutProvider) @@ -266,17 +270,17 @@ public class SubscriberServiceTests async () => await sutProvider.Sut.GetCustomerOrThrow(null)); [Theory, BitAutoData] - public async Task GetCustomerOrThrow_NoGatewayCustomerId_ContactSupport( + public async Task GetCustomerOrThrow_NoGatewayCustomerId_ThrowsBillingException( Organization organization, SutProvider sutProvider) { organization.GatewayCustomerId = null; - await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetCustomerOrThrow(organization)); + await ThrowsBillingExceptionAsync(async () => await sutProvider.Sut.GetCustomerOrThrow(organization)); } [Theory, BitAutoData] - public async Task GetCustomerOrThrow_NoCustomer_ContactSupport( + public async Task GetCustomerOrThrow_NoCustomer_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -284,11 +288,11 @@ public class SubscriberServiceTests .CustomerGetAsync(organization.GatewayCustomerId) .ReturnsNull(); - await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetCustomerOrThrow(organization)); + await ThrowsBillingExceptionAsync(async () => await sutProvider.Sut.GetCustomerOrThrow(organization)); } [Theory, BitAutoData] - public async Task GetCustomerOrThrow_StripeException_ContactSupport( + public async Task GetCustomerOrThrow_StripeException_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -298,10 +302,10 @@ public class SubscriberServiceTests .CustomerGetAsync(organization.GatewayCustomerId) .ThrowsAsync(stripeException); - await ThrowsContactSupportAsync( + await ThrowsBillingExceptionAsync( async () => await sutProvider.Sut.GetCustomerOrThrow(organization), - "An error occurred while trying to retrieve a Stripe Customer", - stripeException); + message: "An error occurred while trying to retrieve a Stripe customer", + innerException: stripeException); } [Theory, BitAutoData] @@ -319,108 +323,6 @@ public class SubscriberServiceTests Assert.Equivalent(customer, gotCustomer); } - #endregion - - #region GetInvoices - - [Theory, BitAutoData] - public async Task GetInvoices_NullSubscriber_ThrowsArgumentNullException( - SutProvider sutProvider) - => await Assert.ThrowsAsync( - async () => await sutProvider.Sut.GetInvoices(null)); - - [Theory, BitAutoData] - public async Task GetCustomer_NoGatewayCustomerId_ReturnsEmptyList( - Organization organization, - SutProvider sutProvider) - { - organization.GatewayCustomerId = null; - - var invoices = await sutProvider.Sut.GetInvoices(organization); - - Assert.Empty(invoices); - } - - [Theory, BitAutoData] - public async Task GetInvoices_StripeException_ReturnsEmptyList( - Organization organization, - SutProvider sutProvider) - { - sutProvider.GetDependency() - .InvoiceListAsync(Arg.Any()) - .ThrowsAsync(); - - var invoices = await sutProvider.Sut.GetInvoices(organization); - - Assert.Empty(invoices); - } - - [Theory, BitAutoData] - public async Task GetInvoices_NullOptions_Succeeds( - Organization organization, - SutProvider sutProvider) - { - var invoices = new List - { - new () - { - Created = new DateTime(2024, 6, 1), - Number = "2", - Status = "open", - Total = 100000, - HostedInvoiceUrl = "https://example.com/invoice/2", - InvoicePdf = "https://example.com/invoice/2/pdf" - }, - new () - { - Created = new DateTime(2024, 5, 1), - Number = "1", - Status = "paid", - Total = 100000, - HostedInvoiceUrl = "https://example.com/invoice/1", - InvoicePdf = "https://example.com/invoice/1/pdf" - } - }; - - sutProvider.GetDependency() - .InvoiceListAsync(Arg.Is(options => options.Customer == organization.GatewayCustomerId)) - .Returns(invoices); - - var gotInvoices = await sutProvider.Sut.GetInvoices(organization); - - Assert.Equivalent(invoices, gotInvoices); - } - - [Theory, BitAutoData] - public async Task GetInvoices_ProvidedOptions_Succeeds( - Organization organization, - SutProvider sutProvider) - { - var invoices = new List - { - new () - { - Created = new DateTime(2024, 5, 1), - Number = "1", - Status = "paid", - Total = 100000, - } - }; - - sutProvider.GetDependency() - .InvoiceListAsync(Arg.Is( - options => - options.Customer == organization.GatewayCustomerId && - options.Status == "paid")) - .Returns(invoices); - - var gotInvoices = await sutProvider.Sut.GetInvoices(organization, new StripeInvoiceListOptions - { - Status = "paid" - }); - - Assert.Equivalent(invoices, gotInvoices); - } #endregion @@ -795,17 +697,17 @@ public class SubscriberServiceTests async () => await sutProvider.Sut.GetSubscriptionOrThrow(null)); [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_NoGatewaySubscriptionId_ContactSupport( + public async Task GetSubscriptionOrThrow_NoGatewaySubscriptionId_ThrowsBillingException( Organization organization, SutProvider sutProvider) { organization.GatewaySubscriptionId = null; - await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization)); + await ThrowsBillingExceptionAsync(async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization)); } [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_NoSubscription_ContactSupport( + public async Task GetSubscriptionOrThrow_NoSubscription_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -813,11 +715,11 @@ public class SubscriberServiceTests .SubscriptionGetAsync(organization.GatewaySubscriptionId) .ReturnsNull(); - await ThrowsContactSupportAsync(async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization)); + await ThrowsBillingExceptionAsync(async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization)); } [Theory, BitAutoData] - public async Task GetSubscriptionOrThrow_StripeException_ContactSupport( + public async Task GetSubscriptionOrThrow_StripeException_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -827,10 +729,10 @@ public class SubscriberServiceTests .SubscriptionGetAsync(organization.GatewaySubscriptionId) .ThrowsAsync(stripeException); - await ThrowsContactSupportAsync( + await ThrowsBillingExceptionAsync( async () => await sutProvider.Sut.GetSubscriptionOrThrow(organization), - "An error occurred while trying to retrieve a Stripe Subscription", - stripeException); + message: "An error occurred while trying to retrieve a Stripe subscription", + innerException: stripeException); } [Theory, BitAutoData] @@ -911,12 +813,12 @@ public class SubscriberServiceTests #region RemovePaymentMethod [Theory, BitAutoData] - public async Task RemovePaymentMethod_NullSubscriber_ArgumentNullException( + public async Task RemovePaymentMethod_NullSubscriber_ThrowsArgumentNullException( SutProvider sutProvider) => await Assert.ThrowsAsync(() => sutProvider.Sut.RemovePaymentMethod(null)); [Theory, BitAutoData] - public async Task RemovePaymentMethod_Braintree_NoCustomer_ContactSupport( + public async Task RemovePaymentMethod_Braintree_NoCustomer_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -940,7 +842,7 @@ public class SubscriberServiceTests braintreeGateway.Customer.Returns(customerGateway); - await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); await customerGateway.Received(1).FindAsync(braintreeCustomerId); @@ -987,7 +889,7 @@ public class SubscriberServiceTests } [Theory, BitAutoData] - public async Task RemovePaymentMethod_Braintree_CustomerUpdateFails_ContactSupport( + public async Task RemovePaymentMethod_Braintree_CustomerUpdateFails_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -1028,7 +930,7 @@ public class SubscriberServiceTests Arg.Is(request => request.DefaultPaymentMethodToken == null)) .Returns(updateBraintreeCustomerResult); - await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); await customerGateway.Received(1).FindAsync(braintreeCustomerId); @@ -1042,7 +944,7 @@ public class SubscriberServiceTests } [Theory, BitAutoData] - public async Task RemovePaymentMethod_Braintree_PaymentMethodDeleteFails_RollBack_ContactSupport( + public async Task RemovePaymentMethod_Braintree_PaymentMethodDeleteFails_RollBack_ThrowsBillingException( Organization organization, SutProvider sutProvider) { @@ -1086,7 +988,7 @@ public class SubscriberServiceTests paymentMethodGateway.DeleteAsync(paymentMethod.Token).Returns(deleteBraintreePaymentMethodResult); - await ThrowsContactSupportAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.RemovePaymentMethod(organization)); await customerGateway.Received(1).FindAsync(braintreeCustomerId); @@ -1206,42 +1108,42 @@ public class SubscriberServiceTests #region UpdatePaymentMethod [Theory, BitAutoData] - public async Task UpdatePaymentMethod_NullSubscriber_ArgumentNullException( + public async Task UpdatePaymentMethod_NullSubscriber_ThrowsArgumentNullException( SutProvider sutProvider) => await Assert.ThrowsAsync(() => sutProvider.Sut.UpdatePaymentMethod(null, null)); [Theory, BitAutoData] - public async Task UpdatePaymentMethod_NullTokenizedPaymentMethod_ArgumentNullException( + public async Task UpdatePaymentMethod_NullTokenizedPaymentMethod_ThrowsArgumentNullException( Provider provider, SutProvider sutProvider) => await Assert.ThrowsAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, null)); [Theory, BitAutoData] - public async Task UpdatePaymentMethod_NoToken_ContactSupport( + public async Task UpdatePaymentMethod_NoToken_ThrowsBillingException( Provider provider, SutProvider sutProvider) { sutProvider.GetDependency().CustomerGetAsync(provider.GatewayCustomerId) .Returns(new Customer()); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.Card, null))); } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_UnsupportedPaymentMethod_ContactSupport( + public async Task UpdatePaymentMethod_UnsupportedPaymentMethod_ThrowsBillingException( Provider provider, SutProvider sutProvider) { sutProvider.GetDependency().CustomerGetAsync(provider.GatewayCustomerId) .Returns(new Customer()); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.BitPay, "TOKEN"))); } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_BankAccount_IncorrectNumberOfSetupIntentsForToken_ContactSupport( + public async Task UpdatePaymentMethod_BankAccount_IncorrectNumberOfSetupIntentsForToken_ThrowsBillingException( Provider provider, SutProvider sutProvider) { @@ -1253,7 +1155,7 @@ public class SubscriberServiceTests stripeAdapter.SetupIntentList(Arg.Is(options => options.PaymentMethod == "TOKEN")) .Returns([new SetupIntent(), new SetupIntent()]); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.BankAccount, "TOKEN"))); } @@ -1348,7 +1250,7 @@ public class SubscriberServiceTests } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_Braintree_NullCustomer_ContactSupport( + public async Task UpdatePaymentMethod_Braintree_NullCustomer_ThrowsBillingException( Provider provider, SutProvider sutProvider) { @@ -1368,13 +1270,13 @@ public class SubscriberServiceTests customerGateway.FindAsync(braintreeCustomerId).ReturnsNull(); - await ThrowsContactSupportAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); await paymentMethodGateway.DidNotReceiveWithAnyArgs().CreateAsync(Arg.Any()); } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_Braintree_ReplacePaymentMethod_CreatePaymentMethodFails_ContactSupport( + public async Task UpdatePaymentMethod_Braintree_ReplacePaymentMethod_CreatePaymentMethodFails_ThrowsBillingException( Provider provider, SutProvider sutProvider) { @@ -1406,13 +1308,13 @@ public class SubscriberServiceTests options => options.CustomerId == braintreeCustomerId && options.PaymentMethodNonce == "TOKEN")) .Returns(createPaymentMethodResult); - await ThrowsContactSupportAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); await customerGateway.DidNotReceiveWithAnyArgs().UpdateAsync(Arg.Any(), Arg.Any()); } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_Braintree_ReplacePaymentMethod_UpdateCustomerFails_DeletePaymentMethod_ContactSupport( + public async Task UpdatePaymentMethod_Braintree_ReplacePaymentMethod_UpdateCustomerFails_DeletePaymentMethod_ThrowsBillingException( Provider provider, SutProvider sutProvider) { @@ -1458,7 +1360,7 @@ public class SubscriberServiceTests options.DefaultPaymentMethodToken == createPaymentMethodResult.Target.Token)) .Returns(updateCustomerResult); - await ThrowsContactSupportAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); await paymentMethodGateway.Received(1).DeleteAsync(createPaymentMethodResult.Target.Token); } @@ -1531,7 +1433,7 @@ public class SubscriberServiceTests } [Theory, BitAutoData] - public async Task UpdatePaymentMethod_Braintree_CreateCustomer_CustomerUpdateFails_ContactSupport( + public async Task UpdatePaymentMethod_Braintree_CreateCustomer_CustomerUpdateFails_ThrowsBillingException( Provider provider, SutProvider sutProvider) { @@ -1564,7 +1466,7 @@ public class SubscriberServiceTests options.PaymentMethodNonce == "TOKEN")) .Returns(createCustomerResult); - await ThrowsContactSupportAsync(() => + await ThrowsBillingExceptionAsync(() => sutProvider.Sut.UpdatePaymentMethod(provider, new TokenizedPaymentMethodDTO(PaymentMethodType.PayPal, "TOKEN"))); @@ -1648,7 +1550,7 @@ public class SubscriberServiceTests stripeAdapter.CustomerGetAsync(provider.GatewayCustomerId, Arg.Is( options => options.Expand.Contains("tax_ids"))).Returns(customer); - var taxInformation = new TaxInformationDTO( + var taxInformation = new TaxInformation( "US", "12345", "123456789", @@ -1685,9 +1587,9 @@ public class SubscriberServiceTests () => sutProvider.Sut.VerifyBankAccount(null, (0, 0))); [Theory, BitAutoData] - public async Task VerifyBankAccount_NoSetupIntentId_ContactSupport( + public async Task VerifyBankAccount_NoSetupIntentId_ThrowsBillingException( Provider provider, - SutProvider sutProvider) => await ThrowsContactSupportAsync(() => sutProvider.Sut.VerifyBankAccount(provider, (1, 1))); + SutProvider sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, (1, 1))); [Theory, BitAutoData] public async Task VerifyBankAccount_MakesCorrectInvocations( diff --git a/test/Core.Test/Billing/Utilities.cs b/test/Core.Test/Billing/Utilities.cs index a66feebef..79383af6e 100644 --- a/test/Core.Test/Billing/Utilities.cs +++ b/test/Core.Test/Billing/Utilities.cs @@ -1,23 +1,22 @@ using Bit.Core.Billing; using Xunit; -using static Bit.Core.Billing.Utilities; - namespace Bit.Core.Test.Billing; public static class Utilities { - public static async Task ThrowsContactSupportAsync( + public static async Task ThrowsBillingExceptionAsync( Func function, - string internalMessage = null, + string response = null, + string message = null, Exception innerException = null) { - var contactSupport = ContactSupport(internalMessage, innerException); + var expected = new BillingException(response, message, innerException); - var exception = await Assert.ThrowsAsync(function); + var actual = await Assert.ThrowsAsync(function); - Assert.Equal(contactSupport.ClientFriendlyMessage, exception.ClientFriendlyMessage); - Assert.Equal(contactSupport.Message, exception.Message); - Assert.Equal(contactSupport.InnerException, exception.InnerException); + Assert.Equal(expected.Response, actual.Response); + Assert.Equal(expected.Message, actual.Message); + Assert.Equal(expected.InnerException, actual.InnerException); } } diff --git a/test/Core.Test/Services/CollectionServiceTests.cs b/test/Core.Test/Services/CollectionServiceTests.cs index 1981d681f..7169962cf 100644 --- a/test/Core.Test/Services/CollectionServiceTests.cs +++ b/test/Core.Test/Services/CollectionServiceTests.cs @@ -77,7 +77,7 @@ public class CollectionServiceTest [Theory, BitAutoData] public async Task SaveAsync_OrganizationNotUseGroup_CreateCollectionWithoutGroupsInRepository(Collection collection, - IEnumerable groups, [CollectionAccessSelectionCustomize(true)] IEnumerable users, + [CollectionAccessSelectionCustomize] IEnumerable groups, [CollectionAccessSelectionCustomize(true)] IEnumerable users, Organization organization, SutProvider sutProvider) { collection.Id = default; @@ -112,11 +112,7 @@ public class CollectionServiceTest [CollectionAccessSelectionCustomize] IEnumerable users, SutProvider sutProvider) { collection.Id = default; - organization.FlexibleCollections = true; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency() - .IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1, Arg.Any()) - .Returns(true); organization.AllowAdminAccessToAllCollectionItems = false; var ex = await Assert.ThrowsAsync(() => sutProvider.Sut.SaveAsync(collection, null, users)); @@ -181,27 +177,6 @@ public class CollectionServiceTest .LogOrganizationUserEventAsync(default, default); } - [Theory, BitAutoData] - public async Task GetOrganizationCollectionsAsync_WithViewAssignedCollectionsTrue_ReturnsAssignedCollections( - CollectionDetails collectionDetails, Guid organizationId, Guid userId, SutProvider sutProvider) - { - collectionDetails.OrganizationId = organizationId; - - sutProvider.GetDependency().UserId.Returns(userId); - sutProvider.GetDependency() - .GetManyByUserIdAsync(userId, Arg.Any()) - .Returns(new List { collectionDetails }); - sutProvider.GetDependency().ViewAssignedCollections(organizationId).Returns(true); - - var result = await sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId); - - Assert.Single(result); - Assert.Equal(collectionDetails, result.First()); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdAsync(default); - await sutProvider.GetDependency().Received(1).GetManyByUserIdAsync(userId, Arg.Any()); - } - [Theory, BitAutoData] public async Task GetOrganizationCollectionsAsync_WithViewAllCollectionsTrue_ReturnsAllOrganizationCollections( Collection collection, Guid organizationId, Guid userId, SutProvider sutProvider) @@ -210,7 +185,6 @@ public class CollectionServiceTest sutProvider.GetDependency() .GetManyByOrganizationIdAsync(organizationId) .Returns(new List { collection }); - sutProvider.GetDependency().ViewAssignedCollections(organizationId).Returns(true); sutProvider.GetDependency().ViewAllCollections(organizationId).Returns(true); var result = await sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId); @@ -219,18 +193,16 @@ public class CollectionServiceTest Assert.Equal(collection, result.First()); await sutProvider.GetDependency().Received(1).GetManyByOrganizationIdAsync(organizationId); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsAsync_WithViewAssignedCollectionsFalse_ThrowsBadRequestException( Guid organizationId, SutProvider sutProvider) { - sutProvider.GetDependency().ViewAssignedCollections(organizationId).Returns(false); - await Assert.ThrowsAsync(() => sutProvider.Sut.GetOrganizationCollectionsAsync(organizationId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdAsync(default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default, default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default); } } diff --git a/test/Core.Test/Services/StripePaymentServiceTests.cs b/test/Core.Test/Services/StripePaymentServiceTests.cs index 4d20fd2c6..e15f07b11 100644 --- a/test/Core.Test/Services/StripePaymentServiceTests.cs +++ b/test/Core.Test/Services/StripePaymentServiceTests.cs @@ -725,7 +725,6 @@ public class StripePaymentServiceTests AmountDue = 0 }); stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { }); - featureService.IsEnabled(FeatureFlagKeys.PM5766AutomaticTax).Returns(true); var upgrade = new OrganizationUpgrade() { diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index e66de5698..19ef6991d 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -89,10 +89,10 @@ public class UserServiceTests .CanGenerateTwoFactorTokenAsync(Arg.Any>(), user) .Returns(Task.FromResult(true)); userTwoFactorTokenProvider - .GenerateAsync("2faEmail:" + email, Arg.Any>(), user) + .GenerateAsync("TwoFactor", Arg.Any>(), user) .Returns(Task.FromResult(token)); - sutProvider.Sut.RegisterTokenProvider("Email", userTwoFactorTokenProvider); + sutProvider.Sut.RegisterTokenProvider("Custom_Email", userTwoFactorTokenProvider); user.SetTwoFactorProviders(new Dictionary { diff --git a/test/Core.Test/Vault/Services/CipherServiceTests.cs b/test/Core.Test/Vault/Services/CipherServiceTests.cs index 41bad8b00..0df8f6749 100644 --- a/test/Core.Test/Vault/Services/CipherServiceTests.cs +++ b/test/Core.Test/Vault/Services/CipherServiceTests.cs @@ -26,7 +26,7 @@ namespace Bit.Core.Test.Services; public class CipherServiceTests { [Theory, BitAutoData] - public async Task ImportCiphersAsync_IntoOrganization_WithFlexibleCollectionsDisabled_Success( + public async Task ImportCiphersAsync_IntoOrganization_Success( Organization organization, Guid importingUserId, OrganizationUser importingOrganizationUser, @@ -35,63 +35,6 @@ public class CipherServiceTests SutProvider sutProvider) { organization.MaxCollections = null; - organization.FlexibleCollections = false; - importingOrganizationUser.OrganizationId = organization.Id; - - foreach (var collection in collections) - { - collection.OrganizationId = organization.Id; - } - - foreach (var cipher in ciphers) - { - cipher.OrganizationId = organization.Id; - } - - KeyValuePair[] collectionRelationships = { - new(0, 0), - new(1, 1), - new(2, 2) - }; - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency() - .GetByOrganizationAsync(organization.Id, importingUserId) - .Returns(importingOrganizationUser); - - // Set up a collection that already exists in the organization - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(organization.Id) - .Returns(new List { collections[0] }); - - await sutProvider.Sut.ImportCiphersAsync(collections, ciphers, collectionRelationships, importingUserId); - - await sutProvider.GetDependency().Received(1).CreateAsync( - ciphers, - Arg.Is>(cols => cols.Count() == collections.Count - 1 && - !cols.Any(c => c.Id == collections[0].Id) && // Check that the collection that already existed in the organization was not added - cols.All(c => collections.Any(x => c.Name == x.Name))), - Arg.Is>(c => c.Count() == ciphers.Count), - Arg.Is>(i => !i.Any())); - await sutProvider.GetDependency().Received(1).PushSyncVaultAsync(importingUserId); - await sutProvider.GetDependency().Received(1).RaiseEventAsync( - Arg.Is(e => e.Type == ReferenceEventType.VaultImported)); - } - - [Theory, BitAutoData] - public async Task ImportCiphersAsync_IntoOrganization_WithFlexibleCollectionsEnabled_Success( - Organization organization, - Guid importingUserId, - OrganizationUser importingOrganizationUser, - List collections, - List ciphers, - SutProvider sutProvider) - { - organization.MaxCollections = null; - organization.FlexibleCollections = true; importingOrganizationUser.OrganizationId = organization.Id; foreach (var collection in collections) @@ -733,7 +676,7 @@ public class CipherServiceTests cipher.RevisionDate = previousRevisionDate; } - sutProvider.GetDependency().GetManyByUserIdAsync(restoringUserId, useFlexibleCollections: Arg.Any()).Returns(ciphers); + sutProvider.GetDependency().GetManyByUserIdAsync(restoringUserId).Returns(ciphers); var revisionDate = previousRevisionDate + TimeSpan.FromMinutes(1); sutProvider.GetDependency().RestoreAsync(Arg.Any>(), restoringUserId).Returns(revisionDate); @@ -847,7 +790,7 @@ public class CipherServiceTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyOrganizationDetailsByOrganizationIdAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreByIdsOrganizationIdAsync(default, default); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default, useFlexibleCollections: default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreAsync(default, default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogCipherEventsAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().PushSyncCiphersAsync(default); diff --git a/test/Identity.IntegrationTest/Controllers/AccountsControllerTests.cs b/test/Identity.IntegrationTest/Controllers/AccountsControllerTests.cs index 13ab748e4..c5f10aacb 100644 --- a/test/Identity.IntegrationTest/Controllers/AccountsControllerTests.cs +++ b/test/Identity.IntegrationTest/Controllers/AccountsControllerTests.cs @@ -62,6 +62,28 @@ public class AccountsControllerTests : IClassFixture Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); } + [Theory, BitAutoData] + public async Task PostRegisterSendEmailVerification_DisabledOpenRegistration_ThrowsBadRequestException(string name, bool receiveMarketingEmails) + { + + // Localize substitutions to this test. + var localFactory = new IdentityApplicationFactory(); + localFactory.UpdateConfiguration("globalSettings:disableUserRegistration", "true"); + + var email = $"test+register+{name}@email.com"; + var model = new RegisterSendVerificationEmailRequestModel + { + Email = email, + Name = name, + ReceiveMarketingEmails = receiveMarketingEmails + }; + + var context = await localFactory.PostRegisterSendEmailVerificationAsync(model); + + Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode); + } + + [Theory] [BitAutoData(true)] [BitAutoData(false)] @@ -198,6 +220,38 @@ public class AccountsControllerTests : IClassFixture Assert.Equal(kdfParallelism, user.KdfParallelism); } + + [Theory, BitAutoData] + public async Task RegistrationWithEmailVerification_OpenRegistrationDisabled_ThrowsBadRequestException([Required] string name, string emailVerificationToken, + [StringLength(1000), Required] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, [Required] string userSymmetricKey, + [Required] KeysRequestModel userAsymmetricKeys, int kdfMemory, int kdfParallelism) + { + // Localize substitutions to this test. + var localFactory = new IdentityApplicationFactory(); + localFactory.UpdateConfiguration("globalSettings:disableUserRegistration", "true"); + + var email = $"test+register+{name}@email.com"; + + // Now we call the finish registration endpoint with the email verification token + var registerFinishReqModel = new RegisterFinishRequestModel + { + Email = email, + MasterPasswordHash = masterPasswordHash, + MasterPasswordHint = masterPasswordHint, + EmailVerificationToken = emailVerificationToken, + Kdf = KdfType.PBKDF2_SHA256, + KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default, + UserSymmetricKey = userSymmetricKey, + UserAsymmetricKeys = userAsymmetricKeys, + KdfMemory = kdfMemory, + KdfParallelism = kdfParallelism + }; + + var postRegisterFinishHttpContext = await localFactory.PostRegisterFinishAsync(registerFinishReqModel); + + Assert.Equal(StatusCodes.Status400BadRequest, postRegisterFinishHttpContext.Response.StatusCode); + } + [Theory, BitAutoData] public async Task RegistrationWithEmailVerification_WithOrgInviteToken_Succeeds( [StringLength(1000)] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, string userSymmetricKey, @@ -268,6 +322,43 @@ public class AccountsControllerTests : IClassFixture Assert.Equal(kdfParallelism, user.KdfParallelism); } + [Theory, BitAutoData] + public async Task PostRegisterVerificationEmailClicked_Success( + [Required, StringLength(20)] string name, + string emailVerificationToken) + { + // Arrange + // Localize substitutions to this test. + var localFactory = new IdentityApplicationFactory(); + + var email = $"test+register+{name}@email.com"; + var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email); + + localFactory.SubstituteService>(emailVerificationTokenDataProtectorFactory => + { + emailVerificationTokenDataProtectorFactory.TryUnprotect(Arg.Is(emailVerificationToken), out Arg.Any()) + .Returns(callInfo => + { + callInfo[1] = registrationEmailVerificationTokenable; + return true; + }); + }); + + var requestModel = new RegisterVerificationEmailClickedRequestModel + { + Email = email, + EmailVerificationToken = emailVerificationToken + }; + + // Act + var httpContext = await localFactory.PostRegisterVerificationEmailClicked(requestModel); + + var body = await httpContext.ReadBodyAsStringAsync(); + + // Assert + Assert.Equal(StatusCodes.Status200OK, httpContext.Response.StatusCode); + } + private async Task CreateUserAsync(string email, string name, IdentityApplicationFactory factory = null) { var factoryToUse = factory ?? _factory; diff --git a/test/Identity.IntegrationTest/Endpoints/IdentityServerSsoTests.cs b/test/Identity.IntegrationTest/Endpoints/IdentityServerSsoTests.cs index 1856ddb95..0189032c2 100644 --- a/test/Identity.IntegrationTest/Endpoints/IdentityServerSsoTests.cs +++ b/test/Identity.IntegrationTest/Endpoints/IdentityServerSsoTests.cs @@ -178,6 +178,11 @@ public class IdentityServerSsoTests { Assert.Equal("HasManageResetPasswordPermission", p.Name); Assert.Equal(JsonValueKind.False, p.Value.ValueKind); + }, + p => + { + Assert.Equal("IsTdeOffboarding", p.Name); + Assert.Equal(JsonValueKind.False, p.Value.ValueKind); }); } @@ -194,6 +199,8 @@ public class IdentityServerSsoTests var userRepository = factory.Services.GetRequiredService(); var user = await userRepository.GetByEmailAsync(TestEmail); + Assert.NotNull(user); + var deviceRepository = factory.Services.GetRequiredService(); await deviceRepository.CreateAsync(new Device { @@ -219,6 +226,7 @@ public class IdentityServerSsoTests // "HasAdminApproval": true, // "HasLoginApprovingDevice": true, // "HasManageResetPasswordPermission": false + // "IsTdeOffboarding": false // } // } @@ -242,6 +250,11 @@ public class IdentityServerSsoTests { Assert.Equal("HasManageResetPasswordPermission", p.Name); Assert.Equal(JsonValueKind.False, p.Value.ValueKind); + }, + p => + { + Assert.Equal("IsTdeOffboarding", p.Name); + Assert.Equal(JsonValueKind.False, p.Value.ValueKind); }); } @@ -266,6 +279,7 @@ public class IdentityServerSsoTests var deviceIdentifier = $"test_id_{Guid.NewGuid()}"; var user = await factory.Services.GetRequiredService().GetByEmailAsync(TestEmail); + Assert.NotNull(user); const string expectedPrivateKey = "2.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==|QmFzZTY0UGFydA=="; const string expectedUserKey = "2.QmFzZTY0UGFydA==|QmFzZTY0UGFydA==|QmFzZTY0UGFydA=="; @@ -394,6 +408,7 @@ public class IdentityServerSsoTests }, challenge); var user = await factory.Services.GetRequiredService().GetByEmailAsync(TestEmail); + Assert.NotNull(user); var providerRepository = factory.Services.GetRequiredService(); var provider = await providerRepository.CreateAsync(new Provider { @@ -540,7 +555,7 @@ public class IdentityServerSsoTests RequestedScopes = new[] { "api", "offline_access" }, CodeChallenge = challenge.Sha256(), CodeChallengeMethod = "plain", // - Subject = null, // Temporarily set it to null + Subject = null!, // Temporarily set it to null }; factory.SubstituteService(service => @@ -558,6 +573,7 @@ public class IdentityServerSsoTests var userRepository = factory.Services.GetRequiredService(); var user = await userRepository.GetByEmailAsync(TestEmail); + Assert.NotNull(user); var organizationRepository = factory.Services.GetRequiredService(); var organization = await organizationRepository.CreateAsync(new Organization @@ -610,7 +626,7 @@ public class IdentityServerSsoTests { var userRepository = factory.Services.GetRequiredService(); var user = await userRepository.GetByEmailAsync(TestEmail); - + Assert.NotNull(user); changeUser(user); await userRepository.ReplaceAsync(user); diff --git a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs index a41c4449f..ae64b832f 100644 --- a/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs +++ b/test/Identity.IntegrationTest/Endpoints/IdentityServerTests.cs @@ -145,7 +145,6 @@ public class IdentityServerTests : IClassFixture [BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.User)] - [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Custom)] public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersTrue_Success(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername) { @@ -173,7 +172,6 @@ public class IdentityServerTests : IClassFixture [BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.User)] - [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Custom)] public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersFalse_Success(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername) { @@ -201,7 +199,6 @@ public class IdentityServerTests : IClassFixture [BitAutoData(OrganizationUserType.Owner)] [BitAutoData(OrganizationUserType.Admin)] [BitAutoData(OrganizationUserType.User)] - [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Custom)] public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersTrue_Throw(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername) { @@ -253,7 +250,6 @@ public class IdentityServerTests : IClassFixture [Theory] [BitAutoData(OrganizationUserType.User)] - [BitAutoData(OrganizationUserType.Manager)] [BitAutoData(OrganizationUserType.Custom)] public async Task TokenEndpoint_GrantTypePassword_WithNonOwnerOrAdmin_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersFalse_Throws(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername) { diff --git a/test/Identity.IntegrationTest/openid-configuration.json b/test/Identity.IntegrationTest/openid-configuration.json index 8cd464d1d..23e5a67c0 100644 --- a/test/Identity.IntegrationTest/openid-configuration.json +++ b/test/Identity.IntegrationTest/openid-configuration.json @@ -26,7 +26,6 @@ "device", "orgowner", "orgadmin", - "orgmanager", "orguser", "orgcustom", "providerprovideradmin", diff --git a/test/Identity.Test/Controllers/AccountsControllerTests.cs b/test/Identity.Test/Controllers/AccountsControllerTests.cs index 594679ca0..8de0282bb 100644 --- a/test/Identity.Test/Controllers/AccountsControllerTests.cs +++ b/test/Identity.Test/Controllers/AccountsControllerTests.cs @@ -41,6 +41,8 @@ public class AccountsControllerTests : IDisposable private readonly ISendVerificationEmailForRegistrationCommand _sendVerificationEmailForRegistrationCommand; private readonly IReferenceEventService _referenceEventService; private readonly IFeatureService _featureService; + private readonly IDataProtectorTokenFactory _registrationEmailVerificationTokenDataFactory; + public AccountsControllerTests() { @@ -54,6 +56,8 @@ public class AccountsControllerTests : IDisposable _sendVerificationEmailForRegistrationCommand = Substitute.For(); _referenceEventService = Substitute.For(); _featureService = Substitute.For(); + _registrationEmailVerificationTokenDataFactory = Substitute.For>(); + _sut = new AccountsController( _currentContext, _logger, @@ -64,7 +68,8 @@ public class AccountsControllerTests : IDisposable _getWebAuthnLoginCredentialAssertionOptionsCommand, _sendVerificationEmailForRegistrationCommand, _referenceEventService, - _featureService + _featureService, + _registrationEmailVerificationTokenDataFactory ); } @@ -380,4 +385,105 @@ public class AccountsControllerTests : IDisposable Assert.Equal(duplicateUserEmailErrorDesc, modelError.ErrorMessage); } + + [Theory, BitAutoData] + public async Task PostRegisterVerificationEmailClicked_WhenTokenIsValid_ShouldReturnOk(string email, string emailVerificationToken) + { + // Arrange + var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email); + _registrationEmailVerificationTokenDataFactory + .TryUnprotect(emailVerificationToken, out Arg.Any()) + .Returns(callInfo => + { + callInfo[1] = registrationEmailVerificationTokenable; + return true; + }); + + _userRepository.GetByEmailAsync(email).ReturnsNull(); // no existing user + + var requestModel = new RegisterVerificationEmailClickedRequestModel + { + Email = email, + EmailVerificationToken = emailVerificationToken + }; + + // Act + var result = await _sut.PostRegisterVerificationEmailClicked(requestModel); + + // Assert + var okResult = Assert.IsType(result); + Assert.Equal(200, okResult.StatusCode); + + await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is(e => + e.Type == ReferenceEventType.SignupEmailClicked + && e.EmailVerificationTokenValid == true + && e.UserAlreadyExists == false + )); + } + + [Theory, BitAutoData] + public async Task PostRegisterVerificationEmailClicked_WhenTokenIsInvalid_ShouldReturnBadRequest(string email, string emailVerificationToken) + { + // Arrange + var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable("wrongEmail"); + _registrationEmailVerificationTokenDataFactory + .TryUnprotect(emailVerificationToken, out Arg.Any()) + .Returns(callInfo => + { + callInfo[1] = registrationEmailVerificationTokenable; + return true; + }); + + _userRepository.GetByEmailAsync(email).ReturnsNull(); // no existing user + + var requestModel = new RegisterVerificationEmailClickedRequestModel + { + Email = email, + EmailVerificationToken = emailVerificationToken + }; + + // Act & assert + await Assert.ThrowsAsync(() => _sut.PostRegisterVerificationEmailClicked(requestModel)); + + await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is(e => + e.Type == ReferenceEventType.SignupEmailClicked + && e.EmailVerificationTokenValid == false + && e.UserAlreadyExists == false + )); + } + + + [Theory, BitAutoData] + public async Task PostRegisterVerificationEmailClicked_WhenTokenIsValidButExistingUser_ShouldReturnBadRequest(string email, string emailVerificationToken, User existingUser) + { + // Arrange + var registrationEmailVerificationTokenable = new RegistrationEmailVerificationTokenable(email); + _registrationEmailVerificationTokenDataFactory + .TryUnprotect(emailVerificationToken, out Arg.Any()) + .Returns(callInfo => + { + callInfo[1] = registrationEmailVerificationTokenable; + return true; + }); + + _userRepository.GetByEmailAsync(email).Returns(existingUser); + + var requestModel = new RegisterVerificationEmailClickedRequestModel + { + Email = email, + EmailVerificationToken = emailVerificationToken + }; + + // Act & assert + await Assert.ThrowsAsync(() => _sut.PostRegisterVerificationEmailClicked(requestModel)); + + await _referenceEventService.Received(1).RaiseEventAsync(Arg.Is(e => + e.Type == ReferenceEventType.SignupEmailClicked + && e.EmailVerificationTokenValid == true + && e.UserAlreadyExists == true + )); + } + + + } diff --git a/test/Identity.Test/IdentityServer/UserDecryptionOptionsBuilderTests.cs b/test/Identity.Test/IdentityServer/UserDecryptionOptionsBuilderTests.cs index fe2d4ad18..89940275b 100644 --- a/test/Identity.Test/IdentityServer/UserDecryptionOptionsBuilderTests.cs +++ b/test/Identity.Test/IdentityServer/UserDecryptionOptionsBuilderTests.cs @@ -3,6 +3,7 @@ using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; using Bit.Core.Context; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Identity.IdentityServer; using Bit.Identity.Utilities; @@ -131,6 +132,40 @@ public class UserDecryptionOptionsBuilderTests Assert.True(result.TrustedDeviceOption?.HasManageResetPasswordPermission); } + [Theory, BitAutoData] + public async Task Build_WhenIsOwnerInvite_ShouldReturnHasManageResetPasswordPermissionTrue( + SsoConfig ssoConfig, + SsoConfigurationData configurationData, + OrganizationUser organizationUser, + User user) + { + configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption; + ssoConfig.Data = configurationData.Serialize(); + organizationUser.Type = OrganizationUserType.Owner; + _organizationUserRepository.GetByOrganizationAsync(ssoConfig.OrganizationId, user.Id).Returns(organizationUser); + + var result = await _builder.ForUser(user).WithSso(ssoConfig).BuildAsync(); + + Assert.True(result.TrustedDeviceOption?.HasManageResetPasswordPermission); + } + + [Theory, BitAutoData] + public async Task Build_WhenIsAdminInvite_ShouldReturnHasManageResetPasswordPermissionTrue( + SsoConfig ssoConfig, + SsoConfigurationData configurationData, + OrganizationUser organizationUser, + User user) + { + configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption; + ssoConfig.Data = configurationData.Serialize(); + organizationUser.Type = OrganizationUserType.Admin; + _organizationUserRepository.GetByOrganizationAsync(ssoConfig.OrganizationId, user.Id).Returns(organizationUser); + + var result = await _builder.ForUser(user).WithSso(ssoConfig).BuildAsync(); + + Assert.True(result.TrustedDeviceOption?.HasManageResetPasswordPermission); + } + [Theory, BitAutoData] public async Task Build_WhenUserHasEnrolledIntoPasswordReset_ShouldReturnHasAdminApprovalTrue( SsoConfig ssoConfig, diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs index 0598bf2f4..4ef057286 100644 --- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs @@ -252,6 +252,5 @@ public class OrganizationUserRepositoryTests Assert.Equal(organization.SmServiceAccounts, result.SmServiceAccounts); Assert.Equal(organization.LimitCollectionCreationDeletion, result.LimitCollectionCreationDeletion); Assert.Equal(organization.AllowAdminAccessToAllCollectionItems, result.AllowAdminAccessToAllCollectionItems); - Assert.Equal(organization.FlexibleCollections, result.FlexibleCollections); } } diff --git a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj index 831cfb504..b8feda50a 100644 --- a/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj +++ b/test/Infrastructure.IntegrationTest/Infrastructure.IntegrationTest.csproj @@ -9,7 +9,7 @@ - + diff --git a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs index 8d645798e..b16a36615 100644 --- a/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs +++ b/test/IntegrationTestCommon/Factories/IdentityApplicationFactory.cs @@ -29,6 +29,11 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase return await Server.PostAsync("/accounts/register/finish", JsonContent.Create(model)); } + public async Task PostRegisterVerificationEmailClicked(RegisterVerificationEmailClickedRequestModel model) + { + return await Server.PostAsync("/accounts/register/verification-email-clicked", JsonContent.Create(model)); + } + public async Task<(string Token, string RefreshToken)> TokenFromPasswordAsync(string username, string password, string deviceIdentifier = DefaultDeviceIdentifier, diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index b06226557..e0fcc0e5e 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -144,6 +144,7 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory // Email Verification { "globalSettings:enableEmailVerification", "true" }, + { "globalSettings:disableUserRegistration", "false" }, { "globalSettings:launchDarkly:flagValues:email-verification", "true" } }); }); diff --git a/test/bitwarden.tests.sln b/test/bitwarden.tests.sln deleted file mode 100644 index 9654dd5e4..000000000 --- a/test/bitwarden.tests.sln +++ /dev/null @@ -1,230 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "Core.Test\Core.Test.csproj", "{A871C001-E815-4044-846E-06B30E110B79}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Icons.Test", "Icons.Test\Icons.Test.csproj", "{5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.Test", "Api.Test\Api.Test.csproj", "{2B29139A-E3B5-4A44-8A85-1593ACB797CC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{E94B2922-EE05-435C-9472-FDEFEAD0AA37}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing.Test", "Billing.Test\Billing.Test.csproj", "{8CD044FE-3FED-4F29-858C-B06BCE70EAA6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.IntegrationTest", "Identity.IntegrationTest\Identity.IntegrationTest.csproj", "{E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTestCommon", "IntegrationTestCommon\IntegrationTestCommon.csproj", "{41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.EFIntegration.Test", "Infrastructure.EFIntegration.Test\Infrastructure.EFIntegration.Test.csproj", "{8DEA714E-567C-4F4A-B424-568C8EC2CDA1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.IntegrationTest", "Api.IntegrationTest\Api.IntegrationTest.csproj", "{6ED94433-3423-498C-96C9-F24756357D95}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.IntegrationTest", "Infrastructure.IntegrationTest\Infrastructure.IntegrationTest.csproj", "{5827E256-D1C5-4BBE-BB74-ED28A83578FA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Test", "Identity.Test\Identity.Test.csproj", "{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.Test", "Admin.Test\Admin.Test.csproj", "{59EC3A17-74C4-41AF-AD21-F82D107C3374}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.Test", "Events.Test\Events.Test.csproj", "{181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventsProcessor.Test", "EventsProcessor.Test\EventsProcessor.Test.csproj", "{D1045453-676A-4353-A6C0-7FDFF78236A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notifications.Test", "Notifications.Test\Notifications.Test.csproj", "{9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A871C001-E815-4044-846E-06B30E110B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Debug|x64.ActiveCfg = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Debug|x64.Build.0 = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Debug|x86.ActiveCfg = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Debug|x86.Build.0 = Debug|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|Any CPU.Build.0 = Release|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|x64.ActiveCfg = Release|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|x64.Build.0 = Release|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|x86.ActiveCfg = Release|Any CPU - {A871C001-E815-4044-846E-06B30E110B79}.Release|x86.Build.0 = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|x64.ActiveCfg = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|x64.Build.0 = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|x86.ActiveCfg = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Debug|x86.Build.0 = Debug|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|Any CPU.Build.0 = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|x64.ActiveCfg = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|x64.Build.0 = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|x86.ActiveCfg = Release|Any CPU - {5B4A482F-1EA1-40A7-89DA-21BE6B897FA6}.Release|x86.Build.0 = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|x64.ActiveCfg = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|x64.Build.0 = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|x86.ActiveCfg = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Debug|x86.Build.0 = Debug|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|Any CPU.Build.0 = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|x64.ActiveCfg = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|x64.Build.0 = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|x86.ActiveCfg = Release|Any CPU - {2B29139A-E3B5-4A44-8A85-1593ACB797CC}.Release|x86.Build.0 = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|x64.ActiveCfg = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|x64.Build.0 = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|x86.ActiveCfg = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Debug|x86.Build.0 = Debug|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|Any CPU.Build.0 = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x64.ActiveCfg = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x64.Build.0 = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.ActiveCfg = Release|Any CPU - {E94B2922-EE05-435C-9472-FDEFEAD0AA37}.Release|x86.Build.0 = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x64.ActiveCfg = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x64.Build.0 = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x86.ActiveCfg = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Debug|x86.Build.0 = Debug|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|Any CPU.Build.0 = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x64.ActiveCfg = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x64.Build.0 = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x86.ActiveCfg = Release|Any CPU - {8CD044FE-3FED-4F29-858C-B06BCE70EAA6}.Release|x86.Build.0 = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|x64.ActiveCfg = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|x64.Build.0 = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|x86.ActiveCfg = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Debug|x86.Build.0 = Debug|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|Any CPU.Build.0 = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|x64.ActiveCfg = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|x64.Build.0 = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|x86.ActiveCfg = Release|Any CPU - {E2BB0D89-4570-43AB-A2E7-C8069AD90E6A}.Release|x86.Build.0 = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|x64.ActiveCfg = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|x64.Build.0 = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|x86.ActiveCfg = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Debug|x86.Build.0 = Debug|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|Any CPU.Build.0 = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|x64.ActiveCfg = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|x64.Build.0 = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|x86.ActiveCfg = Release|Any CPU - {41188BB8-1FAF-45F6-8DC8-F316B8A6C56B}.Release|x86.Build.0 = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|x64.ActiveCfg = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|x64.Build.0 = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|x86.ActiveCfg = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Debug|x86.Build.0 = Debug|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|Any CPU.Build.0 = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x64.ActiveCfg = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x64.Build.0 = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.ActiveCfg = Release|Any CPU - {8DEA714E-567C-4F4A-B424-568C8EC2CDA1}.Release|x86.Build.0 = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|x64.ActiveCfg = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|x64.Build.0 = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|x86.ActiveCfg = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Debug|x86.Build.0 = Debug|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|Any CPU.Build.0 = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|x64.ActiveCfg = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|x64.Build.0 = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|x86.ActiveCfg = Release|Any CPU - {6ED94433-3423-498C-96C9-F24756357D95}.Release|x86.Build.0 = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x64.ActiveCfg = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x64.Build.0 = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x86.ActiveCfg = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Debug|x86.Build.0 = Debug|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|Any CPU.Build.0 = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x64.ActiveCfg = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x64.Build.0 = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.ActiveCfg = Release|Any CPU - {5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.Build.0 = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x64.ActiveCfg = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x64.Build.0 = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x86.ActiveCfg = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x86.Build.0 = Debug|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|Any CPU.Build.0 = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x64.ActiveCfg = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x64.Build.0 = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x86.ActiveCfg = Release|Any CPU - {CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x86.Build.0 = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|Any CPU.Build.0 = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|x64.ActiveCfg = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|x64.Build.0 = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|x86.ActiveCfg = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Debug|x86.Build.0 = Debug|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|Any CPU.ActiveCfg = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|Any CPU.Build.0 = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|x64.ActiveCfg = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|x64.Build.0 = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|x86.ActiveCfg = Release|Any CPU - {59EC3A17-74C4-41AF-AD21-F82D107C3374}.Release|x86.Build.0 = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|x64.ActiveCfg = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|x64.Build.0 = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|x86.ActiveCfg = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Debug|x86.Build.0 = Debug|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|Any CPU.Build.0 = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|x64.ActiveCfg = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|x64.Build.0 = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|x86.ActiveCfg = Release|Any CPU - {181B2AA7-7541-4EC0-A9FA-5D7AF6E626D6}.Release|x86.Build.0 = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|x64.ActiveCfg = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|x64.Build.0 = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|x86.ActiveCfg = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Debug|x86.Build.0 = Debug|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|Any CPU.Build.0 = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|x64.ActiveCfg = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|x64.Build.0 = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|x86.ActiveCfg = Release|Any CPU - {D1045453-676A-4353-A6C0-7FDFF78236A0}.Release|x86.Build.0 = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|x64.ActiveCfg = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|x64.Build.0 = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|x86.ActiveCfg = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Debug|x86.Build.0 = Debug|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|Any CPU.Build.0 = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|x64.ActiveCfg = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|x64.Build.0 = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|x86.ActiveCfg = Release|Any CPU - {9E23D7A3-89D2-484A-BC72-CCCDFF0EDC6C}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/util/Migrator/DbScripts/2024-07-01_00_EnsureMaxGBAndSeatsAreSet.sql b/util/Migrator/DbScripts/2024-07-01_00_EnsureMaxGBAndSeatsAreSet.sql new file mode 100644 index 000000000..b67aefb04 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-01_00_EnsureMaxGBAndSeatsAreSet.sql @@ -0,0 +1,10 @@ +UPDATE + [dbo].[Organization] +SET + [MaxStorageGb] = ISNULL([MaxStorageGb], 1) +WHERE + [MaxStorageGb] IS NULL + AND [PlanType] NOT IN ( + 0, --Free + 6 --Custom + ) diff --git a/util/Migrator/DbScripts/2024-07-09_00_CollectionCipherRemoveAccessAll.sql b/util/Migrator/DbScripts/2024-07-09_00_CollectionCipherRemoveAccessAll.sql new file mode 100644 index 000000000..9da18fd5f --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-09_00_CollectionCipherRemoveAccessAll.sql @@ -0,0 +1,213 @@ +-- Remove AccessAll from CollectionCipher sprocs +-- We created v2 versions of these, but the feature is now fully released, so this copies v2 changes back to non-versioned sproc + +-- CollectionCipher_ReadByUserId +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + CC.* + FROM + [dbo].[CollectionCipher] CC + INNER JOIN + [dbo].[Collection] S ON S.[Id] = CC.[CollectionId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + WHERE + OU.[Status] = 2 -- Confirmed + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +END +GO + +-- CollectionCipher_ReadByUserIdCipherId +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadByUserIdCipherId] + @UserId UNIQUEIDENTIFIER, + @CipherId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + CC.* + FROM + [dbo].[CollectionCipher] CC + INNER JOIN + [dbo].[Collection] S ON S.[Id] = CC.[CollectionId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = S.[OrganizationId] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = S.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + WHERE + CC.[CipherId] = @CipherId + AND OU.[Status] = 2 -- Confirmed + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +END +GO + +-- CollectionCipher_UpdateCollections +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollections] + @CipherId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Cipher] + WHERE + [Id] = @CipherId + ) + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + C.[Id] + FROM + [dbo].[Collection] C + INNER JOIN + [Organization] O ON O.[Id] = C.[OrganizationId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrgId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + CU.[ReadOnly] = 0 + OR CG.[ReadOnly] = 0 + ) + ), + [CollectionCiphersCTE] AS( + SELECT + [CollectionId], + [CipherId] + FROM + [dbo].[CollectionCipher] + WHERE + [CipherId] = @CipherId + ) + MERGE + [CollectionCiphersCTE] AS [Target] + USING + @CollectionIds AS [Source] + ON + [Target].[CollectionId] = [Source].[Id] + AND [Target].[CipherId] = @CipherId + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + INSERT VALUES + ( + [Source].[Id], + @CipherId + ) + WHEN NOT MATCHED BY SOURCE + AND [Target].[CipherId] = @CipherId + AND [Target].[CollectionId] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + DELETE + ; + + IF @OrgId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrgId + END +END +GO + +-- CollectionCipher_UpdateCollectionsForCiphers +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers] + @CipherIds AS [dbo].[GuidIdArray] READONLY, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + CREATE TABLE #AvailableCollections ( + [Id] UNIQUEIDENTIFIER + ) + + INSERT INTO #AvailableCollections + SELECT + C.[Id] + FROM + [dbo].[Collection] C + INNER JOIN + [Organization] O ON O.[Id] = C.[OrganizationId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrganizationId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + CU.[ReadOnly] = 0 + OR CG.[ReadOnly] = 0 + ) + + IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 + BEGIN + -- No writable collections available to share with in this organization. + RETURN + END + + INSERT INTO [dbo].[CollectionCipher] + ( + [CollectionId], + [CipherId] + ) + SELECT + [Collection].[Id], + [Cipher].[Id] + FROM + @CollectionIds [Collection] + INNER JOIN + @CipherIds [Cipher] ON 1 = 1 + WHERE + [Collection].[Id] IN (SELECT [Id] FROM #AvailableCollections) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO diff --git a/util/Migrator/DbScripts/2024-07-09_01_CipherAndCollectionFunctionsRemoveAccessAll.sql b/util/Migrator/DbScripts/2024-07-09_01_CipherAndCollectionFunctionsRemoveAccessAll.sql new file mode 100644 index 000000000..25bfac0b3 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-09_01_CipherAndCollectionFunctionsRemoveAccessAll.sql @@ -0,0 +1,115 @@ +-- Remove AccessAll from CollectionCipher sprocs +-- We created v2 versions of these, but the feature is now fully released, so this copies v2 changes back to non-versioned sproc + +-- UserCollectionDetails +CREATE OR ALTER FUNCTION [dbo].[UserCollectionDetails](@UserId UNIQUEIDENTIFIER) +RETURNS TABLE +AS RETURN +SELECT + C.*, + CASE + WHEN + COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 + THEN 0 + ELSE 1 + END [ReadOnly], + CASE + WHEN + COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 + THEN 0 + ELSE 1 + END [HidePasswords], + CASE + WHEN + COALESCE(CU.[Manage], CG.[Manage], 0) = 0 + THEN 0 + ELSE 1 + END [Manage] +FROM + [dbo].[CollectionView] C +INNER JOIN + [dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] +INNER JOIN + [dbo].[Organization] O ON O.[Id] = C.[OrganizationId] +LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] +LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] +LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] +LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] +WHERE + OU.[UserId] = @UserId + AND OU.[Status] = 2 -- 2 = Confirmed + AND O.[Enabled] = 1 + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +GO + +-- UserCipherDetails +CREATE OR ALTER FUNCTION [dbo].[UserCipherDetails](@UserId UNIQUEIDENTIFIER) +RETURNS TABLE +AS RETURN +WITH [CTE] AS ( + SELECT + [Id], + [OrganizationId] + FROM + [OrganizationUser] + WHERE + [UserId] = @UserId + AND [Status] = 2 -- Confirmed +) +SELECT + C.*, + CASE + WHEN COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 + THEN 1 + ELSE 0 + END [Edit], + CASE + WHEN COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 + THEN 1 + ELSE 0 + END [ViewPassword], + CASE + WHEN O.[UseTotp] = 1 + THEN 1 + ELSE 0 + END [OrganizationUseTotp] +FROM + [dbo].[CipherDetails](@UserId) C +INNER JOIN + [CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE]) +INNER JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1 +LEFT JOIN + [dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] +LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] +LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] +LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] +LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] +WHERE + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + +UNION ALL + +SELECT + *, + 1 [Edit], + 1 [ViewPassword], + 0 [OrganizationUseTotp] +FROM + [dbo].[CipherDetails](@UserId) +WHERE + [UserId] = @UserId + +GO diff --git a/util/Migrator/DbScripts/2024-07-10_00_MiscSprocsRemoveAccessAll.sql b/util/Migrator/DbScripts/2024-07-10_00_MiscSprocsRemoveAccessAll.sql new file mode 100644 index 000000000..f96a6a414 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-10_00_MiscSprocsRemoveAccessAll.sql @@ -0,0 +1,274 @@ +-- Remove AccessAll logic from miscellaneous sprocs + +-- CollectionUser_ReadByOrganizationUserIds +CREATE OR ALTER PROCEDURE [dbo].[CollectionUser_ReadByOrganizationUserIds] + @OrganizationUserIds [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + SELECT + CU.* + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] + INNER JOIN + @OrganizationUserIds OUI ON OUI.[Id] = OU.[Id] +END +GO + +-- OrganizationUser_ReadWithCollectionsById +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadWithCollectionsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [OrganizationUser_ReadById] @Id + + SELECT + CU.[CollectionId] Id, + CU.[ReadOnly], + CU.[HidePasswords], + CU.[Manage] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = [OU].[Id] + WHERE + [OrganizationUserId] = @Id +END +GO + +-- OrganizationUserUserDetails_ReadWithCollectionsById +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUserUserDetails_ReadWithCollectionsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [OrganizationUserUserDetails_ReadById] @Id + + SELECT + CU.[CollectionId] Id, + CU.[ReadOnly], + CU.[HidePasswords], + CU.[Manage] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = [OU].[Id] + WHERE + [OrganizationUserId] = @Id +END +GO + +-- Cipher_ReadCanEditByIdUserId +CREATE OR ALTER PROCEDURE [dbo].[Cipher_ReadCanEditByIdUserId] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DECLARE @CanEdit BIT + + ;WITH [CTE] AS ( + SELECT + CASE + WHEN C.[UserId] IS NOT NULL OR CU.[ReadOnly] = 0 OR CG.[ReadOnly] = 0 THEN 1 + ELSE 0 + END [Edit] + FROM + [dbo].[Cipher] C + LEFT JOIN + [dbo].[Organization] O ON C.[UserId] IS NULL AND O.[Id] = C.[OrganizationId] + LEFT JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionCipher] CC ON C.[UserId] IS NULL AND CC.[CipherId] = C.[Id] + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON C.[UserId] IS NULL AND CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + WHERE + C.Id = @Id + AND ( + C.[UserId] = @UserId + OR ( + C.[UserId] IS NULL + AND OU.[Status] = 2 -- 2 = Confirmed + AND O.[Enabled] = 1 + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) + ) + ) + ) + SELECT + @CanEdit = CASE + WHEN COUNT(1) > 0 THEN 1 + ELSE 0 + END + FROM + [CTE] + WHERE + [Edit] = 1 + + SELECT @CanEdit +END +GO + +-- Cipher_UpdateCollections +CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateCollections] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + IF @OrganizationId IS NULL OR (SELECT COUNT(1) FROM @CollectionIds) < 1 + BEGIN + RETURN(-1) + END + + CREATE TABLE #AvailableCollections ( + [Id] UNIQUEIDENTIFIER + ) + + IF @UserId IS NULL + BEGIN + INSERT INTO #AvailableCollections + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + END + ELSE + BEGIN + INSERT INTO #AvailableCollections + SELECT + C.[Id] + FROM + [dbo].[Collection] C + INNER JOIN + [Organization] O ON O.[Id] = C.[OrganizationId] + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[OrganizationId] = O.[Id] AND OU.[UserId] = @UserId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] + WHERE + O.[Id] = @OrganizationId + AND O.[Enabled] = 1 + AND OU.[Status] = 2 -- Confirmed + AND ( + CU.[ReadOnly] = 0 + OR CG.[ReadOnly] = 0 + ) + END + + IF (SELECT COUNT(1) FROM #AvailableCollections) < 1 + BEGIN + -- No writable collections available to share with in this organization. + RETURN(-1) + END + + INSERT INTO [dbo].[CollectionCipher] + ( + [CollectionId], + [CipherId] + ) + SELECT + [Id], + @Id + FROM + @CollectionIds + WHERE + [Id] IN (SELECT [Id] FROM #AvailableCollections) + + RETURN(0) +END +GO + +-- Folder_DeleteById +CREATE OR ALTER PROCEDURE [dbo].[Folder_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DECLARE @UserId UNIQUEIDENTIFIER = (SELECT TOP 1 [UserId] FROM [dbo].[Folder] WHERE [Id] = @Id) + DECLARE @UserIdPath VARCHAR(50) = CONCAT('$."', @UserId, '"') + + ;WITH [CTE] AS ( + SELECT + [Id], + [OrganizationId] + FROM + [OrganizationUser] + WHERE + [UserId] = @UserId + AND [Status] = 2 -- Confirmed + ) + UPDATE + C + SET + C.[Folders] = JSON_MODIFY(C.[Folders], @UserIdPath, NULL) + FROM + [dbo].[Cipher] C + INNER JOIN + [CTE] OU ON C.[UserId] IS NULL AND C.[OrganizationId] IN (SELECT [OrganizationId] FROM [CTE]) + INNER JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] AND O.[Id] = C.[OrganizationId] AND O.[Enabled] = 1 + LEFT JOIN + [dbo].[CollectionCipher] CC ON CC.[CipherId] = C.[Id] + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[CollectionId] = CC.[CollectionId] AND CU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[CollectionId] = CC.[CollectionId] AND CG.[GroupId] = GU.[GroupId] + WHERE + ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) + AND JSON_VALUE(C.[Folders], @UserIdPath) = @Id + + UPDATE + C + SET + C.[Folders] = JSON_MODIFY(C.[Folders], @UserIdPath, NULL) + FROM + [dbo].[Cipher] C + WHERE + [UserId] = @UserId + AND JSON_VALUE([Folders], @UserIdPath) = @Id + + DELETE + FROM + [dbo].[Folder] + WHERE + [Id] = @Id + + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId +END +GO diff --git a/util/Migrator/DbScripts/2024-07-11_00_BumpAccountRevisionDateRemoveAccessAll.sql b/util/Migrator/DbScripts/2024-07-11_00_BumpAccountRevisionDateRemoveAccessAll.sql new file mode 100644 index 000000000..827de97bf --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-11_00_BumpAccountRevisionDateRemoveAccessAll.sql @@ -0,0 +1,107 @@ +-- Remove AccessAll logic from bump account revision date sprocs + +-- User_BumpAccountRevisionDateByCipherId +CREATE OR ALTER PROCEDURE [dbo].[User_BumpAccountRevisionDateByCipherId] + @CipherId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + UPDATE + U + SET + U.[AccountRevisionDate] = GETUTCDATE() + FROM + [dbo].[User] U + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[UserId] = U.[Id] + LEFT JOIN + [dbo].[CollectionCipher] CC ON CC.[CipherId] = @CipherId + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = CC.[CollectionId] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = CC.[CollectionId] + WHERE + OU.[OrganizationId] = @OrganizationId + AND OU.[Status] = 2 -- 2 = Confirmed + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +END +GO + +-- User_BumpAccountRevisionDateByCollectionId +CREATE OR ALTER PROCEDURE [dbo].[User_BumpAccountRevisionDateByCollectionId] + @CollectionId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + UPDATE + U + SET + U.[AccountRevisionDate] = GETUTCDATE() + FROM + [dbo].[User] U + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[UserId] = U.[Id] + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = @CollectionId + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = @CollectionId + WHERE + OU.[OrganizationId] = @OrganizationId + AND OU.[Status] = 2 -- 2 = Confirmed + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +END +GO + +-- User_BumpAccountRevisionDateByCollectionIds +CREATE OR ALTER PROCEDURE [dbo].[User_BumpAccountRevisionDateByCollectionIds] + @CollectionIds AS [dbo].[GuidIdArray] READONLY, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +UPDATE + U +SET + U.[AccountRevisionDate] = GETUTCDATE() + FROM + [dbo].[User] U + INNER JOIN + [dbo].[Collection] C ON C.[Id] IN (SELECT [Id] FROM @CollectionIds) + INNER JOIN + [dbo].[OrganizationUser] OU ON OU.[UserId] = U.[Id] + LEFT JOIN + [dbo].[CollectionUser] CU ON CU.[OrganizationUserId] = OU.[Id] AND CU.[CollectionId] = C.[Id] + LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND GU.[OrganizationUserId] = OU.[Id] + LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] + LEFT JOIN + [dbo].[CollectionGroup] CG ON CG.[GroupId] = GU.[GroupId] AND CG.[CollectionId] = C.[Id] +WHERE + OU.[OrganizationId] = @OrganizationId + AND OU.[Status] = 2 -- 2 = Confirmed + AND ( + CU.[CollectionId] IS NOT NULL + OR CG.[CollectionId] IS NOT NULL + ) +END +GO diff --git a/util/Migrator/DbScripts/2024-07-16_00_FinalizeCollectionManagePermissionFinal.sql b/util/Migrator/DbScripts/2024-07-16_00_FinalizeCollectionManagePermissionFinal.sql new file mode 100644 index 000000000..2fba16333 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-16_00_FinalizeCollectionManagePermissionFinal.sql @@ -0,0 +1,45 @@ +-- Drop the v2 naming for sprocs that added the CollectionUser.Manage and CollectionGroup.Manage columns. +-- 2024-06-25_00_FinalizeCollectionManagePermission duplicated the v2 sprocs back to v0 +-- Step 2: delete v2 sprocs + +IF OBJECT_ID('[dbo].[Group_CreateWithCollections_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Group_CreateWithCollections_V2] +END +GO + +IF OBJECT_ID('[dbo].[Group_UpdateWithCollections_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Group_UpdateWithCollections_V2] +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_CreateWithCollections_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[OrganizationUser_CreateWithCollections_V2] +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_UpdateWithCollections_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections_V2] +END +GO + +IF OBJECT_ID('[dbo].[Collection_CreateWithGroupsAndUsers_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers_V2] +END +GO + +IF OBJECT_ID('[dbo].[Collection_UpdateWithGroupsAndUsers_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers_V2] +END +GO + +IF OBJECT_ID('[dbo].[CollectionUser_UpdateUsers_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionUser_UpdateUsers_V2] +END +GO diff --git a/util/Migrator/DbScripts/2024-07-16_01_DropCipherRepositoryV2Sprocs.sql b/util/Migrator/DbScripts/2024-07-16_01_DropCipherRepositoryV2Sprocs.sql new file mode 100644 index 000000000..6d47e066f --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-16_01_DropCipherRepositoryV2Sprocs.sql @@ -0,0 +1,29 @@ +IF OBJECT_ID('[dbo].[Cipher_Delete_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_Delete_V2] +END +GO + +IF OBJECT_ID('[dbo].[Cipher_Move_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_Move_V2] +END +GO + +IF OBJECT_ID('[dbo].[Cipher_Restore_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_Restore_V2] +END +GO + +IF OBJECT_ID('[dbo].[Cipher_SoftDelete_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Cipher_SoftDelete_V2] +END +GO + +IF OBJECT_ID('[dbo].[CipherDetails_ReadByIdUserId_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CipherDetails_ReadByIdUserId_V2] +END +GO diff --git a/util/Migrator/DbScripts/2024-07-16_02_DropCollectionCipherSprocs.sql b/util/Migrator/DbScripts/2024-07-16_02_DropCollectionCipherSprocs.sql new file mode 100644 index 000000000..d2168354a --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-16_02_DropCollectionCipherSprocs.sql @@ -0,0 +1,26 @@ +-- Clean up chore: delete v2 CollectionCipher sprocs +-- These were already copied back to v0 in 2024-07-09_00_CollectionCipherRemoveAccessAll + +IF OBJECT_ID('[dbo].[CollectionCipher_ReadByUserId_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionCipher_ReadByUserId_V2] +END +GO + +IF OBJECT_ID('[dbo].[CollectionCipher_ReadByUserIdCipherId_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionCipher_ReadByUserIdCipherId_V2] +END +GO + +IF OBJECT_ID('[dbo].[CollectionCipher_UpdateCollections_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionCipher_UpdateCollections_V2] +END +GO + +IF OBJECT_ID('[dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsForCiphers_V2] +END +GO diff --git a/util/Migrator/DbScripts/2024-07-23_00_GroupMakeAccessAllOptional.sql b/util/Migrator/DbScripts/2024-07-23_00_GroupMakeAccessAllOptional.sql new file mode 100644 index 000000000..d5b9d97a2 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-23_00_GroupMakeAccessAllOptional.sql @@ -0,0 +1,176 @@ +-- Give Group.AccessAll a default value so we can remove it from the code before dropping it permanently from the db + +CREATE OR ALTER PROCEDURE [dbo].[Group_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Group] + ( + [Id], + [OrganizationId], + [Name], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @OrganizationId, + @Name, + @AccessAll, + @ExternalId, + @CreationDate, + @RevisionDate + ) +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[Group_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionGroup] + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[Group_Update] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Group] + SET + [OrganizationId] = @OrganizationId, + [Name] = @Name, + [AccessAll] = @AccessAll, + [ExternalId] = @ExternalId, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[Group_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Update] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + Id + FROM + [dbo].[Collection] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @Collections AS [Source] + ON + [Target].[CollectionId] = [Source].[Id] + AND [Target].[GroupId] = @Id + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES + ( + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + WHEN NOT MATCHED BY SOURCE + AND [Target].[GroupId] = @Id THEN + DELETE + ; + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO diff --git a/util/Migrator/DbScripts/2024-07-24_00_OrganizationUserMakeAccessAllOptional.sql b/util/Migrator/DbScripts/2024-07-24_00_OrganizationUserMakeAccessAllOptional.sql new file mode 100644 index 000000000..b07d31e88 --- /dev/null +++ b/util/Migrator/DbScripts/2024-07-24_00_OrganizationUserMakeAccessAllOptional.sql @@ -0,0 +1,448 @@ +-- Remove OrganizationUser.AccessAll from all sprocs that read/write it directly + +-- View: don't return the AccessAll value. This is already unused in code. +CREATE OR ALTER VIEW [dbo].[OrganizationUserUserDetailsView] +AS +SELECT + OU.[Id], + OU.[UserId], + OU.[OrganizationId], + U.[Name], + ISNULL(U.[Email], OU.[Email]) Email, + U.[AvatarColor], + U.[TwoFactorProviders], + U.[Premium], + OU.[Status], + OU.[Type], + OU.[AccessSecretsManager], + OU.[ExternalId], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + OU.[ResetPasswordKey], + U.[UsesKeyConnector], + CASE WHEN U.[MasterPassword] IS NOT NULL THEN 1 ELSE 0 END AS HasMasterPassword +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] +GO + +-- Refresh metadata on sprocs that use the View + +IF OBJECT_ID('[dbo].[OrganizationUser_ReadByMinimumRole]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByMinimumRole]'; +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUserUserDetails_ReadById]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserUserDetails_ReadById]'; +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUserUserDetails_ReadByOrganizationId]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserUserDetails_ReadByOrganizationId]'; +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_ReadWithCollectionsById]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadWithCollectionsById]'; +END +GO + +-- Sprocs that don't use user-defined types: Give AccessAll a default value so we can remove it from the code +-- before dropping it permanently from the db + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationUser] + ( + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey], + [AccessSecretsManager] + ) + VALUES + ( + @Id, + @OrganizationId, + @UserId, + @Email, + @Key, + @Status, + @Type, + @AccessAll, + @ExternalId, + @CreationDate, + @RevisionDate, + @Permissions, + @ResetPasswordKey, + @AccessSecretsManager + ) +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_Update] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[OrganizationUser] + SET + [OrganizationId] = @OrganizationId, + [UserId] = @UserId, + [Email] = @Email, + [Key] = @Key, + [Status] = @Status, + [Type] = @Type, + [AccessAll] = @AccessAll, + [ExternalId] = @ExternalId, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [Permissions] = @Permissions, + [ResetPasswordKey] = @ResetPasswordKey, + [AccessSecretsManager] = @AccessSecretsManager + WHERE + [Id] = @Id + + EXEC [dbo].[User_BumpAccountRevisionDate] @UserId +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT = 0, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + -- Update + UPDATE + [Target] + SET + [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + FROM + [dbo].[CollectionUser] AS [Target] + INNER JOIN + @Collections AS [Source] ON [Source].[Id] = [Target].[CollectionId] + WHERE + [Target].[OrganizationUserId] = @Id + AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) + + -- Insert + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + FROM + @Collections AS [Source] + INNER JOIN + [dbo].[Collection] C ON C.[Id] = [Source].[Id] AND C.[OrganizationId] = @OrganizationId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[CollectionUser] + WHERE + [CollectionId] = [Source].[Id] + AND [OrganizationUserId] = @Id + ) + + -- Delete + DELETE + CU + FROM + [dbo].[CollectionUser] CU + WHERE + CU.[OrganizationUserId] = @Id + AND NOT EXISTS ( + SELECT + 1 + FROM + @Collections + WHERE + [Id] = CU.[CollectionId] + ) +END +GO + +-- Sprocs that do use user-defined types: +-- Create a new version of the sproc without using the type, and update that. +-- These were already versioned from a previous update, so take the opportunity to drop the version suffix. + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_CreateMany] + @jsonData NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationUser] + ( + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [AccessAll], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey], + [AccessSecretsManager] + ) + SELECT + OUI.[Id], + OUI.[OrganizationId], + OUI.[UserId], + OUI.[Email], + OUI.[Key], + OUI.[Status], + OUI.[Type], + 0, -- AccessAll will be removed shortly + OUI.[ExternalId], + OUI.[CreationDate], + OUI.[RevisionDate], + OUI.[Permissions], + OUI.[ResetPasswordKey], + OUI.[AccessSecretsManager] + FROM + OPENJSON(@jsonData) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [OrganizationId] UNIQUEIDENTIFIER '$.OrganizationId', + [UserId] UNIQUEIDENTIFIER '$.UserId', + [Email] NVARCHAR(256) '$.Email', + [Key] VARCHAR(MAX) '$.Key', + [Status] SMALLINT '$.Status', + [Type] TINYINT '$.Type', + [ExternalId] NVARCHAR(300) '$.ExternalId', + [CreationDate] DATETIME2(7) '$.CreationDate', + [RevisionDate] DATETIME2(7) '$.RevisionDate', + [Permissions] NVARCHAR (MAX) '$.Permissions', + [ResetPasswordKey] VARCHAR (MAX) '$.ResetPasswordKey', + [AccessSecretsManager] BIT '$.AccessSecretsManager' + ) OUI +END +GO + + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_UpdateMany] + @jsonData NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse the JSON string + DECLARE @OrganizationUserInput AS TABLE ( + [Id] UNIQUEIDENTIFIER, + [OrganizationId] UNIQUEIDENTIFIER, + [UserId] UNIQUEIDENTIFIER, + [Email] NVARCHAR(256), + [Key] VARCHAR(MAX), + [Status] SMALLINT, + [Type] TINYINT, + [ExternalId] NVARCHAR(300), + [CreationDate] DATETIME2(7), + [RevisionDate] DATETIME2(7), + [Permissions] NVARCHAR(MAX), + [ResetPasswordKey] VARCHAR(MAX), + [AccessSecretsManager] BIT + ) + + INSERT INTO @OrganizationUserInput + SELECT + [Id], + [OrganizationId], + [UserId], + [Email], + [Key], + [Status], + [Type], + [ExternalId], + [CreationDate], + [RevisionDate], + [Permissions], + [ResetPasswordKey], + [AccessSecretsManager] + FROM OPENJSON(@jsonData) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [OrganizationId] UNIQUEIDENTIFIER '$.OrganizationId', + [UserId] UNIQUEIDENTIFIER '$.UserId', + [Email] NVARCHAR(256) '$.Email', + [Key] VARCHAR(MAX) '$.Key', + [Status] SMALLINT '$.Status', + [Type] TINYINT '$.Type', + [ExternalId] NVARCHAR(300) '$.ExternalId', + [CreationDate] DATETIME2(7) '$.CreationDate', + [RevisionDate] DATETIME2(7) '$.RevisionDate', + [Permissions] NVARCHAR (MAX) '$.Permissions', + [ResetPasswordKey] VARCHAR (MAX) '$.ResetPasswordKey', + [AccessSecretsManager] BIT '$.AccessSecretsManager' + ) + + -- Perform the update + UPDATE + OU + SET + [OrganizationId] = OUI.[OrganizationId], + [UserId] = OUI.[UserId], + [Email] = OUI.[Email], + [Key] = OUI.[Key], + [Status] = OUI.[Status], + [Type] = OUI.[Type], + [AccessAll] = 0, -- AccessAll will be removed shortly + [ExternalId] = OUI.[ExternalId], + [CreationDate] = OUI.[CreationDate], + [RevisionDate] = OUI.[RevisionDate], + [Permissions] = OUI.[Permissions], + [ResetPasswordKey] = OUI.[ResetPasswordKey], + [AccessSecretsManager] = OUI.[AccessSecretsManager] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + @OrganizationUserInput OUI ON OU.Id = OUI.Id + + -- Bump account revision dates + EXEC [dbo].[User_BumpManyAccountRevisionDates] + ( + SELECT [UserId] + FROM @OrganizationUserInput + ) +END +GO diff --git a/util/Migrator/DbScripts/2024-08-02_00_UserReadByIdsWithCalculatedPremium.sql b/util/Migrator/DbScripts/2024-08-02_00_UserReadByIdsWithCalculatedPremium.sql new file mode 100644 index 000000000..fc8e3d7ed --- /dev/null +++ b/util/Migrator/DbScripts/2024-08-02_00_UserReadByIdsWithCalculatedPremium.sql @@ -0,0 +1,41 @@ +CREATE OR ALTER PROCEDURE [dbo].[User_ReadByIdsWithCalculatedPremium] + @Ids NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON; + + -- Declare a table variable to hold the parsed JSON data + DECLARE @ParsedIds TABLE (Id UNIQUEIDENTIFIER); + + -- Parse the JSON input into the table variable + INSERT INTO @ParsedIds (Id) + SELECT value + FROM OPENJSON(@Ids); + + -- Check if the input table is empty + IF (SELECT COUNT(1) FROM @ParsedIds) < 1 + BEGIN + RETURN(-1); + END + + -- Main query to fetch user details and calculate premium access + SELECT + U.*, + CASE + WHEN U.[Premium] = 1 + OR EXISTS ( + SELECT 1 + FROM [dbo].[OrganizationUser] OU + JOIN [dbo].[Organization] O ON OU.[OrganizationId] = O.[Id] + WHERE OU.[UserId] = U.[Id] + AND O.[UsersGetPremium] = 1 + AND O.[Enabled] = 1 + ) + THEN 1 + ELSE 0 + END AS HasPremiumAccess + FROM + [dbo].[UserView] U + WHERE + U.[Id] IN (SELECT [Id] FROM @ParsedIds); +END; diff --git a/util/Migrator/DbScripts/2024-08-08_00_DropCipherAndCollectionV2Functions.sql b/util/Migrator/DbScripts/2024-08-08_00_DropCipherAndCollectionV2Functions.sql new file mode 100644 index 000000000..26a83fdd9 --- /dev/null +++ b/util/Migrator/DbScripts/2024-08-08_00_DropCipherAndCollectionV2Functions.sql @@ -0,0 +1,25 @@ +-- chore: drop v2 sprocs and functions that are no longer in use + +IF OBJECT_ID('[dbo].[Collection_ReadByUserId_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_ReadByUserId_V2] +END +GO + +IF OBJECT_ID('[dbo].[UserCollectionDetails_V2]') IS NOT NULL +BEGIN + DROP FUNCTION [dbo].[UserCollectionDetails_V2] +END +GO + +IF OBJECT_ID('[dbo].[CipherDetails_ReadByUserId_V2]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CipherDetails_ReadByUserId_V2] +END +GO + +IF OBJECT_ID('[dbo].[UserCipherDetails_V2]') IS NOT NULL +BEGIN + DROP FUNCTION [dbo].[UserCipherDetails_V2] +END +GO diff --git a/util/Migrator/DbScripts/2024-08-09_00_OrganizationUser_UpdateDataForKeyRotation.sql b/util/Migrator/DbScripts/2024-08-09_00_OrganizationUser_UpdateDataForKeyRotation.sql new file mode 100644 index 000000000..11066e495 --- /dev/null +++ b/util/Migrator/DbScripts/2024-08-09_00_OrganizationUser_UpdateDataForKeyRotation.sql @@ -0,0 +1,37 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_UpdateDataForKeyRotation] + @UserId UNIQUEIDENTIFIER, + @OrganizationUserJson NVARCHAR(MAX) +AS +BEGIN + SET NOCOUNT ON + + -- Parse the JSON string and insert into a temporary table + DECLARE @OrganizationUserInput AS TABLE ( + [Id] UNIQUEIDENTIFIER, + [ResetPasswordKey] VARCHAR(MAX) + ) + + INSERT INTO @OrganizationUserInput + SELECT + [Id], + [ResetPasswordKey] + FROM OPENJSON(@OrganizationUserJson) + WITH ( + [Id] UNIQUEIDENTIFIER '$.Id', + [ResetPasswordKey] VARCHAR(MAX) '$.ResetPasswordKey' + ) + + -- Perform the update + UPDATE + [dbo].[OrganizationUser] + SET + [ResetPasswordKey] = OUI.[ResetPasswordKey] + FROM + [dbo].[OrganizationUser] OU + INNER JOIN + @OrganizationUserInput OUI ON OU.Id = OUI.Id + WHERE + OU.[UserId] = @UserId + +END +GO diff --git a/util/Migrator/Migrator.csproj b/util/Migrator/Migrator.csproj index 548efd00b..7893a81c0 100644 --- a/util/Migrator/Migrator.csproj +++ b/util/Migrator/Migrator.csproj @@ -6,7 +6,7 @@ - + diff --git a/util/MySqlMigrations/Migrations/20240723011515_DropOrganizationFlexibleCollections.Designer.cs b/util/MySqlMigrations/Migrations/20240723011515_DropOrganizationFlexibleCollections.Designer.cs new file mode 100644 index 000000000..41220b3cb --- /dev/null +++ b/util/MySqlMigrations/Migrations/20240723011515_DropOrganizationFlexibleCollections.Designer.cs @@ -0,0 +1,2693 @@ +// +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("20240723011515_DropOrganizationFlexibleCollections")] + partial class DropOrganizationFlexibleCollections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreationDeletion") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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") + .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("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("AccessAll") + .HasColumnType("tinyint(1)"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("Npgsql:IndexInclude", new[] { "AccessAll" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240723011515_DropOrganizationFlexibleCollections.cs b/util/MySqlMigrations/Migrations/20240723011515_DropOrganizationFlexibleCollections.cs new file mode 100644 index 000000000..60d287eb6 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20240723011515_DropOrganizationFlexibleCollections.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class DropOrganizationFlexibleCollections : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FlexibleCollections", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FlexibleCollections", + table: "Organization", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } +} diff --git a/util/MySqlMigrations/Migrations/20240724001641_MakeBlobNonNull.Designer.cs b/util/MySqlMigrations/Migrations/20240724001641_MakeBlobNonNull.Designer.cs new file mode 100644 index 000000000..81ebee41f --- /dev/null +++ b/util/MySqlMigrations/Migrations/20240724001641_MakeBlobNonNull.Designer.cs @@ -0,0 +1,2697 @@ +// +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("20240724001641_MakeBlobNonNull")] + partial class MakeBlobNonNull + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("FlexibleCollections") + .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("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("LimitCollectionCreationDeletion") + .HasColumnType("tinyint(1)") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("int"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("SmSeats") + .HasColumnType("int"); + + b.Property("SmServiceAccounts") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePasswordManager") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasColumnType("longtext"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("longtext"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AaGuid") + .HasColumnType("char(36)"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SupportsPrf") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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("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("AccessAll") + .HasColumnType("tinyint(1)"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("Npgsql:IndexInclude", new[] { "AccessAll" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240724001641_MakeBlobNonNull.cs b/util/MySqlMigrations/Migrations/20240724001641_MakeBlobNonNull.cs new file mode 100644 index 000000000..b331080d1 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20240724001641_MakeBlobNonNull.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +/// +public partial class MakeBlobNonNull : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "longblob", + nullable: false, + defaultValue: new byte[0], + oldClrType: typeof(byte[]), + oldType: "longblob", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "longblob", + nullable: true, + oldClrType: typeof(byte[]), + oldType: "longblob"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 56283d5e6..610fd37b7 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Bit.MySqlMigrations.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 64); MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); @@ -69,9 +69,6 @@ namespace Bit.MySqlMigrations.Migrations b.Property("ExpirationDate") .HasColumnType("datetime(6)"); - b.Property("FlexibleCollections") - .HasColumnType("tinyint(1)"); - b.Property("Gateway") .HasColumnType("tinyint unsigned"); @@ -780,6 +777,7 @@ namespace Bit.MySqlMigrations.Migrations .HasColumnType("bigint"); b.Property("Value") + .IsRequired() .HasColumnType("longblob"); b.HasKey("Id") @@ -1622,7 +1620,7 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("AccessPolicy", (string)null); - b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + b.HasDiscriminator().HasValue("AccessPolicy"); b.UseTphMappingStrategy(); }); diff --git a/util/MySqlMigrations/MySqlMigrations.csproj b/util/MySqlMigrations/MySqlMigrations.csproj index 54229af52..b627bc7ac 100644 --- a/util/MySqlMigrations/MySqlMigrations.csproj +++ b/util/MySqlMigrations/MySqlMigrations.csproj @@ -10,7 +10,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/util/PostgresMigrations/Migrations/20240723011507_DropOrganizationFlexibleCollections.Designer.cs b/util/PostgresMigrations/Migrations/20240723011507_DropOrganizationFlexibleCollections.Designer.cs new file mode 100644 index 000000000..bad906ce5 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20240723011507_DropOrganizationFlexibleCollections.Designer.cs @@ -0,0 +1,2700 @@ +// +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("20240723011507_DropOrganizationFlexibleCollections")] + partial class DropOrganizationFlexibleCollections + { + /// + 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.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreationDeletion") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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") + .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("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("AccessAll") + .HasColumnType("boolean"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("UserId", "OrganizationId", "Status"), new[] { "AccessAll" }); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240723011507_DropOrganizationFlexibleCollections.cs b/util/PostgresMigrations/Migrations/20240723011507_DropOrganizationFlexibleCollections.cs new file mode 100644 index 000000000..a8e45120d --- /dev/null +++ b/util/PostgresMigrations/Migrations/20240723011507_DropOrganizationFlexibleCollections.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class DropOrganizationFlexibleCollections : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FlexibleCollections", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FlexibleCollections", + table: "Organization", + type: "boolean", + nullable: false, + defaultValue: false); + } +} diff --git a/util/PostgresMigrations/Migrations/20240724001647_MakeBlobNonNull.Designer.cs b/util/PostgresMigrations/Migrations/20240724001647_MakeBlobNonNull.Designer.cs new file mode 100644 index 000000000..1ca6698b0 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20240724001647_MakeBlobNonNull.Designer.cs @@ -0,0 +1,2704 @@ +// +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("20240724001647_MakeBlobNonNull")] + partial class MakeBlobNonNull + { + /// + 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.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AllowAdminAccessToAllCollectionItems") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("BillingEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("FlexibleCollections") + .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("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("LimitCollectionCreationDeletion") + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("integer"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("SmSeats") + .HasColumnType("integer"); + + b.Property("SmServiceAccounts") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePasswordManager") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled"); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("Id", "Enabled"), new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasColumnType("text"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("OrganizationId", "ExternalId"), new[] { "UserId" }); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AaGuid") + .HasColumnType("uuid"); + + b.Property("Counter") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupportsPrf") + .HasColumnType("boolean"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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("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("AccessAll") + .HasColumnType("boolean"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("SqlServer:Clustered", false); + + NpgsqlIndexBuilderExtensions.IncludeProperties(b.HasIndex("UserId", "OrganizationId", "Status"), new[] { "AccessAll" }); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240724001647_MakeBlobNonNull.cs b/util/PostgresMigrations/Migrations/20240724001647_MakeBlobNonNull.cs new file mode 100644 index 000000000..0bd37867d --- /dev/null +++ b/util/PostgresMigrations/Migrations/20240724001647_MakeBlobNonNull.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +/// +public partial class MakeBlobNonNull : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "bytea", + nullable: false, + defaultValue: new byte[0], + oldClrType: typeof(byte[]), + oldType: "bytea", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "bytea", + nullable: true, + oldClrType: typeof(byte[]), + oldType: "bytea"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 94c279b72..3899375cb 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Bit.PostgresMigrations.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -70,9 +70,6 @@ namespace Bit.PostgresMigrations.Migrations b.Property("ExpirationDate") .HasColumnType("timestamp with time zone"); - b.Property("FlexibleCollections") - .HasColumnType("boolean"); - b.Property("Gateway") .HasColumnType("smallint"); @@ -785,6 +782,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("bigint"); b.Property("Value") + .IsRequired() .HasColumnType("bytea"); b.HasKey("Id") @@ -1629,7 +1627,7 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("AccessPolicy", (string)null); - b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + b.HasDiscriminator().HasValue("AccessPolicy"); b.UseTphMappingStrategy(); }); diff --git a/util/PostgresMigrations/PostgresMigrations.csproj b/util/PostgresMigrations/PostgresMigrations.csproj index 650652d54..7eca61122 100644 --- a/util/PostgresMigrations/PostgresMigrations.csproj +++ b/util/PostgresMigrations/PostgresMigrations.csproj @@ -6,7 +6,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj b/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj index 66083dd13..fbfd5494c 100644 --- a/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj +++ b/util/SqlServerEFScaffold/SqlServerEFScaffold.csproj @@ -1,6 +1,6 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/util/SqliteMigrations/Migrations/20240723011520_DropOrganizationFlexibleCollections.Designer.cs b/util/SqliteMigrations/Migrations/20240723011520_DropOrganizationFlexibleCollections.Designer.cs new file mode 100644 index 000000000..38a677b01 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20240723011520_DropOrganizationFlexibleCollections.Designer.cs @@ -0,0 +1,2682 @@ +// +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("20240723011520_DropOrganizationFlexibleCollections")] + partial class DropOrganizationFlexibleCollections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + 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("LimitCollectionCreationDeletion") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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") + .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("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("AccessAll") + .HasColumnType("INTEGER"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("Npgsql:IndexInclude", new[] { "AccessAll" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240723011520_DropOrganizationFlexibleCollections.cs b/util/SqliteMigrations/Migrations/20240723011520_DropOrganizationFlexibleCollections.cs new file mode 100644 index 000000000..cf473c5a5 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20240723011520_DropOrganizationFlexibleCollections.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class DropOrganizationFlexibleCollections : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FlexibleCollections", + table: "Organization"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FlexibleCollections", + table: "Organization", + type: "INTEGER", + nullable: false, + defaultValue: false); + } +} diff --git a/util/SqliteMigrations/Migrations/20240724001634_MakeBlobNonNull.Designer.cs b/util/SqliteMigrations/Migrations/20240724001634_MakeBlobNonNull.Designer.cs new file mode 100644 index 000000000..183c5f209 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20240724001634_MakeBlobNonNull.Designer.cs @@ -0,0 +1,2686 @@ +// +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("20240724001634_MakeBlobNonNull")] + partial class MakeBlobNonNull + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); + + 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("FlexibleCollections") + .HasColumnType("INTEGER"); + + 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("LimitCollectionCreationDeletion") + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxAutoscaleSmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("SmSeats") + .HasColumnType("INTEGER"); + + b.Property("SmServiceAccounts") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePasswordManager") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Enabled") + .HasAnnotation("Npgsql:IndexInclude", new[] { "UseTotp" }); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "Type") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.Grant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasName("PK_Grant") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ExpirationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "ExternalId") + .IsUnique() + .HasAnnotation("Npgsql:IndexInclude", new[] { "UserId" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique() + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AaGuid") + .HasColumnType("TEXT"); + + b.Property("Counter") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("CredentialId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EncryptedPrivateKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedPublicKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EncryptedUserKey") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SupportsPrf") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebAuthnCredential", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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("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("AccessAll") + .HasColumnType("INTEGER"); + + 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.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.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DomainName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("JobRunCount") + .HasColumnType("INTEGER"); + + b.Property("LastCheckedDate") + .HasColumnType("TEXT"); + + b.Property("NextRunDate") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Txt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("VerifiedDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.HasIndex("SponsoringOrganizationUserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + 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.HasIndex("UserId", "OrganizationId", "Status") + .HasAnnotation("Npgsql:IndexInclude", new[] { "AccessAll" }) + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeletionDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("UserId", "OrganizationId", "CreationDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.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.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("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("ServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserSecretAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedSecretId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedSecretId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedSecretId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_secret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Auth.Models.WebAuthnCredential", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Billing.Models.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.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .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("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() + .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("GroupAccessPolicies"); + + 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/20240724001634_MakeBlobNonNull.cs b/util/SqliteMigrations/Migrations/20240724001634_MakeBlobNonNull.cs new file mode 100644 index 000000000..1dc6b391f --- /dev/null +++ b/util/SqliteMigrations/Migrations/20240724001634_MakeBlobNonNull.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +/// +public partial class MakeBlobNonNull : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "BLOB", + nullable: false, + defaultValue: new byte[0], + oldClrType: typeof(byte[]), + oldType: "BLOB", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Value", + table: "Cache", + type: "BLOB", + nullable: true, + oldClrType: typeof(byte[]), + oldType: "BLOB"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 3c2ec62d8..1234777b7 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Bit.SqliteMigrations.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.7"); modelBuilder.Entity("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", b => { @@ -64,9 +64,6 @@ namespace Bit.SqliteMigrations.Migrations b.Property("ExpirationDate") .HasColumnType("TEXT"); - b.Property("FlexibleCollections") - .HasColumnType("INTEGER"); - b.Property("Gateway") .HasColumnType("INTEGER"); @@ -769,6 +766,7 @@ namespace Bit.SqliteMigrations.Migrations .HasColumnType("INTEGER"); b.Property("Value") + .IsRequired() .HasColumnType("BLOB"); b.HasKey("Id") @@ -1611,7 +1609,7 @@ namespace Bit.SqliteMigrations.Migrations b.ToTable("AccessPolicy", (string)null); - b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + b.HasDiscriminator().HasValue("AccessPolicy"); b.UseTphMappingStrategy(); }); diff --git a/util/SqliteMigrations/SqliteMigrations.csproj b/util/SqliteMigrations/SqliteMigrations.csproj index c9b287c2e..ad0301ad0 100644 --- a/util/SqliteMigrations/SqliteMigrations.csproj +++ b/util/SqliteMigrations/SqliteMigrations.csproj @@ -11,7 +11,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all