diff --git a/.checkmarx/config.yml b/.checkmarx/config.yml new file mode 100644 index 0000000000..641da0eacb --- /dev/null +++ b/.checkmarx/config.yml @@ -0,0 +1,13 @@ +version: 1 + +# Checkmarx configuration file +# +# https://checkmarx.com/resource/documents/en/34965-68549-configuring-projects-using-config-as-code-files.html +checkmarx: + scan: + configs: + sast: + # Exclude test directory + filter: "!test" + kics: + filter: "!dev,!.devcontainer" diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3a7def9a18..a1119ca479 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,7 +7,7 @@ "commands": ["swagger"] }, "dotnet-ef": { - "version": "7.0.14", + "version": "8.0.1", "commands": ["dotnet-ef"] } } diff --git a/.devcontainer/bitwarden_common/docker-compose.yml b/.devcontainer/bitwarden_common/docker-compose.yml index 295fd08da2..ccc5a9ec40 100644 --- a/.devcontainer/bitwarden_common/docker-compose.yml +++ b/.devcontainer/bitwarden_common/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: bitwarden_server: - image: mcr.microsoft.com/devcontainers/dotnet:dev-6.0 + image: mcr.microsoft.com/devcontainers/dotnet:8.0 volumes: - ../../:/workspace:cached # Overrides default command so things don't shut down after the process ends. diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000000..3a606f3b5a --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "test" # Tests diff --git a/.github/renovate.json b/.github/renovate.json index 714baad8e1..4463d18415 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,17 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base", - ":combinePatchMinorReleases", - ":dependencyDashboard", - ":maintainLockFilesWeekly", - ":pinAllExceptPeerDependencies", - ":prConcurrentLimit10", - ":rebaseStalePrs", - ":separateMajorReleases", - "group:monorepos", - "schedule:weekends" - ], + "extends": ["github>bitwarden/renovate-config"], "enabledManagers": [ "dockerfile", "docker-compose", @@ -19,8 +8,6 @@ "npm", "nuget" ], - "commitMessagePrefix": "[deps]:", - "commitMessageTopic": "{{depName}}", "packageRules": [ { "groupName": "dockerfile minor", @@ -112,27 +99,13 @@ "groupName": "Microsoft.Extensions.Logging", "description": "Group Microsoft.Extensions.Logging to exclude them from the dotnet monorepo preset" }, - { - "matchPackageNames": ["CommandDotNet", "dbup-sqlserver", "YamlDotNet"], - "description": "DevOps owned dependencies", - "commitMessagePrefix": "[deps] DevOps:", - "reviewers": ["team:team-devops"] - }, - { - "matchPackageNames": [ - "Microsoft.AspNetCore.Authentication.JwtBearer", - "Microsoft.AspNetCore.Http", - "Microsoft.Data.SqlClient" - ], - "description": "Platform owned dependencies", - "commitMessagePrefix": "[deps] Platform:", - "reviewers": ["team:team-platform-dev"] - }, { "matchPackageNames": [ "Dapper", + "dbup-sqlserver", "dotnet-ef", "linq2db.EntityFrameworkCore", + "Microsoft.Data.SqlClient", "Microsoft.EntityFrameworkCore.Design", "Microsoft.EntityFrameworkCore.InMemory", "Microsoft.EntityFrameworkCore.Relational", @@ -141,9 +114,24 @@ "Npgsql.EntityFrameworkCore.PostgreSQL", "Pomelo.EntityFrameworkCore.MySql" ], - "description": "Secrets Manager owned dependencies", - "commitMessagePrefix": "[deps] SM:", - "reviewers": ["team:team-secrets-manager-dev"] + "description": "DbOps owned dependencies", + "commitMessagePrefix": "[deps] DbOps:", + "reviewers": ["team:dept-dbops"] + }, + { + "matchPackageNames": ["CommandDotNet", "YamlDotNet"], + "description": "DevOps owned dependencies", + "commitMessagePrefix": "[deps] DevOps:", + "reviewers": ["team:dept-devops"] + }, + { + "matchPackageNames": [ + "Microsoft.AspNetCore.Authentication.JwtBearer", + "Microsoft.AspNetCore.Http" + ], + "description": "Platform owned dependencies", + "commitMessagePrefix": "[deps] Platform:", + "reviewers": ["team:team-platform-dev"] }, { "matchPackagePatterns": ["EntityFrameworkCore", "^dotnet-ef"], @@ -203,10 +191,5 @@ "reviewers": ["team:team-vault-dev"] } ], - "force": { - "constraints": { - "dotnet": "6.0.100" - } - }, "ignoreDeps": ["dotnet-sdk"] } diff --git a/.github/test/on-master-event.json b/.github/test/on-master-event.json deleted file mode 100644 index c497522e6d..0000000000 --- a/.github/test/on-master-event.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "release": { - "head": { - "ref": "master" - } - } -} diff --git a/.github/workflows/_move_finalization_db_scripts.yml b/.github/workflows/_move_finalization_db_scripts.yml index 5e3ea0d250..0b1d18797e 100644 --- a/.github/workflows/_move_finalization_db_scripts.yml +++ b/.github/workflows/_move_finalization_db_scripts.yml @@ -1,7 +1,6 @@ --- - name: _move_finalization_db_scripts -run-name: Move finalization db scripts +run-name: Move finalization database scripts on: workflow_call: @@ -11,7 +10,6 @@ permissions: contents: write jobs: - setup: name: Setup runs-on: ubuntu-22.04 @@ -19,7 +17,7 @@ jobs: migration_filename_prefix: ${{ steps.prefix.outputs.prefix }} copy_finalization_scripts: ${{ steps.check-finalization-scripts-existence.outputs.copy_finalization_scripts }} steps: - - name: Login to Azure + - name: Log in to Azure uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -31,7 +29,7 @@ jobs: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" - - name: Checkout Branch + - name: Check out branch uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: token: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }} @@ -40,7 +38,7 @@ jobs: id: prefix run: echo "prefix=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - - name: Check if any files in db finalization + - name: Check if any files in DB finalization directory id: check-finalization-scripts-existence run: | if [ -f util/Migrator/DbScripts_finalization/* ]; then @@ -50,7 +48,7 @@ jobs: fi move-finalization-db-scripts: - name: Move finalization db scripts + name: Move finalization database scripts runs-on: ubuntu-22.04 needs: setup if: ${{ needs.setup.outputs.copy_finalization_scripts == 'true' }} @@ -95,12 +93,12 @@ jobs: done echo "moved_files=$moved_files" >> $GITHUB_OUTPUT - - name: Login to Azure - Prod Subscription + - name: Log in to Azure - production subscription uses: Azure/login@de95379fe4dadc2defb305917eaa7e5dde727294 # v1.5.1 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - name: Retrieve Secrets + - name: Retrieve secrets id: retrieve-secrets uses: bitwarden/gh-actions/get-keyvault-secrets@main with: @@ -140,7 +138,7 @@ jobs: BRANCH: ${{ steps.branch_name.outputs.branch_name }} GH_TOKEN: ${{ github.token }} MOVED_FILES: ${{ steps.move-files.outputs.moved_files }} - TITLE: "Move finalization db scripts" + TITLE: "Move finalization database scripts" run: | PR_URL=$(gh pr create --title "$TITLE" \ --base "main" \ diff --git a/.github/workflows/automatic-issue-responses.yml b/.github/workflows/automatic-issue-responses.yml index cfe999c80b..21c65e1938 100644 --- a/.github/workflows/automatic-issue-responses.yml +++ b/.github/workflows/automatic-issue-responses.yml @@ -6,8 +6,8 @@ on: - labeled jobs: close-issue: - name: 'Close issue with automatic response' - runs-on: ubuntu-20.04 + name: Close issue with automatic response + runs-on: ubuntu-22.04 permissions: issues: write steps: @@ -24,7 +24,7 @@ jobs: This issue will now be closed. Thanks! # Intended behavior - if: github.event.label.name == 'intended-behavior' - name: Intended behaviour + name: Intended behavior uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0 with: comment: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96061b128e..c63ebd669f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,93 +2,35 @@ name: Build on: - push: - branches-ignore: - - "l10n_master" - - "gh-pages" - paths-ignore: - - ".github/workflows/**" workflow_dispatch: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + pull_request: env: _AZ_REGISTRY: "bitwardenprod.azurecr.io" jobs: - cloc: - name: CLOC - runs-on: ubuntu-22.04 - steps: - - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - name: Install cloc - run: | - sudo apt-get update - sudo apt-get -y install cloc - - - name: Print lines of code - run: cloc --include-lang C#,SQL,Razor,"Bourne Shell",PowerShell,HTML,CSS,Sass,JavaScript,TypeScript --vcs git - lint: name: Lint runs-on: ubuntu-22.04 steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - - name: Verify Format + - name: Verify format run: dotnet format --verify-no-changes - testing: - name: Testing - runs-on: ubuntu-22.04 - env: - NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages - steps: - - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - name: Set up dotnet - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - - - name: Print environment - run: | - dotnet --info - nuget help | grep Version - echo "GitHub ref: $GITHUB_REF" - echo "GitHub event: $GITHUB_EVENT" - - - name: Remove SQL proj - run: dotnet sln bitwarden-server.sln remove src/Sql/Sql.sqlproj - - - name: Test OSS solution - run: dotnet test ./test --configuration Release --logger "trx;LogFileName=oss-test-results.trx" /p:CoverletOutputFormatter="cobertura" --collect:"XPlat Code Coverage" - - - name: Test Bitwarden solution - run: dotnet test ./bitwarden_license/test --configuration Release --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 - if: always() - with: - name: Test Results - path: "**/*-test-results.trx" - reporter: dotnet-trx - fail-on-error: true - - - name: Upload to codecov.io - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - build-artifacts: name: Build artifacts runs-on: ubuntu-22.04 needs: - - testing - lint strategy: fail-fast: false @@ -125,10 +67,10 @@ jobs: base_path: ./bitwarden_license/src node: true steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - name: Set up Node @@ -228,7 +170,7 @@ jobs: base_path: ./bitwarden_license/src dotnet: true steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Check Branch to Publish @@ -245,7 +187,7 @@ jobs: fi ########## ACRs ########## - - name: Login to Azure - PROD Subscription + - name: Log in to Azure - production subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -253,7 +195,7 @@ jobs: - name: Login to PROD ACR run: az acr login -n bitwardenprod - - name: Login to Azure - CI Subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -269,13 +211,20 @@ jobs: - name: Generate Docker image tag id: tag run: | - IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name + if [[ $(grep "pull" <<< "${GITHUB_REF}") ]]; then + IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s#/#-#g") + else + IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") + fi + if [[ "$IMAGE_TAG" == "main" ]]; then IMAGE_TAG=dev fi - echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT - - name: Setup project name + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "### :mega: Docker Image Tag: $IMAGE_TAG" >> $GITHUB_STEP_SUMMARY + + - name: Set up project name id: setup run: | PROJECT_NAME=$(echo "${{ matrix.project_name }}" | awk '{print tolower($0)}') @@ -303,7 +252,7 @@ jobs: with: name: ${{ matrix.project_name }}.zip - - name: Setup build artifact + - name: Set up build artifact if: ${{ matrix.dotnet }} run: | mkdir -p ${{ matrix.base_path}}/${{ matrix.project_name }}/obj/build-output/publish @@ -326,13 +275,13 @@ jobs: runs-on: ubuntu-22.04 needs: build-docker steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - - name: Login to Azure - PROD Subscription + - name: Log in to Azure - production subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -445,7 +394,7 @@ jobs: if-no-files-found: error build-mssqlmigratorutility: - name: Build MsSqlMigratorUtility + name: Build MSSQL migrator utility runs-on: ubuntu-22.04 needs: lint defaults: @@ -460,10 +409,10 @@ jobs: - linux-x64 - win-x64 steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Set up dotnet + - name: Set up .NET uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - name: Print environment @@ -478,7 +427,7 @@ jobs: dotnet publish -c "Release" -o obj/build-output/publish -r ${{ matrix.target }} -p:PublishSingleFile=true \ -p:IncludeNativeLibrariesForSelfExtract=true --self-contained true - - name: Upload project artifact Windows + - name: Upload project artifact for Windows if: ${{ contains(matrix.target, 'win') == true }} uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 with: @@ -499,7 +448,7 @@ jobs: runs-on: ubuntu-22.04 needs: build-docker steps: - - name: Login to Azure - CI Subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -532,7 +481,7 @@ jobs: runs-on: ubuntu-22.04 needs: build-docker steps: - - name: Login to Azure - CI Subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -565,9 +514,7 @@ jobs: if: always() runs-on: ubuntu-22.04 needs: - - cloc - lint - - testing - build-artifacts - build-docker - upload @@ -581,7 +528,6 @@ jobs: || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' env: - CLOC_STATUS: ${{ needs.cloc.result }} LINT_STATUS: ${{ needs.lint.result }} TESTING_STATUS: ${{ needs.testing.result }} BUILD_ARTIFACTS_STATUS: ${{ needs.build-artifacts.result }} @@ -591,9 +537,7 @@ jobs: TRIGGER_SELF_HOST_BUILD_STATUS: ${{ needs.self-host-build.result }} TRIGGER_K8S_DEPLOY_STATUS: ${{ needs.trigger-k8s-deploy.result }} run: | - if [ "$CLOC_STATUS" = "failure" ]; then - exit 1 - elif [ "$LINT_STATUS" = "failure" ]; then + if [ "$LINT_STATUS" = "failure" ]; then exit 1 elif [ "$TESTING_STATUS" = "failure" ]; then exit 1 @@ -611,7 +555,7 @@ jobs: exit 1 fi - - name: Login to Azure - CI subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 if: failure() with: diff --git a/.github/workflows/cleanup-after-pr.yml b/.github/workflows/cleanup-after-pr.yml index ac8a1b624e..b63dd4a30d 100644 --- a/.github/workflows/cleanup-after-pr.yml +++ b/.github/workflows/cleanup-after-pr.yml @@ -1,5 +1,5 @@ --- -name: Clean After PR +name: Container registry cleanup on: pull_request: @@ -7,32 +7,30 @@ on: jobs: build-docker: - name: Remove feature branch docker images - runs-on: ubuntu-20.04 + name: Remove branch-specific Docker images + runs-on: ubuntu-22.04 steps: - - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - ########## ACR ########## - - name: Login to Azure - QA Subscription + - name: Log in to Azure - QA Subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} - - name: Login to Azure ACR + - name: Log in to Azure ACR run: az acr login -n bitwardenqa - - name: Login to Azure - PROD Subscription + - name: Log in to Azure - production subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Login to Azure ACR + - name: Log in to Azure ACR run: az acr login -n bitwardenprod ########## Remove Docker images ########## - - name: Remove the docker image from ACR + - name: Remove the Docker image from ACR env: + REF: ${{ github.event.pull_request.head.ref }} REGISTRIES: | registries: - bitwardenprod @@ -59,7 +57,7 @@ jobs: for REGISTRY in $( echo "${{ env.REGISTRIES }}" | yq e ".registries[]" - ) do SERVICE_NAME=$(echo $SERVICE | awk '{print tolower($0)}') - IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name + IMAGE_TAG=$(echo "${REF}" | sed "s#/#-#g") # slash safe branch name echo "[*] Checking if remote exists: $REGISTRY.azurecr.io/$SERVICE_NAME:$IMAGE_TAG" TAG_EXISTS=$( diff --git a/.github/workflows/code-references.yml b/.github/workflows/code-references.yml new file mode 100644 index 0000000000..ca584a1d3a --- /dev/null +++ b/.github/workflows/code-references.yml @@ -0,0 +1,42 @@ +--- +name: Collect code references + +on: + pull_request: + branches-ignore: + - "renovate/**" + +permissions: + contents: read + pull-requests: write + +jobs: + refs: + name: Code reference collection + runs-on: ubuntu-22.04 + steps: + - name: Check out repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Collect + id: collect + uses: launchdarkly/find-code-references-in-pull-request@2e9333c88539377cfbe818c265ba8b9ebced3c91 # v1.1.0 + with: + project-key: default + environment-key: dev + access-token: ${{ secrets.LD_ACCESS_TOKEN }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Add label + if: steps.collect.outputs.any-changed == 'true' + run: gh pr edit $PR_NUMBER --add-label feature-flag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + + - name: Remove label + if: steps.collect.outputs.any-changed == 'false' + run: gh pr edit $PR_NUMBER --remove-label feature-flag + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/container-registry-purge.yml b/.github/workflows/container-registry-purge.yml index f9999e8dc8..4b61e59125 100644 --- a/.github/workflows/container-registry-purge.yml +++ b/.github/workflows/container-registry-purge.yml @@ -1,18 +1,18 @@ --- -name: Container Registry Purge +name: Container registry purge on: schedule: - - cron: '0 0 * * SUN' + - cron: "0 0 * * SUN" workflow_dispatch: inputs: {} jobs: purge: name: Purge old images - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - name: Login to Azure + - name: Log in to Azure uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -68,7 +68,7 @@ jobs: check-failures: name: Check for failures if: always() - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: - purge steps: @@ -84,7 +84,7 @@ jobs: exit 1 fi - - name: Login to Azure - CI subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 if: failure() with: diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml deleted file mode 100644 index 2527440abd..0000000000 --- a/.github/workflows/database.yml +++ /dev/null @@ -1,95 +0,0 @@ ---- -name: Validate Database - -on: - pull_request: - branches-ignore: - - 'l10n_master' - - 'gh-pages' - paths: - - 'src/Sql/**' - - 'util/Migrator/**' - push: - branches: - - 'main' - - 'rc' - paths: - - 'src/Sql/**' - - 'util/Migrator/**' - workflow_dispatch: - inputs: {} - -jobs: - validate: - name: Validate - runs-on: ubuntu-22.04 - steps: - - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - name: Set up dotnet - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - with: - dotnet-version: '6.0.x' - - - name: Print environment - run: | - dotnet --info - nuget help | grep Version - echo "GitHub ref: $GITHUB_REF" - echo "GitHub event: $GITHUB_EVENT" - - - name: Build DACPAC - run: dotnet build src/Sql --configuration Release --verbosity minimal --output . - shell: pwsh - - - name: Upload DACPAC - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 - with: - name: sql.dacpac - path: Sql.dacpac - - - name: Docker Compose up - working-directory: "dev" - run: | - cp .env.example .env - docker compose --profile mssql up -d - shell: pwsh - - - name: Migrate - working-directory: "dev" - run: "pwsh ./migrate.ps1" - shell: pwsh - - - name: Diff sqlproj to migrations - run: /usr/local/sqlpackage/sqlpackage /action:DeployReport /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"report.xml" /p:IgnoreColumnOrder=True /p:IgnoreComments=True - shell: pwsh - - - name: Generate SQL file - run: /usr/local/sqlpackage/sqlpackage /action:Script /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"diff.sql" /p:IgnoreColumnOrder=True /p:IgnoreComments=True - shell: pwsh - - - name: Upload Report - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 - with: - name: report.xml - path: | - report.xml - diff.sql - - - name: Validate XML - run: | - if grep -q "" "report.xml"; then - echo - echo "Migrations are out of sync with sqlproj!" - exit 1 - else - echo "Report looks good" - fi - shell: bash - - - name: Docker compose down - if: ${{ always() }} - working-directory: "dev" - run: docker compose down - shell: pwsh diff --git a/.github/workflows/enforce-labels.yml b/.github/workflows/enforce-labels.yml index eff371fbb3..160ee15b96 100644 --- a/.github/workflows/enforce-labels.yml +++ b/.github/workflows/enforce-labels.yml @@ -2,15 +2,18 @@ name: Enforce PR labels on: + workflow_call: pull_request: - types: [labeled, unlabeled, opened, edited, synchronize] - + types: [labeled, unlabeled, opened, reopened, synchronize] jobs: enforce-label: - name: EnforceLabel - runs-on: ubuntu-20.04 + if: ${{ contains(github.event.*.labels.*.name, 'hold') || contains(github.event.*.labels.*.name, 'needs-qa') || contains(github.event.*.labels.*.name, 'DB-migrations-changed') }} + name: Enforce label + runs-on: ubuntu-22.04 + steps: - - name: Enforce Label - uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2 - with: - BANNED_LABELS: "hold,DB-migrations-changed,needs-qa" + - name: Check for label + run: | + echo "PRs with the hold or needs-qa labels cannot be merged" + echo "### :x: PRs with the hold or needs-qa labels cannot be merged" >> $GITHUB_STEP_SUMMARY + exit 1 diff --git a/.github/workflows/infrastructure-tests.yml b/.github/workflows/infrastructure-tests.yml deleted file mode 100644 index 1e17203bf9..0000000000 --- a/.github/workflows/infrastructure-tests.yml +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: Run Database Infrastructure Tests -on: - pull_request: - branches-ignore: - - 'l10n_master' - - 'gh-pages' - paths: - - '.github/workflows/infrastructure-tests.yml' # This file - - 'src/Sql/**' # SQL Server Database Changes - - 'util/Migrator/**' # New SQL Server Migrations - - 'util/MySqlMigrations/**' # Changes to MySQL - - 'util/PostgresMigrations/**' # Changes to Postgres - - 'util/SqliteMigrations/**' # Changes to Sqlite - - 'src/Infrastructure.Dapper/**' # Changes to SQL Server Dapper Repository Layer - - 'src/Infrastructure.EntityFramework/**' # Changes to Entity Framework Repository Layer - - 'test/Infrastructure.IntegrationTest/**' # Any changes to the tests - push: - branches: - - 'main' - - 'rc' - paths: - - '.github/workflows/infrastructure-tests.yml' # This file - - 'src/Sql/**' # SQL Server Database Changes - - 'util/Migrator/**' # New SQL Server Migrations - - 'util/MySqlMigrations/**' # Changes to MySQL - - 'util/PostgresMigrations/**' # Changes to Postgres - - 'util/SqliteMigrations/**' # Changes to Sqlite - - 'src/Infrastructure.Dapper/**' # Changes to SQL Server Dapper Repository Layer - - 'src/Infrastructure.EntityFramework/**' # Changes to Entity Framework Repository Layer - - 'test/Infrastructure.IntegrationTest/**' # Any changes to the tests - workflow_dispatch: - inputs: {} - -jobs: - test: - name: 'Run Infrastructure.IntegrationTest' - runs-on: ubuntu-22.04 - steps: - - name: Checkout repo - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - name: Set up dotnet - uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 - with: - dotnet-version: '6.0.x' - - - name: Restore Tools - run: dotnet tool restore - - - name: Compose Databases - working-directory: 'dev' - # We could think about not using profiles and pulling images directly to cover multiple versions - run: | - cp .env.example .env - docker compose --profile mssql --profile postgres --profile mysql up -d - shell: pwsh - - # I've seen the SQL Server container not be ready for commands right after starting up and just needing a bit longer to be ready - - name: Sleep - run: sleep 15s - - - name: Migrate SQL Server - working-directory: 'dev' - run: "pwsh ./migrate.ps1" - shell: pwsh - - - name: Migrate MySQL - working-directory: 'util/MySqlMigrations' - run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"' - env: - CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true" - - - name: Migrate Postgres - working-directory: 'util/PostgresMigrations' - run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:PostgreSql:ConnectionString="$CONN_STR"' - env: - CONN_STR: "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev" - - - name: Migrate Sqlite - working-directory: 'util/SqliteMigrations' - run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:Sqlite:ConnectionString="$CONN_STR"' - env: - CONN_STR: "Data Source=${{ runner.temp }}/test.db" - - - name: Run Tests - working-directory: 'test/Infrastructure.IntegrationTest' - env: - # Default Postgres: - BW_TEST_DATABASES__0__TYPE: "Postgres" - BW_TEST_DATABASES__0__CONNECTIONSTRING: "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev" - # Default MySql - BW_TEST_DATABASES__1__TYPE: "MySql" - BW_TEST_DATABASES__1__CONNECTIONSTRING: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev" - # Default Dapper SqlServer - BW_TEST_DATABASES__2__TYPE: "SqlServer" - BW_TEST_DATABASES__2__CONNECTIONSTRING: "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" - # Default Sqlite - BW_TEST_DATABASES__3__TYPE: "Sqlite" - BW_TEST_DATABASES__3__CONNECTIONSTRING: "Data Source=${{ runner.temp }}/test.db" - run: dotnet test --logger "trx;LogFileName=infrastructure-test-results.trx" - shell: pwsh - - - name: Report test results - uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0 - if: always() - with: - name: Test Results - path: "**/*-test-results.trx" - reporter: dotnet-trx - fail-on-error: true - - - name: Docker compose down - if: always() - working-directory: "dev" - run: docker compose down - shell: pwsh diff --git a/.github/workflows/protect-files.yml b/.github/workflows/protect-files.yml index df595e900c..dea02dd917 100644 --- a/.github/workflows/protect-files.yml +++ b/.github/workflows/protect-files.yml @@ -2,8 +2,7 @@ # Starts a matrix job to check for modified files, then sets output based on the results. # The input decides if the label job is ran, adding a label to the PR. --- - -name: Protect Files +name: Protect files on: pull_request: @@ -17,7 +16,7 @@ on: jobs: changed-files: name: Check for file changes - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: changes: ${{steps.check-changes.outputs.changes_detected}} @@ -29,7 +28,7 @@ jobs: path: util/Migrator/DbScripts label: "DB-migrations-changed" steps: - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: fetch-depth: 2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9839641d26..e4c238755a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ on: - Dry Run env: - _AZ_REGISTRY: 'bitwardenprod.azurecr.io' + _AZ_REGISTRY: "bitwardenprod.azurecr.io" jobs: setup: @@ -36,10 +36,10 @@ jobs: exit 1 fi - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Check Release Version + - name: Check release version id: version uses: bitwarden/gh-actions/release-version-check@main with: @@ -87,7 +87,7 @@ jobs: task: "deploy" description: "Deploy from ${{ needs.setup.outputs.branch-name }} branch" - - name: Download latest Release ${{ matrix.name }} asset + - name: Download latest release ${{ matrix.name }} asset if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: @@ -96,7 +96,7 @@ jobs: branch: ${{ needs.setup.outputs.branch-name }} artifacts: ${{ matrix.name }}.zip - - name: Dry Run - Download latest Release ${{ matrix.name }} asset + - 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: @@ -105,7 +105,7 @@ jobs: branch: main artifacts: ${{ matrix.name }}.zip - - name: Login to Azure - CI subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -130,12 +130,12 @@ jobs: echo "::add-mask::$publish_profile" echo "publish-profile=$publish_profile" >> $GITHUB_OUTPUT - - name: Login to Azure + - name: Log in to Azure uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Deploy App + - name: Deploy app uses: azure/webapps-deploy@4bca689e4c7129e55923ea9c45401b22dc6aa96f # v2.2.11 with: app-name: ${{ steps.retrieve-secrets.outputs.webapp-name }} @@ -156,7 +156,7 @@ jobs: fi az webapp start -n $WEBAPP_NAME -g $RESOURCE_GROUP -s staging - - name: Update ${{ matrix.name }} deployment status to Success + - 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: @@ -164,7 +164,7 @@ jobs: state: "success" deployment-id: ${{ steps.deployment.outputs.deployment_id }} - - name: Update ${{ matrix.name }} deployment status to Failure + - 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: @@ -210,10 +210,10 @@ jobs: echo "GitHub event: $GITHUB_EVENT" echo "Github Release Option: $RELEASE_OPTION" - - name: Checkout repo + - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Setup project name + - name: Set up project name id: setup run: | PROJECT_NAME=$(echo "${{ matrix.project_name }}" | awk '{print tolower($0)}') @@ -222,12 +222,12 @@ jobs: echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT ########## ACR PROD ########## - - name: Login to Azure - PROD Subscription + - name: Log in to Azure - production subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Login to Azure ACR + - name: Log in to Azure ACR run: az acr login -n $_AZ_REGISTRY --only-show-errors - name: Pull latest project image @@ -266,13 +266,13 @@ jobs: run: docker logout release: - name: Create GitHub Release + name: Create GitHub release runs-on: ubuntu-22.04 needs: - setup - deploy steps: - - name: Download latest Release Docker Stubs + - name: Download latest release Docker stubs if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: @@ -285,7 +285,7 @@ jobs: docker-stub-EU-sha256.txt, swagger.json" - - name: Dry Run - Download latest Release Docker Stubs + - name: Dry Run - Download latest release Docker stubs if: ${{ github.event.inputs.release_type == 'Dry Run' }} uses: bitwarden/gh-actions/download-artifacts@main with: diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 1bd058b94b..721fee4ae7 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -1,23 +1,23 @@ --- -name: 'Close stale issues and PRs' +name: Staleness on: workflow_dispatch: - schedule: # Run once a day at 5.23am (arbitrary but should avoid peak loads on the hour) - - cron: '23 5 * * *' + schedule: # Run once a day at 5.23am (arbitrary but should avoid peak loads on the hour) + - cron: "23 5 * * *" jobs: stale: - name: 'Check for stale issues and PRs' - runs-on: ubuntu-20.04 + name: Check for stale issues and PRs + runs-on: ubuntu-22.04 steps: - - name: 'Run stale action' + - name: Check uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 with: - stale-issue-label: 'needs-reply' - stale-pr-label: 'needs-changes' - days-before-stale: -1 # Do not apply the stale labels automatically, this is a manual process - days-before-issue-close: 14 # Close issue if no further activity after X days - days-before-pr-close: 21 # Close PR if no further activity after X days + stale-issue-label: "needs-reply" + stale-pr-label: "needs-changes" + days-before-stale: -1 # Do not apply the stale labels automatically, this is a manual process + days-before-issue-close: 14 # Close issue if no further activity after X days + days-before-pr-close: 21 # Close PR if no further activity after X days close-issue-message: | We need more information before we can help you with your problem. As we haven’t heard from you recently, this issue will be closed. diff --git a/.github/workflows/stop-staging-slots.yml b/.github/workflows/stop-staging-slots.yml index ca28a4db6b..0ffe94ecdf 100644 --- a/.github/workflows/stop-staging-slots.yml +++ b/.github/workflows/stop-staging-slots.yml @@ -1,5 +1,5 @@ --- -name: Stop Staging Slots +name: Stop staging slots on: workflow_dispatch: @@ -7,8 +7,8 @@ on: jobs: stop-slots: - name: Stop Slots - runs-on: ubuntu-20.04 + name: Stop slots + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -28,7 +28,7 @@ jobs: echo "NAME_LOWER: $NAME_LOWER" echo "name_lower=$NAME_LOWER" >> $GITHUB_OUTPUT - - name: Login to Azure - CI Subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -46,7 +46,7 @@ jobs: echo "::add-mask::$webapp_name" echo "webapp-name=$webapp_name" >> $GITHUB_OUTPUT - - name: Login to Azure + - name: Log in to Azure uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} diff --git a/.github/workflows/test-database.yml b/.github/workflows/test-database.yml new file mode 100644 index 0000000000..cf62a1f431 --- /dev/null +++ b/.github/workflows/test-database.yml @@ -0,0 +1,185 @@ +--- +name: Database testing + +on: + workflow_dispatch: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + paths: + - ".github/workflows/infrastructure-tests.yml" # This file + - "src/Sql/**" # SQL Server Database Changes + - "util/Migrator/**" # New SQL Server Migrations + - "util/MySqlMigrations/**" # Changes to MySQL + - "util/PostgresMigrations/**" # Changes to Postgres + - "util/SqliteMigrations/**" # Changes to Sqlite + - "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer + - "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer + - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests + pull_request: + paths: + - ".github/workflows/infrastructure-tests.yml" # This file + - "src/Sql/**" # SQL Server Database Changes + - "util/Migrator/**" # New SQL Server Migrations + - "util/MySqlMigrations/**" # Changes to MySQL + - "util/PostgresMigrations/**" # Changes to Postgres + - "util/SqliteMigrations/**" # Changes to Sqlite + - "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer + - "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer + - "test/Infrastructure.IntegrationTest/**" # Any changes to the tests + +jobs: + test: + name: Run tests + runs-on: ubuntu-22.04 + steps: + - name: Check out repo + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - name: Set up .NET + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + + - name: Restore tools + run: dotnet tool restore + + - name: Docker Compose databases + working-directory: "dev" + # We could think about not using profiles and pulling images directly to cover multiple versions + run: | + cp .env.example .env + docker compose --profile mssql --profile postgres --profile mysql up -d + shell: pwsh + + # I've seen the SQL Server container not be ready for commands right after starting up and just needing a bit longer to be ready + - name: Sleep + run: sleep 15s + + - name: Migrate SQL Server + working-directory: "dev" + run: "./migrate.ps1" + shell: pwsh + + - name: Migrate MySQL + working-directory: "util/MySqlMigrations" + run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"' + env: + CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true" + + - name: Migrate Postgres + working-directory: "util/PostgresMigrations" + run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:PostgreSql:ConnectionString="$CONN_STR"' + env: + CONN_STR: "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev" + + - name: Migrate SQLite + working-directory: "util/SqliteMigrations" + run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:Sqlite:ConnectionString="$CONN_STR"' + env: + CONN_STR: "Data Source=${{ runner.temp }}/test.db" + + - name: Run tests + working-directory: "test/Infrastructure.IntegrationTest" + env: + # Default Postgres: + BW_TEST_DATABASES__0__TYPE: "Postgres" + BW_TEST_DATABASES__0__CONNECTIONSTRING: "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev" + # Default MySql + BW_TEST_DATABASES__1__TYPE: "MySql" + BW_TEST_DATABASES__1__CONNECTIONSTRING: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev" + # Default Dapper SqlServer + BW_TEST_DATABASES__2__TYPE: "SqlServer" + BW_TEST_DATABASES__2__CONNECTIONSTRING: "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" + # Default Sqlite + BW_TEST_DATABASES__3__TYPE: "Sqlite" + BW_TEST_DATABASES__3__CONNECTIONSTRING: "Data Source=${{ runner.temp }}/test.db" + run: dotnet test --logger "trx;LogFileName=infrastructure-test-results.trx" + shell: pwsh + + - name: Report test results + uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0 + if: always() + with: + name: Test Results + path: "**/*-test-results.trx" + reporter: dotnet-trx + fail-on-error: true + + - name: Docker Compose down + if: always() + working-directory: "dev" + run: docker compose down + shell: pwsh + + validate: + name: Run validation + runs-on: ubuntu-22.04 + steps: + - name: Check out repo + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - name: Set up .NET + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + + - name: Print environment + run: | + dotnet --info + nuget help | grep Version + echo "GitHub ref: $GITHUB_REF" + echo "GitHub event: $GITHUB_EVENT" + + - name: Build DACPAC + run: dotnet build src/Sql --configuration Release --verbosity minimal --output . + shell: pwsh + + - name: Upload DACPAC + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: sql.dacpac + path: Sql.dacpac + + - name: Docker Compose up + working-directory: "dev" + run: | + cp .env.example .env + docker compose --profile mssql up -d + shell: pwsh + + - name: Migrate + working-directory: "dev" + run: "./migrate.ps1" + shell: pwsh + + - name: Diff .sqlproj to migrations + run: /usr/local/sqlpackage/sqlpackage /action:DeployReport /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"report.xml" /p:IgnoreColumnOrder=True /p:IgnoreComments=True + shell: pwsh + + - name: Generate SQL file + run: /usr/local/sqlpackage/sqlpackage /action:Script /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"diff.sql" /p:IgnoreColumnOrder=True /p:IgnoreComments=True + shell: pwsh + + - name: Report validation results + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: report.xml + path: | + report.xml + diff.sql + + - name: Validate XML + run: | + if grep -q "" "report.xml"; then + echo + echo "Migrations are out of sync with sqlproj!" + exit 1 + else + echo "Report looks good" + fi + shell: bash + + - name: Docker Compose down + if: ${{ always() }} + working-directory: "dev" + run: docker compose down + shell: pwsh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..78890f1d1b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,58 @@ +--- +name: Testing + +on: + workflow_dispatch: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + pull_request: + +env: + _AZ_REGISTRY: "bitwardenprod.azurecr.io" + +jobs: + testing: + name: Run tests + if: ${{ startsWith(github.head_ref, 'version_bump_') == false }} + runs-on: ubuntu-22.04 + env: + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + steps: + - name: Check out repo + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - name: Set up .NET + uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 + + - name: Print environment + run: | + dotnet --info + nuget help | grep Version + echo "GitHub ref: $GITHUB_REF" + echo "GitHub event: $GITHUB_EVENT" + + - name: Remove SQL project + run: dotnet sln bitwarden-server.sln remove src/Sql/Sql.sqlproj + + - name: Test OSS solution + run: dotnet test ./test --configuration Debug --logger "trx;LogFileName=oss-test-results.trx" /p:CoverletOutputFormatter="cobertura" --collect:"XPlat Code Coverage" + + - name: Test Bitwarden solution + 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 + if: always() + with: + name: Test Results + path: "**/*-test-results.trx" + reporter: dotnet-trx + fail-on-error: true + + - name: Upload to codecov.io + uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index ea6af8136e..3258a94eb1 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -1,6 +1,6 @@ --- -name: Version Bump -run-name: Version Bump - v${{ inputs.version_number }} +name: Bump version +run-name: Bump version to ${{ inputs.version_number }} on: workflow_dispatch: @@ -16,10 +16,10 @@ on: jobs: bump_version: - name: "Bump Version to v${{ inputs.version_number }}" + name: Bump runs-on: ubuntu-22.04 steps: - - name: Login to Azure - CI Subscription + - name: Log in to Azure - CI subscription uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} @@ -33,11 +33,20 @@ jobs: github-gpg-private-key-passphrase, github-pat-bitwarden-devops-bot-repo-scope" - - name: Checkout Branch + - name: Check out branch uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 with: ref: main - repository: bitwarden/server + + - name: Check if RC branch exists + if: ${{ inputs.cut_rc_branch == true }} + run: | + remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l) + if [[ "${remote_rc_branch_check}" -gt 0 ]]; then + echo "Remote RC branch exists." + echo "Please delete current RC branch before running again." + exit 1 + fi - name: Import GPG key uses: crazy-max/ghaction-import-gpg@82a020f1f7f605c65dd2449b392a52c3fcfef7ef # v6.0.0 @@ -47,7 +56,7 @@ jobs: git_user_signingkey: true git_commit_gpgsign: true - - name: Create Version Branch + - name: Create version branch id: create-branch run: | NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }} @@ -78,13 +87,13 @@ jobs: exit 1 fi - - name: Bump Version - Props + - name: Bump version props uses: bitwarden/gh-actions/version-bump@main with: version: ${{ inputs.version_number }} file_path: "Directory.Build.props" - - name: Setup git + - name: Set up Git run: | git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com" git config --local user.name "bitwarden-devops-bot" @@ -109,7 +118,7 @@ jobs: PR_BRANCH: ${{ steps.create-branch.outputs.name }} run: git push -u origin $PR_BRANCH - - name: Create Version PR + - name: Create version PR if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} id: create-pr env: @@ -152,28 +161,36 @@ jobs: if: ${{ inputs.cut_rc_branch == true }} runs-on: ubuntu-22.04 steps: - - name: Checkout Branch + - name: Check out branch uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: ref: main + + - name: Install xmllint + run: sudo apt install -y libxml2-utils - - name: Check if RC branch exists + - name: Verify version has been updated + env: + NEW_VERSION: ${{ inputs.version_number }} run: | - remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l) - if [[ "${remote_rc_branch_check}" -gt 0 ]]; then - echo "Remote RC branch exists." - echo "Please delete current RC branch before running again." - exit 1 - fi + # Wait for version to change. + while : ; do + echo "Waiting for version to be updated..." + git pull --force + CURRENT_VERSION=$(xmllint -xpath "/Project/PropertyGroup/Version/text()" Directory.Build.props) + + # If the versions don't match we continue the loop, otherwise we break out of the loop. + [[ "$NEW_VERSION" != "$CURRENT_VERSION" ]] || break + sleep 10 + done - name: Cut RC branch run: | git switch --quiet --create rc git push --quiet --set-upstream origin rc - move-future-db-scripts: - name: Move future DB scripts + name: Move finalization database scripts needs: cut_rc uses: ./.github/workflows/_move_finalization_db_scripts.yml secrets: inherit diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml deleted file mode 100644 index fc1db4d390..0000000000 --- a/.github/workflows/workflow-linter.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Workflow Linter - -on: - pull_request: - paths: - - .github/workflows/** - -jobs: - call-workflow: - uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main diff --git a/.vscode/launch.json b/.vscode/launch.json index e260116a24..330d34929c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -252,7 +252,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Identity/bin/Debug/net6.0/Identity.dll", + "program": "${workspaceFolder}/src/Identity/bin/Debug/net8.0/Identity.dll", "args": [], "cwd": "${workspaceFolder}/src/Identity", "stopAtEntry": false, @@ -271,7 +271,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Api/bin/Debug/net6.0/Api.dll", + "program": "${workspaceFolder}/src/Api/bin/Debug/net8.0/Api.dll", "args": [], "cwd": "${workspaceFolder}/src/Api", "stopAtEntry": false, @@ -290,7 +290,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Billing/bin/Debug/net6.0/Billing.dll", + "program": "${workspaceFolder}/src/Billing/bin/Debug/net8.0/Billing.dll", "args": [], "cwd": "${workspaceFolder}/src/Billing", "stopAtEntry": false, @@ -310,7 +310,7 @@ "type": "coreclr", "request": "launch", "OS-COMMENT4": "If you have changed target frameworks, make sure to update the program path.", - "program": "${workspaceFolder}/src/Admin/bin/Debug/net6.0/Admin.dll", + "program": "${workspaceFolder}/src/Admin/bin/Debug/net8.0/Admin.dll", "args": [], "cwd": "${workspaceFolder}/src/Admin", "stopAtEntry": false, @@ -330,7 +330,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/bitwarden_license/src/Sso/bin/Debug/net6.0/Sso.dll", + "program": "${workspaceFolder}/bitwarden_license/src/Sso/bin/Debug/net8.0/Sso.dll", "args": [], "cwd": "${workspaceFolder}/bitwarden_license/src/Sso", "stopAtEntry": false, @@ -349,7 +349,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/EventsProcessor/bin/Debug/net6.0/EventsProcessor.dll", + "program": "${workspaceFolder}/src/EventsProcessor/bin/Debug/net8.0/EventsProcessor.dll", "args": [], "cwd": "${workspaceFolder}/src/EventsProcessor", "stopAtEntry": false, @@ -368,7 +368,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Icons/bin/Debug/net6.0/Icons.dll", + "program": "${workspaceFolder}/src/Icons/bin/Debug/net8.0/Icons.dll", "args": [], "cwd": "${workspaceFolder}/src/Icons", "stopAtEntry": false, @@ -387,7 +387,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Notifications/bin/Debug/net6.0/Notifications.dll", + "program": "${workspaceFolder}/src/Notifications/bin/Debug/net8.0/Notifications.dll", "args": [], "cwd": "${workspaceFolder}/src/Notifications", "stopAtEntry": false, @@ -406,7 +406,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Identity/bin/Debug/net6.0/Identity.dll", + "program": "${workspaceFolder}/src/Identity/bin/Debug/net8.0/Identity.dll", "args": [], "cwd": "${workspaceFolder}/src/Identity", "stopAtEntry": false, @@ -427,7 +427,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Api/bin/Debug/net6.0/Api.dll", + "program": "${workspaceFolder}/src/Api/bin/Debug/net8.0/Api.dll", "args": [], "cwd": "${workspaceFolder}/src/Api", "stopAtEntry": false, @@ -449,7 +449,7 @@ "type": "coreclr", "request": "launch", "OS-COMMENT4": "If you have changed target frameworks, make sure to update the program path.", - "program": "${workspaceFolder}/src/Admin/bin/Debug/net6.0/Admin.dll", + "program": "${workspaceFolder}/src/Admin/bin/Debug/net8.0/Admin.dll", "args": [], "cwd": "${workspaceFolder}/src/Admin", "stopAtEntry": false, @@ -471,7 +471,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/bitwarden_license/src/Sso/bin/Debug/net6.0/Sso.dll", + "program": "${workspaceFolder}/bitwarden_license/src/Sso/bin/Debug/net8.0/Sso.dll", "args": [], "cwd": "${workspaceFolder}/bitwarden_license/src/Sso", "stopAtEntry": false, @@ -492,7 +492,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/Notifications/bin/Debug/net6.0/Notifications.dll", + "program": "${workspaceFolder}/src/Notifications/bin/Debug/net8.0/Notifications.dll", "args": [], "cwd": "${workspaceFolder}/src/Notifications", "stopAtEntry": false, @@ -513,7 +513,7 @@ "requireExactSource": true, "type": "coreclr", "request": "launch", - "program": "${workspaceFolder}/src/EventsProcessor/bin/Debug/net6.0/EventsProcessor.dll", + "program": "${workspaceFolder}/src/EventsProcessor/bin/Debug/net8.0/EventsProcessor.dll", "args": [], "cwd": "${workspaceFolder}/src/EventsProcessor", "stopAtEntry": false, diff --git a/Directory.Build.props b/Directory.Build.props index 98d030daa7..7fd4d54306 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,10 @@ - net6.0 - 2024.1.0 + net8.0 + + 2024.2.2 + Bit.$(MSBuildProjectName) enable false @@ -17,31 +19,31 @@ - 17.1.0 + 17.8.0 - 2.4.1 + 2.6.6 - 2.4.3 + 2.5.6 - 3.1.2 + 6.0.0 - 4.3.0 + 5.1.0 - 4.17.0 + 4.18.1 - 4.17.0 + 4.18.1 - + diff --git a/bitwarden_license/src/Sso/Startup.cs b/bitwarden_license/src/Sso/Startup.cs index f6be418bd8..5ed613e159 100644 --- a/bitwarden_license/src/Sso/Startup.cs +++ b/bitwarden_license/src/Sso/Startup.cs @@ -65,7 +65,7 @@ public class Startup } // Authentication - services.AddDistributedIdentityServices(globalSettings); + services.AddDistributedIdentityServices(); services.AddAuthentication() .AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); services.AddSsoServices(globalSettings); diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs index 17a7b9e8c7..8bde8f84a1 100644 --- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs +++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs @@ -349,7 +349,9 @@ public class DynamicAuthenticationSchemeProvider : AuthenticationSchemeProvider } var spEntityId = new Sustainsys.Saml2.Metadata.EntityId( - SsoConfigurationData.BuildSaml2ModulePath(_globalSettings.BaseServiceUri.Sso)); + SsoConfigurationData.BuildSaml2ModulePath( + _globalSettings.BaseServiceUri.Sso, + config.SpUniqueEntityId ? name : null)); bool? allowCreate = null; if (config.SpNameIdFormat != Saml2NameIdFormat.Transient) { @@ -415,7 +417,7 @@ public class DynamicAuthenticationSchemeProvider : AuthenticationSchemeProvider }; options.IdentityProviders.Add(idp); - return new DynamicAuthenticationScheme(name, name, typeof(Saml2BitHandler), options, SsoType.Saml2); + return new DynamicAuthenticationScheme(name, name, typeof(Saml2Handler), options, SsoType.Saml2); } private NameIdFormat GetNameIdFormat(Saml2NameIdFormat format) diff --git a/bitwarden_license/src/Sso/Utilities/Saml2BitHandler.cs b/bitwarden_license/src/Sso/Utilities/Saml2BitHandler.cs deleted file mode 100644 index 6e5a37fb96..0000000000 --- a/bitwarden_license/src/Sso/Utilities/Saml2BitHandler.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System.Text; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.Extensions.Options; -using Sustainsys.Saml2.AspNetCore2; -using Sustainsys.Saml2.WebSso; - -namespace Bit.Sso.Utilities; - -// Temporary handler for validating Saml2 requests -// Most of this is taken from Sustainsys.Saml2.AspNetCore2.Saml2Handler -// TODO: PM-3641 - Remove this handler once there is a proper solution -public class Saml2BitHandler : IAuthenticationRequestHandler -{ - private readonly Saml2Handler _saml2Handler; - private string _scheme; - - private readonly IOptionsMonitorCache _optionsCache; - private Saml2Options _options; - private HttpContext _context; - private readonly IDataProtector _dataProtector; - private readonly IOptionsFactory _optionsFactory; - private bool _emitSameSiteNone; - - public Saml2BitHandler( - IOptionsMonitorCache optionsCache, - IDataProtectionProvider dataProtectorProvider, - IOptionsFactory optionsFactory) - { - if (dataProtectorProvider == null) - { - throw new ArgumentNullException(nameof(dataProtectorProvider)); - } - - _optionsFactory = optionsFactory; - _optionsCache = optionsCache; - - _saml2Handler = new Saml2Handler(optionsCache, dataProtectorProvider, optionsFactory); - _dataProtector = dataProtectorProvider.CreateProtector(_saml2Handler.GetType().FullName); - } - - public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) - { - _context = context ?? throw new ArgumentNullException(nameof(context)); - _options = _optionsCache.GetOrAdd(scheme.Name, () => _optionsFactory.Create(scheme.Name)); - _emitSameSiteNone = _options.Notifications.EmitSameSiteNone(context.Request.GetUserAgent()); - _scheme = scheme.Name; - - return _saml2Handler.InitializeAsync(scheme, context); - } - - - public async Task HandleRequestAsync() - { - if (!_context.Request.Path.StartsWithSegments(_options.SPOptions.ModulePath, StringComparison.Ordinal)) - { - return false; - } - - var commandName = _context.Request.Path.Value.Substring( - _options.SPOptions.ModulePath.Length).TrimStart('/'); - - var commandResult = CommandFactory.GetCommand(commandName).Run( - _context.ToHttpRequestData(_options.CookieManager, _dataProtector.Unprotect), _options); - - // Scheme is the organization ID since we use dynamic handlers for authentication schemes. - // We need to compare this to the scheme returned in the RelayData to ensure this value hasn't been - // tampered with - if (commandResult.RelayData["scheme"] != _scheme) - { - return false; - } - - await commandResult.Apply( - _context, _dataProtector, _options.CookieManager, _options.SignInScheme, _options.SignOutScheme, _emitSameSiteNone); - - return true; - } - - public Task AuthenticateAsync() => _saml2Handler.AuthenticateAsync(); - - public Task ChallengeAsync(AuthenticationProperties properties) => _saml2Handler.ChallengeAsync(properties); - - public Task ForbidAsync(AuthenticationProperties properties) => _saml2Handler.ForbidAsync(properties); -} - - -static class HttpRequestExtensions -{ - public static HttpRequestData ToHttpRequestData( - this HttpContext httpContext, - ICookieManager cookieManager, - Func cookieDecryptor) - { - var request = httpContext.Request; - - var uri = new Uri( - request.Scheme - + "://" - + request.Host - + request.Path - + request.QueryString); - - var pathBase = httpContext.Request.PathBase.Value; - pathBase = string.IsNullOrEmpty(pathBase) ? "/" : pathBase; - IEnumerable>> formData = null; - if (httpContext.Request.Method == "POST" && httpContext.Request.HasFormContentType) - { - formData = request.Form.Select( - f => new KeyValuePair>(f.Key, f.Value)); - } - - return new HttpRequestData( - httpContext.Request.Method, - uri, - pathBase, - formData, - cookieName => cookieManager.GetRequestCookie(httpContext, cookieName), - cookieDecryptor, - httpContext.User); - } - - public static string GetUserAgent(this HttpRequest request) - { - return request.Headers["user-agent"].FirstOrDefault() ?? ""; - } -} - -static class CommandResultExtensions -{ - public static async Task Apply( - this CommandResult commandResult, - HttpContext httpContext, - IDataProtector dataProtector, - ICookieManager cookieManager, - string signInScheme, - string signOutScheme, - bool emitSameSiteNone) - { - httpContext.Response.StatusCode = (int)commandResult.HttpStatusCode; - - if (commandResult.Location != null) - { - httpContext.Response.Headers["Location"] = commandResult.Location.OriginalString; - } - - if (!string.IsNullOrEmpty(commandResult.SetCookieName)) - { - var cookieData = HttpRequestData.ConvertBinaryData( - dataProtector.Protect(commandResult.GetSerializedRequestState())); - - cookieManager.AppendResponseCookie( - httpContext, - commandResult.SetCookieName, - cookieData, - new CookieOptions() - { - HttpOnly = true, - Secure = commandResult.SetCookieSecureFlag, - // We are expecting a different site to POST back to us, - // so the ASP.Net Core default of Lax is not appropriate in this case - SameSite = emitSameSiteNone ? SameSiteMode.None : (SameSiteMode)(-1), - IsEssential = true - }); - } - - foreach (var h in commandResult.Headers) - { - httpContext.Response.Headers.Add(h.Key, h.Value); - } - - if (!string.IsNullOrEmpty(commandResult.ClearCookieName)) - { - cookieManager.DeleteCookie( - httpContext, - commandResult.ClearCookieName, - new CookieOptions - { - Secure = commandResult.SetCookieSecureFlag - }); - } - - if (!string.IsNullOrEmpty(commandResult.Content)) - { - var buffer = Encoding.UTF8.GetBytes(commandResult.Content); - httpContext.Response.ContentType = commandResult.ContentType; - await httpContext.Response.Body.WriteAsync(buffer, 0, buffer.Length); - } - - if (commandResult.Principal != null) - { - var authProps = new AuthenticationProperties(commandResult.RelayData) - { - RedirectUri = commandResult.Location.OriginalString - }; - await httpContext.SignInAsync(signInScheme, commandResult.Principal, authProps); - } - - if (commandResult.TerminateLocalSession) - { - await httpContext.SignOutAsync(signOutScheme ?? signInScheme); - } - } -} diff --git a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs new file mode 100644 index 0000000000..7148bcb17b --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/ProviderFeatures/RemoveOrganizationFromProviderCommandTests.cs @@ -0,0 +1,132 @@ +using Bit.Commercial.Core.AdminConsole.Providers; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Entities.Provider; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Stripe; +using Xunit; + +namespace Bit.Commercial.Core.Test.AdminConsole.ProviderFeatures; + +[SutProviderCustomize] +public class RemoveOrganizationFromProviderCommandTests +{ + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_NoProvider_BadRequest( + SutProvider sutProvider) + { + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.RemoveOrganizationFromProvider(null, null, null)); + + Assert.Equal("Failed to remove organization. Please contact support.", exception.Message); + } + + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_NoProviderOrganization_BadRequest( + Provider provider, + SutProvider sutProvider) + { + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.RemoveOrganizationFromProvider(provider, null, null)); + + Assert.Equal("Failed to remove organization. Please contact support.", exception.Message); + } + + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_NoOrganization_BadRequest( + Provider provider, + ProviderOrganization providerOrganization, + SutProvider sutProvider) + { + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.RemoveOrganizationFromProvider( + provider, providerOrganization, null)); + + Assert.Equal("Failed to remove organization. Please contact support.", exception.Message); + } + + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_MismatchedProviderOrganization_BadRequest( + Provider provider, + ProviderOrganization providerOrganization, + Organization organization, + SutProvider sutProvider) + { + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization)); + + Assert.Equal("Failed to remove organization. Please contact support.", exception.Message); + } + + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_NoConfirmedOwners_BadRequest( + Provider provider, + ProviderOrganization providerOrganization, + Organization organization, + SutProvider sutProvider) + { + providerOrganization.ProviderId = provider.Id; + + sutProvider.GetDependency().HasConfirmedOwnersExceptAsync( + providerOrganization.OrganizationId, + Array.Empty(), + includeProvider: false) + .Returns(false); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization)); + + Assert.Equal("Organization must have at least one confirmed owner.", exception.Message); + } + + [Theory, BitAutoData] + public async Task RemoveOrganizationFromProvider_MakesCorrectInvocations( + Provider provider, + ProviderOrganization providerOrganization, + Organization organization, + SutProvider sutProvider) + { + providerOrganization.ProviderId = provider.Id; + + var organizationRepository = sutProvider.GetDependency(); + + sutProvider.GetDependency().HasConfirmedOwnersExceptAsync( + providerOrganization.OrganizationId, + Array.Empty(), + includeProvider: false) + .Returns(true); + + var organizationOwnerEmails = new List { "a@gmail.com", "b@gmail.com" }; + + organizationRepository.GetOwnerEmailAddressesById(organization.Id).Returns(organizationOwnerEmails); + + await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization); + + await organizationRepository.Received(1).ReplaceAsync(Arg.Is( + org => org.Id == organization.Id && org.BillingEmail == "a@gmail.com")); + + var stripeAdapter = sutProvider.GetDependency(); + + await stripeAdapter.Received(1).CustomerUpdateAsync( + organization.GatewayCustomerId, Arg.Is( + options => options.Coupon == string.Empty && options.Email == "a@gmail.com")); + + await stripeAdapter.Received(1).SubscriptionUpdateAsync( + organization.GatewaySubscriptionId, Arg.Is( + options => options.CollectionMethod == "send_invoice" && options.DaysUntilDue == 30)); + + await sutProvider.GetDependency().Received(1).SendProviderUpdatePaymentMethod( + organization.Id, + organization.Name, + provider.Name, + Arg.Is>(emails => emails.Contains("a@gmail.com") && emails.Contains("b@gmail.com"))); + + await sutProvider.GetDependency().Received(1) + .DeleteAsync(providerOrganization); + + await sutProvider.GetDependency().Received(1).LogProviderOrganizationEventAsync( + providerOrganization, + EventType.ProviderOrganization_Removed); + } +} 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 24167e7141..b503d0d5a7 100644 --- a/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/AdminConsole/Services/ProviderServiceTests.cs @@ -12,6 +12,7 @@ using Bit.Core.Exceptions; using Bit.Core.Models.Business; 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; @@ -513,7 +514,7 @@ public class ProviderServiceTests await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogProviderOrganizationEventsAsync(default); } - [Theory, BitAutoData] + [Theory, OrganizationCustomize(FlexibleCollections = false), BitAutoData] public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, User user, SutProvider sutProvider) { @@ -522,7 +523,7 @@ public class ProviderServiceTests sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); var providerOrganizationRepository = sutProvider.GetDependency(); sutProvider.GetDependency().SignUpAsync(organizationSignup, true) - .Returns(Tuple.Create(organization, null as OrganizationUser)); + .Returns((organization, null as OrganizationUser, new Collection())); var providerOrganization = await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user); @@ -538,70 +539,45 @@ public class ProviderServiceTests t.First().Item1.Emails.First() == clientOwnerEmail && t.First().Item1.Type == OrganizationUserType.Owner && t.First().Item1.AccessAll && + !t.First().Item1.Collections.Any() && + t.First().Item2 == null)); + } + + [Theory, OrganizationCustomize(FlexibleCollections = true), BitAutoData] + public async Task CreateOrganizationAsync_WithFlexibleCollections_SetsAccessAllToFalse + (Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, + User user, SutProvider sutProvider, Collection defaultCollection) + { + organizationSignup.Plan = PlanType.EnterpriseAnnually; + + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + sutProvider.GetDependency().SignUpAsync(organizationSignup, true) + .Returns((organization, null as OrganizationUser, defaultCollection)); + + var providerOrganization = + await sutProvider.Sut.CreateOrganizationAsync(provider.Id, organizationSignup, clientOwnerEmail, user); + + await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency() + .Received().LogProviderOrganizationEventAsync(providerOrganization, + EventType.ProviderOrganization_Created); + await sutProvider.GetDependency() + .Received().InviteUsersAsync(organization.Id, user.Id, Arg.Is>( + t => t.Count() == 1 && + 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 && + t.First().Item1.Collections.Single().Manage && t.First().Item2 == null)); } [Theory, BitAutoData] - public async Task RemoveOrganization_ProviderOrganizationIsInvalid_Throws(Provider provider, - ProviderOrganization providerOrganization, User user, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) - .ReturnsNull(); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id)); - Assert.Equal("Invalid organization.", exception.Message); - } - - [Theory, BitAutoData] - public async Task RemoveOrganization_ProviderOrganizationBelongsToWrongProvider_Throws(Provider provider, - ProviderOrganization providerOrganization, User user, SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) - .Returns(providerOrganization); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id)); - Assert.Equal("Invalid organization.", exception.Message); - } - - [Theory, BitAutoData] - public async Task RemoveOrganization_HasNoOwners_Throws(Provider provider, - ProviderOrganization providerOrganization, User user, SutProvider sutProvider) - { - providerOrganization.ProviderId = provider.Id; - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - sutProvider.GetDependency().GetByIdAsync(providerOrganization.Id) - .Returns(providerOrganization); - sutProvider.GetDependency().HasConfirmedOwnersExceptAsync(default, default, default) - .ReturnsForAnyArgs(false); - - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id)); - Assert.Equal("Organization needs to have at least one confirmed owner.", exception.Message); - } - - [Theory, BitAutoData] - public async Task RemoveOrganization_Success(Provider provider, - ProviderOrganization providerOrganization, User user, SutProvider sutProvider) - { - providerOrganization.ProviderId = provider.Id; - sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); - var providerOrganizationRepository = sutProvider.GetDependency(); - providerOrganizationRepository.GetByIdAsync(providerOrganization.Id).Returns(providerOrganization); - sutProvider.GetDependency().HasConfirmedOwnersExceptAsync(default, default, default) - .ReturnsForAnyArgs(true); - - await sutProvider.Sut.RemoveOrganizationAsync(provider.Id, providerOrganization.Id, user.Id); - await providerOrganizationRepository.Received().DeleteAsync(providerOrganization); - await sutProvider.GetDependency().Received() - .LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Removed); - } - - [Theory, BitAutoData] - public async Task AddOrganization_CreateAfterNov162023_PlanTypeDoesNotUpdated(Provider provider, Organization organization, string key, + public async Task AddOrganization_CreateAfterNov62023_PlanTypeDoesNotUpdated(Provider provider, Organization organization, string key, SutProvider sutProvider) { provider.Type = ProviderType.Msp; @@ -623,10 +599,10 @@ public class ProviderServiceTests } [Theory, BitAutoData] - public async Task AddOrganization_CreateBeforeNov162023_PlanTypeUpdated(Provider provider, Organization organization, string key, + public async Task AddOrganization_CreateBeforeNov62023_PlanTypeUpdated(Provider provider, Organization organization, string key, SutProvider sutProvider) { - var newCreationDate = DateTime.UtcNow.AddMonths(-3); + var newCreationDate = new DateTime(2023, 11, 5); BackdateProviderCreationDate(provider, newCreationDate); provider.Type = ProviderType.Msp; diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/CountNewServiceAccountSlotsRequiredQueryTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/CountNewServiceAccountSlotsRequiredQueryTests.cs index 659e72729a..3445b97413 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/CountNewServiceAccountSlotsRequiredQueryTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/ServiceAccounts/CountNewServiceAccountSlotsRequiredQueryTests.cs @@ -28,7 +28,6 @@ public class CountNewServiceAccountSlotsRequiredQueryTests { organization.UseSecretsManager = true; organization.SmServiceAccounts = organizationSmServiceAccounts; - organization.SecretsManagerBeta = false; sutProvider.GetDependency() .GetByIdAsync(organization.Id) @@ -62,7 +61,6 @@ public class CountNewServiceAccountSlotsRequiredQueryTests organization.UseSecretsManager = true; organization.SmServiceAccounts = null; - organization.SecretsManagerBeta = false; sutProvider.GetDependency() .GetByIdAsync(organization.Id) @@ -80,27 +78,6 @@ public class CountNewServiceAccountSlotsRequiredQueryTests .GetServiceAccountCountByOrganizationIdAsync(default); } - [Theory, BitAutoData] - public async Task CountNewServiceAccountSlotsRequiredAsync_WithSecretsManagerBeta_ReturnsZero( - int serviceAccountsToAdd, - Organization organization, - SutProvider sutProvider) - { - organization.UseSecretsManager = true; - organization.SecretsManagerBeta = true; - - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - var result = await sutProvider.Sut.CountNewServiceAccountSlotsRequiredAsync(organization.Id, serviceAccountsToAdd); - - Assert.Equal(0, result); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetServiceAccountCountByOrganizationIdAsync(default); - } - [Theory, BitAutoData] public async Task CountNewServiceAccountSlotsRequiredAsync_WithNonExistentOrganizationId_ThrowsNotFound( Guid organizationId, int serviceAccountsToAdd, diff --git a/bitwarden_license/test/Scim.IntegrationTest/Factories/ScimApplicationFactory.cs b/bitwarden_license/test/Scim.IntegrationTest/Factories/ScimApplicationFactory.cs index 6ebd59283d..bf64f5cee8 100644 --- a/bitwarden_license/test/Scim.IntegrationTest/Factories/ScimApplicationFactory.cs +++ b/bitwarden_license/test/Scim.IntegrationTest/Factories/ScimApplicationFactory.cs @@ -90,12 +90,12 @@ public class ScimApplicationFactory : WebApplicationFactoryBase public async Task GroupsPostAsync(Guid organizationId, ScimGroupRequestModel model) { - return await Server.PostAsync($"/v2/{organizationId}/groups", GetStringContent(model), httpContext => httpContext.Request.Headers.Add(HeaderNames.UserAgent, "Okta")); + return await Server.PostAsync($"/v2/{organizationId}/groups", GetStringContent(model), httpContext => httpContext.Request.Headers.Append(HeaderNames.UserAgent, "Okta")); } public async Task GroupsPutAsync(Guid organizationId, Guid id, ScimGroupRequestModel model) { - return await Server.PutAsync($"/v2/{organizationId}/groups/{id}", GetStringContent(model), httpContext => httpContext.Request.Headers.Add(HeaderNames.UserAgent, "Okta")); + return await Server.PutAsync($"/v2/{organizationId}/groups/{id}", GetStringContent(model), httpContext => httpContext.Request.Headers.Append(HeaderNames.UserAgent, "Okta")); } public async Task GroupsPatchAsync(Guid organizationId, Guid id, ScimPatchModel model) diff --git a/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj b/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj index 1a2b9bc76e..7ece41ecac 100644 --- a/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj +++ b/bitwarden_license/test/Scim.IntegrationTest/Scim.IntegrationTest.csproj @@ -9,7 +9,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/global.json b/global.json index 10b65be864..391ba3c2a3 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.100", + "version": "8.0.100", "rollForward": "latestFeature" } } diff --git a/perf/MicroBenchmarks/Identity/IdentityServer/RedisPersistedGrantStoreTests.cs b/perf/MicroBenchmarks/Identity/IdentityServer/PersistedGrantStoreTests.cs similarity index 91% rename from perf/MicroBenchmarks/Identity/IdentityServer/RedisPersistedGrantStoreTests.cs rename to perf/MicroBenchmarks/Identity/IdentityServer/PersistedGrantStoreTests.cs index cbb11acbba..c5b79a7b88 100644 --- a/perf/MicroBenchmarks/Identity/IdentityServer/RedisPersistedGrantStoreTests.cs +++ b/perf/MicroBenchmarks/Identity/IdentityServer/PersistedGrantStoreTests.cs @@ -3,19 +3,15 @@ using Bit.Identity.IdentityServer; using Bit.Infrastructure.Dapper.Auth.Repositories; using Duende.IdentityServer.Models; using Duende.IdentityServer.Stores; -using Microsoft.Extensions.Logging.Abstractions; -using StackExchange.Redis; namespace Bit.MicroBenchmarks.Identity.IdentityServer; [MemoryDiagnoser] -public class RedisPersistedGrantStoreTests +public class PersistedGrantStoreTests { const string SQL = nameof(SQL); - const string Redis = nameof(Redis); const string Cosmos = nameof(Cosmos); - private readonly IPersistedGrantStore _redisGrantStore; private readonly IPersistedGrantStore _sqlGrantStore; private readonly IPersistedGrantStore _cosmosGrantStore; private readonly PersistedGrant _updateGrant; @@ -39,14 +35,8 @@ public class RedisPersistedGrantStoreTests // 15) "ClientId" // 16) "web" - public RedisPersistedGrantStoreTests() + public PersistedGrantStoreTests() { - _redisGrantStore = new RedisPersistedGrantStore( - ConnectionMultiplexer.Connect("localhost"), - NullLogger.Instance, - new InMemoryPersistedGrantStore() - ); - var sqlConnectionString = "YOUR CONNECTION STRING HERE"; _sqlGrantStore = new PersistedGrantStore( new GrantRepository( @@ -78,17 +68,13 @@ public class RedisPersistedGrantStoreTests }; } - [Params(Redis, SQL, Cosmos)] + [Params(SQL, Cosmos)] public string StoreType { get; set; } = null!; [GlobalSetup] public void Setup() { - if (StoreType == Redis) - { - _grantStore = _redisGrantStore; - } - else if (StoreType == SQL) + if (StoreType == SQL) { _grantStore = _sqlGrantStore; } diff --git a/perf/MicroBenchmarks/MicroBenchmarks.csproj b/perf/MicroBenchmarks/MicroBenchmarks.csproj index 7a2bdb02d9..ce2d2fdbcb 100644 --- a/perf/MicroBenchmarks/MicroBenchmarks.csproj +++ b/perf/MicroBenchmarks/MicroBenchmarks.csproj @@ -2,13 +2,12 @@ Exe - net6.0 enable enable - + diff --git a/perf/load/config.js b/perf/load/config.js index 54413cba01..f4e1b33bc0 100644 --- a/perf/load/config.js +++ b/perf/load/config.js @@ -43,7 +43,7 @@ export const options = { }, thresholds: { http_req_failed: ["rate<0.01"], - http_req_duration: ["p(95)<200"], + http_req_duration: ["p(95)<350"], }, }; diff --git a/perf/load/groups.js b/perf/load/groups.js index a668f7f023..aee3b3e94d 100644 --- a/perf/load/groups.js +++ b/perf/load/groups.js @@ -44,7 +44,7 @@ export const options = { }, thresholds: { http_req_failed: ["rate<0.01"], - http_req_duration: ["p(95)<300"], + http_req_duration: ["p(95)<400"], }, }; diff --git a/src/Admin/Admin.csproj b/src/Admin/Admin.csproj index 5557884b1d..cd30e841b4 100644 --- a/src/Admin/Admin.csproj +++ b/src/Admin/Admin.csproj @@ -1,4 +1,4 @@ - + bitwarden-Admin @@ -24,8 +24,4 @@ - - - - diff --git a/src/Admin/AdminSettings.cs b/src/Admin/AdminSettings.cs index 6941bbc8f6..18694e3e38 100644 --- a/src/Admin/AdminSettings.cs +++ b/src/Admin/AdminSettings.cs @@ -3,13 +3,5 @@ public class AdminSettings { public virtual string Admins { get; set; } - public virtual CloudflareSettings Cloudflare { get; set; } public int? DeleteTrashDaysAgo { get; set; } - - public class CloudflareSettings - { - public string ZoneId { get; set; } - public string AuthEmail { get; set; } - public string AuthKey { get; set; } - } } diff --git a/src/Admin/Controllers/LogsController.cs b/src/Admin/Controllers/LogsController.cs deleted file mode 100644 index d3934cb8e5..0000000000 --- a/src/Admin/Controllers/LogsController.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Bit.Admin.Models; -using Bit.Admin.Utilities; -using Bit.Core.Settings; -using Bit.Core.Utilities; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.Cosmos; -using Microsoft.Azure.Cosmos.Linq; -using Serilog.Events; - -namespace Bit.Admin.Controllers; - -[Authorize] -[SelfHosted(NotSelfHostedOnly = true)] -[RequirePermission(Enums.Permission.Logs_View)] -public class LogsController : Controller -{ - private const string Database = "Diagnostics"; - private const string Container = "Logs"; - - private readonly GlobalSettings _globalSettings; - - public LogsController(GlobalSettings globalSettings) - { - _globalSettings = globalSettings; - } - - public async Task Index(string cursor = null, int count = 50, - LogEventLevel? level = null, string project = null, DateTime? start = null, DateTime? end = null) - { - using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri, - _globalSettings.DocumentDb.Key)) - { - var cosmosContainer = client.GetContainer(Database, Container); - var query = cosmosContainer.GetItemLinqQueryable( - requestOptions: new QueryRequestOptions() - { - MaxItemCount = count - }, - continuationToken: cursor - ).AsQueryable(); - - if (level.HasValue) - { - query = query.Where(l => l.Level == level.Value.ToString()); - } - if (!string.IsNullOrWhiteSpace(project)) - { - query = query.Where(l => l.Properties != null && l.Properties["Project"] == (object)project); - } - if (start.HasValue) - { - query = query.Where(l => l.Timestamp >= start.Value); - } - if (end.HasValue) - { - query = query.Where(l => l.Timestamp <= end.Value); - } - var feedIterator = query.OrderByDescending(l => l.Timestamp).ToFeedIterator(); - var response = await feedIterator.ReadNextAsync(); - - return View(new LogsModel - { - Level = level, - Project = project, - Start = start, - End = end, - Items = response.ToList(), - Count = count, - Cursor = cursor, - NextCursor = response.ContinuationToken - }); - } - } - - public async Task View(Guid id) - { - using (var client = new CosmosClient(_globalSettings.DocumentDb.Uri, - _globalSettings.DocumentDb.Key)) - { - var cosmosContainer = client.GetContainer(Database, Container); - var query = cosmosContainer.GetItemLinqQueryable() - .AsQueryable() - .Where(l => l.Id == id.ToString()); - - var response = await query.ToFeedIterator().ReadNextAsync(); - if (response == null || response.Count == 0) - { - return RedirectToAction("Index"); - } - return View(response.First()); - } - } -} diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index aebdcefe4f..3fba88006d 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -3,7 +3,9 @@ using Bit.Admin.Models; using Bit.Admin.Services; using Bit.Admin.Utilities; using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Providers.Interfaces; using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Commands; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -48,6 +50,9 @@ public class OrganizationsController : Controller private readonly ISecretRepository _secretRepository; private readonly IProjectRepository _projectRepository; private readonly IServiceAccountRepository _serviceAccountRepository; + private readonly IProviderOrganizationRepository _providerOrganizationRepository; + private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand; + private readonly IRemovePaymentMethodCommand _removePaymentMethodCommand; public OrganizationsController( IOrganizationService organizationService, @@ -71,7 +76,10 @@ public class OrganizationsController : Controller ICurrentContext currentContext, ISecretRepository secretRepository, IProjectRepository projectRepository, - IServiceAccountRepository serviceAccountRepository) + IServiceAccountRepository serviceAccountRepository, + IProviderOrganizationRepository providerOrganizationRepository, + IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand, + IRemovePaymentMethodCommand removePaymentMethodCommand) { _organizationService = organizationService; _organizationRepository = organizationRepository; @@ -95,6 +103,9 @@ public class OrganizationsController : Controller _secretRepository = secretRepository; _projectRepository = projectRepository; _serviceAccountRepository = serviceAccountRepository; + _providerOrganizationRepository = providerOrganizationRepository; + _removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand; + _removePaymentMethodCommand = removePaymentMethodCommand; } [RequirePermission(Permission.Org_List_View)] @@ -202,7 +213,6 @@ public class OrganizationsController : Controller var organization = await GetOrganization(id, model); if (organization.UseSecretsManager && - !organization.SecretsManagerBeta && !StaticStore.GetPlan(organization.PlanType).SupportsSecretsManager) { throw new BadRequestException("Plan does not support Secrets Manager"); @@ -286,6 +296,38 @@ public class OrganizationsController : Controller return Json(null); } + + [HttpPost] + [RequirePermission(Permission.Provider_Edit)] + public async Task UnlinkOrganizationFromProviderAsync(Guid id) + { + var organization = await _organizationRepository.GetByIdAsync(id); + if (organization is null) + { + return RedirectToAction("Index"); + } + + var provider = await _providerRepository.GetByOrganizationIdAsync(id); + if (provider is null) + { + return RedirectToAction("Edit", new { id }); + } + + var providerOrganization = await _providerOrganizationRepository.GetByOrganizationId(id); + if (providerOrganization is null) + { + return RedirectToAction("Edit", new { id }); + } + + await _removeOrganizationFromProviderCommand.RemoveOrganizationFromProvider( + provider, + providerOrganization, + organization); + + await _removePaymentMethodCommand.RemovePaymentMethod(organization); + + return Json(null); + } private async Task GetOrganization(Guid id, OrganizationEditModel model) { var organization = await _organizationRepository.GetByIdAsync(id); @@ -320,7 +362,6 @@ public class OrganizationsController : Controller organization.UseTotp = model.UseTotp; organization.UsersGetPremium = model.UsersGetPremium; organization.UseSecretsManager = model.UseSecretsManager; - organization.SecretsManagerBeta = model.SecretsManagerBeta; //secrets organization.SmSeats = model.SmSeats; diff --git a/src/Admin/Controllers/ProviderOrganizationsController.cs b/src/Admin/Controllers/ProviderOrganizationsController.cs new file mode 100644 index 0000000000..e21e1297f6 --- /dev/null +++ b/src/Admin/Controllers/ProviderOrganizationsController.cs @@ -0,0 +1,67 @@ +using Bit.Admin.Enums; +using Bit.Admin.Utilities; +using Bit.Core.AdminConsole.Providers.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Billing.Commands; +using Bit.Core.Repositories; +using Bit.Core.Utilities; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Admin.Controllers; + +[Authorize] +[SelfHosted(NotSelfHostedOnly = true)] +public class ProviderOrganizationsController : Controller +{ + private readonly IProviderRepository _providerRepository; + private readonly IProviderOrganizationRepository _providerOrganizationRepository; + private readonly IOrganizationRepository _organizationRepository; + private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand; + private readonly IRemovePaymentMethodCommand _removePaymentMethodCommand; + + public ProviderOrganizationsController(IProviderRepository providerRepository, + IProviderOrganizationRepository providerOrganizationRepository, + IOrganizationRepository organizationRepository, + IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand, + IRemovePaymentMethodCommand removePaymentMethodCommand) + { + _providerRepository = providerRepository; + _providerOrganizationRepository = providerOrganizationRepository; + _organizationRepository = organizationRepository; + _removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand; + _removePaymentMethodCommand = removePaymentMethodCommand; + } + + [HttpPost] + [RequirePermission(Permission.Provider_Edit)] + public async Task DeleteAsync(Guid providerId, Guid id) + { + var provider = await _providerRepository.GetByIdAsync(providerId); + if (provider is null) + { + return RedirectToAction("Index", "Providers"); + } + + var providerOrganization = await _providerOrganizationRepository.GetByIdAsync(id); + if (providerOrganization is null) + { + return RedirectToAction("View", "Providers", new { id = providerId }); + } + + var organization = await _organizationRepository.GetByIdAsync(providerOrganization.OrganizationId); + if (organization == null) + { + return RedirectToAction("View", "Providers", new { id = providerId }); + } + + await _removeOrganizationFromProviderCommand.RemoveOrganizationFromProvider( + provider, + providerOrganization, + organization); + + await _removePaymentMethodCommand.RemovePaymentMethod(organization); + + return Json(null); + } +} diff --git a/src/Admin/Controllers/UsersController.cs b/src/Admin/Controllers/UsersController.cs index e4ff88a68e..ba9d04e3af 100644 --- a/src/Admin/Controllers/UsersController.cs +++ b/src/Admin/Controllers/UsersController.cs @@ -27,7 +27,7 @@ public class UsersController : Controller private readonly IFeatureService _featureService; private bool UseFlexibleCollections => - _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); + _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections); public UsersController( IUserRepository userRepository, diff --git a/src/Admin/Dockerfile b/src/Admin/Dockerfile index 1c2264bf20..54ecbac0d6 100644 --- a/src/Admin/Dockerfile +++ b/src/Admin/Dockerfile @@ -1,11 +1,11 @@ -FROM mcr.microsoft.com/dotnet/aspnet:6.0 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 LABEL com.bitwarden.product="bitwarden" RUN apt-get update \ && apt-get install -y --no-install-recommends \ - gosu \ - curl \ + gosu \ + curl \ && rm -rf /var/lib/apt/lists/* ENV ASPNETCORE_URLS http://+:5000 diff --git a/src/Admin/Enums/Permissions.cs b/src/Admin/Enums/Permissions.cs index 2354e9435c..6b73ba4205 100644 --- a/src/Admin/Enums/Permissions.cs +++ b/src/Admin/Enums/Permissions.cs @@ -47,7 +47,5 @@ public enum Permission Tools_GenerateLicenseFile, Tools_ManageTaxRates, Tools_ManageStripeSubscriptions, - Tools_CreateEditTransaction, - - Logs_View + Tools_CreateEditTransaction } diff --git a/src/Admin/HostedServices/AmazonSqsBlockIpHostedService.cs b/src/Admin/HostedServices/AmazonSqsBlockIpHostedService.cs deleted file mode 100644 index 646da09c5d..0000000000 --- a/src/Admin/HostedServices/AmazonSqsBlockIpHostedService.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Amazon; -using Amazon.SQS; -using Amazon.SQS.Model; -using Bit.Core.Settings; -using Microsoft.Extensions.Options; - -namespace Bit.Admin.HostedServices; - -public class AmazonSqsBlockIpHostedService : BlockIpHostedService -{ - private AmazonSQSClient _client; - - public AmazonSqsBlockIpHostedService( - ILogger logger, - IOptions adminSettings, - GlobalSettings globalSettings) - : base(logger, adminSettings, globalSettings) - { } - - public override void Dispose() - { - _client?.Dispose(); - } - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - _client = new AmazonSQSClient(_globalSettings.Amazon.AccessKeyId, - _globalSettings.Amazon.AccessKeySecret, RegionEndpoint.GetBySystemName(_globalSettings.Amazon.Region)); - var blockIpQueue = await _client.GetQueueUrlAsync("block-ip", cancellationToken); - var blockIpQueueUrl = blockIpQueue.QueueUrl; - var unblockIpQueue = await _client.GetQueueUrlAsync("unblock-ip", cancellationToken); - var unblockIpQueueUrl = unblockIpQueue.QueueUrl; - - while (!cancellationToken.IsCancellationRequested) - { - var blockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = blockIpQueueUrl, - MaxNumberOfMessages = 10, - WaitTimeSeconds = 15 - }, cancellationToken); - if (blockMessageResponse.Messages.Any()) - { - foreach (var message in blockMessageResponse.Messages) - { - try - { - await BlockIpAsync(message.Body, cancellationToken); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to block IP."); - } - await _client.DeleteMessageAsync(blockIpQueueUrl, message.ReceiptHandle, cancellationToken); - } - } - - var unblockMessageResponse = await _client.ReceiveMessageAsync(new ReceiveMessageRequest - { - QueueUrl = unblockIpQueueUrl, - MaxNumberOfMessages = 10, - WaitTimeSeconds = 15 - }, cancellationToken); - if (unblockMessageResponse.Messages.Any()) - { - foreach (var message in unblockMessageResponse.Messages) - { - try - { - await UnblockIpAsync(message.Body, cancellationToken); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to unblock IP."); - } - await _client.DeleteMessageAsync(unblockIpQueueUrl, message.ReceiptHandle, cancellationToken); - } - } - - await Task.Delay(TimeSpan.FromSeconds(15)); - } - } -} diff --git a/src/Admin/HostedServices/AzureQueueBlockIpHostedService.cs b/src/Admin/HostedServices/AzureQueueBlockIpHostedService.cs deleted file mode 100644 index f1590377e1..0000000000 --- a/src/Admin/HostedServices/AzureQueueBlockIpHostedService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Azure.Storage.Queues; -using Bit.Core.Settings; -using Microsoft.Extensions.Options; - -namespace Bit.Admin.HostedServices; - -public class AzureQueueBlockIpHostedService : BlockIpHostedService -{ - private QueueClient _blockIpQueueClient; - private QueueClient _unblockIpQueueClient; - - public AzureQueueBlockIpHostedService( - ILogger logger, - IOptions adminSettings, - GlobalSettings globalSettings) - : base(logger, adminSettings, globalSettings) - { } - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - _blockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "blockip"); - _unblockIpQueueClient = new QueueClient(_globalSettings.Storage.ConnectionString, "unblockip"); - - while (!cancellationToken.IsCancellationRequested) - { - var blockMessages = await _blockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32); - if (blockMessages.Value?.Any() ?? false) - { - foreach (var message in blockMessages.Value) - { - try - { - await BlockIpAsync(message.MessageText, cancellationToken); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to block IP."); - } - await _blockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); - } - } - - var unblockMessages = await _unblockIpQueueClient.ReceiveMessagesAsync(maxMessages: 32); - if (unblockMessages.Value?.Any() ?? false) - { - foreach (var message in unblockMessages.Value) - { - try - { - await UnblockIpAsync(message.MessageText, cancellationToken); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to unblock IP."); - } - await _unblockIpQueueClient.DeleteMessageAsync(message.MessageId, message.PopReceipt); - } - } - - await Task.Delay(TimeSpan.FromSeconds(15)); - } - } -} diff --git a/src/Admin/HostedServices/AzureQueueMailHostedService.cs b/src/Admin/HostedServices/AzureQueueMailHostedService.cs index b2031a405b..cff724e4f3 100644 --- a/src/Admin/HostedServices/AzureQueueMailHostedService.cs +++ b/src/Admin/HostedServices/AzureQueueMailHostedService.cs @@ -67,14 +67,14 @@ public class AzureQueueMailHostedService : IHostedService if (root.ValueKind == JsonValueKind.Array) { - foreach (var mailQueueMessage in root.ToObject>()) + foreach (var mailQueueMessage in root.Deserialize>()) { await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage); } } else if (root.ValueKind == JsonValueKind.Object) { - var mailQueueMessage = root.ToObject(); + var mailQueueMessage = root.Deserialize(); await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage); } } diff --git a/src/Admin/HostedServices/BlockIpHostedService.cs b/src/Admin/HostedServices/BlockIpHostedService.cs deleted file mode 100644 index 6a1f58c6b3..0000000000 --- a/src/Admin/HostedServices/BlockIpHostedService.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Bit.Core.Settings; -using Microsoft.Extensions.Options; - -namespace Bit.Admin.HostedServices; - -public abstract class BlockIpHostedService : IHostedService, IDisposable -{ - protected readonly ILogger _logger; - protected readonly GlobalSettings _globalSettings; - private readonly AdminSettings _adminSettings; - - private Task _executingTask; - private CancellationTokenSource _cts; - private HttpClient _httpClient = new HttpClient(); - - public BlockIpHostedService( - ILogger logger, - IOptions adminSettings, - GlobalSettings globalSettings) - { - _logger = logger; - _globalSettings = globalSettings; - _adminSettings = adminSettings?.Value; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _executingTask = ExecuteAsync(_cts.Token); - return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask; - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - if (_executingTask == null) - { - return; - } - _cts.Cancel(); - await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)); - cancellationToken.ThrowIfCancellationRequested(); - } - - public virtual void Dispose() - { } - - protected abstract Task ExecuteAsync(CancellationToken cancellationToken); - - protected async Task BlockIpAsync(string message, CancellationToken cancellationToken) - { - var request = new HttpRequestMessage(); - request.Headers.Accept.Clear(); - request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail); - request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey); - request.Method = HttpMethod.Post; - request.RequestUri = new Uri("https://api.cloudflare.com/" + - $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules"); - - request.Content = JsonContent.Create(new - { - mode = "block", - configuration = new - { - target = "ip", - value = message - }, - notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}." - }); - - var response = await _httpClient.SendAsync(request, cancellationToken); - if (!response.IsSuccessStatusCode) - { - return; - } - - var accessRuleResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - if (!accessRuleResponse.Success) - { - return; - } - - // TODO: Send `accessRuleResponse.Result?.Id` message to unblock queue - } - - protected async Task UnblockIpAsync(string message, CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(message)) - { - return; - } - - if (message.Contains(".") || message.Contains(":")) - { - // IP address messages - var request = new HttpRequestMessage(); - request.Headers.Accept.Clear(); - request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail); - request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey); - request.Method = HttpMethod.Get; - request.RequestUri = new Uri("https://api.cloudflare.com/" + - $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules?" + - $"configuration_target=ip&configuration_value={message}"); - - var response = await _httpClient.SendAsync(request, cancellationToken); - if (!response.IsSuccessStatusCode) - { - return; - } - - var listResponse = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); - if (!listResponse.Success) - { - return; - } - - foreach (var rule in listResponse.Result) - { - await DeleteAccessRuleAsync(rule.Id, cancellationToken); - } - } - else - { - // Rule Id messages - await DeleteAccessRuleAsync(message, cancellationToken); - } - } - - protected async Task DeleteAccessRuleAsync(string ruleId, CancellationToken cancellationToken) - { - var request = new HttpRequestMessage(); - request.Headers.Accept.Clear(); - request.Headers.Add("X-Auth-Email", _adminSettings.Cloudflare.AuthEmail); - request.Headers.Add("X-Auth-Key", _adminSettings.Cloudflare.AuthKey); - request.Method = HttpMethod.Delete; - request.RequestUri = new Uri("https://api.cloudflare.com/" + - $"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules/{ruleId}"); - await _httpClient.SendAsync(request, cancellationToken); - } - - public class ListResponse - { - public bool Success { get; set; } - public List Result { get; set; } - } - - public class AccessRuleResponse - { - public bool Success { get; set; } - public AccessRuleResultResponse Result { get; set; } - } - - public class AccessRuleResultResponse - { - public string Id { get; set; } - public string Notes { get; set; } - public ConfigurationResponse Configuration { get; set; } - - public class ConfigurationResponse - { - public string Target { get; set; } - public string Value { get; set; } - } - } -} diff --git a/src/Admin/Models/LogModel.cs b/src/Admin/Models/LogModel.cs deleted file mode 100644 index 8967025d12..0000000000 --- a/src/Admin/Models/LogModel.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.Azure.Documents; -using Newtonsoft.Json.Linq; - -namespace Bit.Admin.Models; - -public class LogModel : Resource -{ - public long EventIdHash { get; set; } - public string Level { get; set; } - public string Message { get; set; } - public string MessageTruncated => Message.Length > 200 ? $"{Message.Substring(0, 200)}..." : Message; - public string MessageTemplate { get; set; } - public IDictionary Properties { get; set; } - public string Project => Properties?.ContainsKey("Project") ?? false ? Properties["Project"].ToString() : null; -} - -public class LogDetailsModel : LogModel -{ - public JObject Exception { get; set; } - - public string ExceptionToString(JObject e) - { - if (e == null) - { - return null; - } - - var val = string.Empty; - if (e["Message"] != null && e["Message"].ToObject() != null) - { - val += "Message:\n"; - val += e["Message"] + "\n"; - } - - if (e["StackTrace"] != null && e["StackTrace"].ToObject() != null) - { - val += "\nStack Trace:\n"; - val += e["StackTrace"]; - } - else if (e["StackTraceString"] != null && e["StackTraceString"].ToObject() != null) - { - val += "\nStack Trace String:\n"; - val += e["StackTraceString"]; - } - - if (e["InnerException"] != null && e["InnerException"].ToObject() != null) - { - val += "\n\n=== Inner Exception ===\n\n"; - val += ExceptionToString(e["InnerException"].ToObject()); - } - - return val; - } -} diff --git a/src/Admin/Models/LogsModel.cs b/src/Admin/Models/LogsModel.cs deleted file mode 100644 index c5527a3191..0000000000 --- a/src/Admin/Models/LogsModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Serilog.Events; - -namespace Bit.Admin.Models; - -public class LogsModel : CursorPagedModel -{ - public LogEventLevel? Level { get; set; } - public string Project { get; set; } - public DateTime? Start { get; set; } - public DateTime? End { get; set; } -} diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index 234d8bb157..559b5b6868 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -70,7 +70,6 @@ public class OrganizationEditModel : OrganizationViewModel MaxAutoscaleSmSeats = org.MaxAutoscaleSmSeats; SmServiceAccounts = org.SmServiceAccounts; MaxAutoscaleSmServiceAccounts = org.MaxAutoscaleSmServiceAccounts; - SecretsManagerBeta = org.SecretsManagerBeta; } public BillingInfo BillingInfo { get; set; } @@ -150,8 +149,6 @@ public class OrganizationEditModel : OrganizationViewModel public int? SmServiceAccounts { get; set; } [Display(Name = "Max Autoscale Service Accounts")] public int? MaxAutoscaleSmServiceAccounts { get; set; } - [Display(Name = "Secrets Manager Beta")] - public bool SecretsManagerBeta { get; set; } /** * Creates a Plan[] object for use in Javascript @@ -210,7 +207,6 @@ public class OrganizationEditModel : OrganizationViewModel existingOrganization.MaxAutoscaleSmSeats = MaxAutoscaleSmSeats; existingOrganization.SmServiceAccounts = SmServiceAccounts; existingOrganization.MaxAutoscaleSmServiceAccounts = MaxAutoscaleSmServiceAccounts; - existingOrganization.SecretsManagerBeta = SecretsManagerBeta; return existingOrganization; } } diff --git a/src/Admin/Startup.cs b/src/Admin/Startup.cs index a10cd4d2de..7b806dce3f 100644 --- a/src/Admin/Startup.cs +++ b/src/Admin/Startup.cs @@ -9,6 +9,7 @@ using Stripe; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection.Extensions; using Bit.Admin.Services; +using Bit.Core.Billing.Extensions; #if !OSS using Bit.Commercial.Core.Utilities; @@ -87,6 +88,7 @@ public class Startup services.AddBaseServices(globalSettings); services.AddDefaultServices(globalSettings); services.AddScoped(); + services.AddBillingCommands(); #if OSS services.AddOosServices(); @@ -116,14 +118,6 @@ public class Startup } else { - if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString)) - { - services.AddHostedService(); - } - else if (CoreHelpers.SettingHasValue(globalSettings.Amazon?.AccessKeySecret)) - { - services.AddHostedService(); - } if (CoreHelpers.SettingHasValue(globalSettings.Mail.ConnectionString)) { services.AddHostedService(); diff --git a/src/Admin/Utilities/RolePermissionMapping.cs b/src/Admin/Utilities/RolePermissionMapping.cs index a9b2d3dfc7..1dee17c05c 100644 --- a/src/Admin/Utilities/RolePermissionMapping.cs +++ b/src/Admin/Utilities/RolePermissionMapping.cs @@ -47,8 +47,7 @@ public static class RolePermissionMapping Permission.Tools_PromoteAdmin, Permission.Tools_GenerateLicenseFile, Permission.Tools_ManageTaxRates, - Permission.Tools_ManageStripeSubscriptions, - Permission.Logs_View + Permission.Tools_ManageStripeSubscriptions } }, { "admin", new List @@ -94,8 +93,7 @@ public static class RolePermissionMapping Permission.Tools_GenerateLicenseFile, Permission.Tools_ManageTaxRates, Permission.Tools_ManageStripeSubscriptions, - Permission.Tools_CreateEditTransaction, - Permission.Logs_View + Permission.Tools_CreateEditTransaction } }, { "cs", new List @@ -123,8 +121,7 @@ public static class RolePermissionMapping Permission.Org_Billing_View, Permission.Org_Billing_LaunchGateway, Permission.Provider_List_View, - Permission.Provider_View, - Permission.Logs_View + Permission.Provider_View } }, { "billing", new List @@ -163,8 +160,7 @@ public static class RolePermissionMapping Permission.Tools_GenerateLicenseFile, Permission.Tools_ManageTaxRates, Permission.Tools_ManageStripeSubscriptions, - Permission.Tools_CreateEditTransaction, - Permission.Logs_View + Permission.Tools_CreateEditTransaction } }, { "sales", new List @@ -193,8 +189,7 @@ public static class RolePermissionMapping Permission.Provider_Create, Permission.Provider_Edit, Permission.Provider_View, - Permission.Provider_ResendEmailInvite, - Permission.Logs_View + Permission.Provider_ResendEmailInvite } }, }; diff --git a/src/Admin/Views/Logs/Index.cshtml b/src/Admin/Views/Logs/Index.cshtml deleted file mode 100644 index 93eeb05e4c..0000000000 --- a/src/Admin/Views/Logs/Index.cshtml +++ /dev/null @@ -1,91 +0,0 @@ -@model LogsModel -@{ - ViewData["Title"] = "Logs"; -} - -

Logs

- -

Current UTC time: @DateTime.UtcNow.ToString()

- -
- - - - - - - -
- -
- - - - - - - - - - - - @if(!Model.Items.Any()) - { - - - - } - else - { - @foreach(var log in Model.Items) - { - - - - - - - - } - } - -
 TimestampProjectLevelMessage
No results to list.
- - - - @log.Timestamp.ToString()@(string.IsNullOrWhiteSpace(log.Project) ? "-" : log.Project)@log.Level@log.MessageTruncated
-
- - diff --git a/src/Admin/Views/Logs/View.cshtml b/src/Admin/Views/Logs/View.cshtml deleted file mode 100644 index 86ca77164d..0000000000 --- a/src/Admin/Views/Logs/View.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@model LogDetailsModel -@{ - ViewData["Title"] = "Log: " + Model.Id; -} - -

Log @Model.Id

- -

Information

-
-
Id
-
@Model.Id
- -
Event Id Hash
-
@Model.EventIdHash
- -
Timestamp
-
@Model.Timestamp.ToString()
- -
Level
-
@Model.Level
-
- -

Message

-
@Model.Message
- -@if(Model.Exception != null) -{ -

Exception

-
@Model.ExceptionToString(Model.Exception)
-} - -@if(Model.Properties != null && Model.Properties.Count > 0) -{ -

Properties

-
- @foreach(var prop in Model.Properties) - { -
@prop.Key
-
@(prop.Value?.ToString() ?? "-")
- } -
-} diff --git a/src/Admin/Views/Organizations/Edit.cshtml b/src/Admin/Views/Organizations/Edit.cshtml index e3f6d50905..ad4e4f8482 100644 --- a/src/Admin/Views/Organizations/Edit.cshtml +++ b/src/Admin/Views/Organizations/Edit.cshtml @@ -8,6 +8,7 @@ var canViewBillingInformation = AccessControlService.UserHasPermission(Permission.Org_BillingInformation_View); var canInitiateTrial = AccessControlService.UserHasPermission(Permission.Org_InitiateTrial); var canDelete = AccessControlService.UserHasPermission(Permission.Org_Delete); + var canUnlinkFromProvider = AccessControlService.UserHasPermission(Permission.Provider_Edit); } @section Scripts { @@ -81,7 +82,7 @@
- @if (canInitiateTrial) + @if (canInitiateTrial && Model.Provider is null) { } + @if (canUnlinkFromProvider && Model.Provider is not null) + { + + } @if (canDelete) {
Provider Organizations
@@ -32,26 +40,28 @@ } else { - @foreach (var org in Model.ProviderOrganizations) + @foreach (var providerOrganization in Model.ProviderOrganizations) { - @org.OrganizationName + @providerOrganization.OrganizationName - @org.Status + @providerOrganization.Status
- @if (org.Status == OrganizationStatusType.Pending) + @if (canUnlinkFromProvider) { - - + + Unlink provider } - else + @if (providerOrganization.Status == OrganizationStatusType.Pending) { - + + Resend invitation + }
diff --git a/src/Admin/Views/Providers/_ProviderOrganizationScripts.cshtml b/src/Admin/Views/Providers/_ProviderOrganizationScripts.cshtml new file mode 100644 index 0000000000..b8fefb4c14 --- /dev/null +++ b/src/Admin/Views/Providers/_ProviderOrganizationScripts.cshtml @@ -0,0 +1,21 @@ + diff --git a/src/Admin/Views/Shared/_Layout.cshtml b/src/Admin/Views/Shared/_Layout.cshtml index 55a380da07..37b893e643 100644 --- a/src/Admin/Views/Shared/_Layout.cshtml +++ b/src/Admin/Views/Shared/_Layout.cshtml @@ -1,4 +1,4 @@ -@using Bit.Admin.Enums; +@using Bit.Admin.Enums; @inject SignInManager SignInManager @inject Bit.Core.Settings.GlobalSettings GlobalSettings @@ -8,7 +8,6 @@ var canViewUsers = AccessControlService.UserHasPermission(Permission.User_List_View); var canViewOrgs = AccessControlService.UserHasPermission(Permission.Org_List_View); var canViewProviders = AccessControlService.UserHasPermission(Permission.Provider_List_View); - var canViewLogs = AccessControlService.UserHasPermission(Permission.Logs_View); var canChargeBraintree = AccessControlService.UserHasPermission(Permission.Tools_ChargeBrainTreeCustomer); var canCreateTransaction = AccessControlService.UserHasPermission(Permission.Tools_CreateEditTransaction); var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin); @@ -121,12 +120,6 @@
} - @if (canViewLogs) - { - - } } } @if (GlobalSettings.SelfHosted) diff --git a/src/Admin/Views/Shared/_OrganizationForm.cshtml b/src/Admin/Views/Shared/_OrganizationForm.cshtml index 72076cbd37..1438a5aa8c 100644 --- a/src/Admin/Views/Shared/_OrganizationForm.cshtml +++ b/src/Admin/Views/Shared/_OrganizationForm.cshtml @@ -174,10 +174,6 @@
-
- - -
} @@ -217,7 +213,7 @@ @if (canViewPlan) { -