1
0
mirror of https://github.com/bitwarden/server synced 2025-12-18 09:13:19 +00:00

Merge remote-tracking branch 'origin/master' into feature/sm-billing

This commit is contained in:
Thomas Rittson
2023-07-04 14:44:02 +10:00
87 changed files with 1259 additions and 665 deletions

View File

@@ -14,7 +14,7 @@ jobs:
# Feature request # Feature request
- if: github.event.label.name == 'feature-request' - if: github.event.label.name == 'feature-request'
name: Feature request name: Feature request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0 uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0
with: with:
comment: | comment: |
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one. We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
@@ -25,7 +25,7 @@ jobs:
# Intended behavior # Intended behavior
- if: github.event.label.name == 'intended-behavior' - if: github.event.label.name == 'intended-behavior'
name: Intended behaviour name: Intended behaviour
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0 uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0
with: with:
comment: | comment: |
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request. Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
@@ -38,7 +38,7 @@ jobs:
# Customer support request # Customer support request
- if: github.event.label.name == 'customer-support' - if: github.event.label.name == 'customer-support'
name: Customer Support request name: Customer Support request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0 uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0
with: with:
comment: | comment: |
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team. We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
@@ -49,14 +49,14 @@ jobs:
# Resolved # Resolved
- if: github.event.label.name == 'resolved' - if: github.event.label.name == 'resolved'
name: Resolved name: Resolved
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0 uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0
with: with:
comment: | comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis. Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale # Stale
- if: github.event.label.name == 'stale' - if: github.event.label.name == 'stale'
name: Stale name: Stale
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0 uses: peter-evans/close-issue@1373cadf1f0c96c1420bc000cfba2273ea307fd1 # v2.2.0
with: with:
comment: | comment: |
As we havent heard from you about this problem in some time, this issue will now be closed. As we havent heard from you about this problem in some time, this issue will now be closed.

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Install cloc - name: Install cloc
run: | run: |
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Verify Format - name: Verify Format
run: dotnet format --verify-no-changes run: dotnet format --verify-no-changes
@@ -43,7 +43,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829 # v2.0.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: "6.0.x" dotnet-version: "6.0.x"
@@ -55,7 +55,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Restore - name: Restore
run: dotnet restore --locked-mode run: dotnet restore --locked-mode
@@ -131,10 +131,10 @@ jobs:
dotnet: true dotnet: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up Node - name: Set up Node
uses: actions/setup-node@9ced9a43a244f3ac94f13bfd896db8c8f30da67a # v3.0.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with: with:
cache: "npm" cache: "npm"
cache-dependency-path: "**/package-lock.json" cache-dependency-path: "**/package-lock.json"
@@ -178,7 +178,7 @@ jobs:
ls -atlh ../../../ ls -atlh ../../../
- name: Upload project artifact - name: Upload project artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: ${{ matrix.project_name }}.zip name: ${{ matrix.project_name }}.zip
path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip
@@ -255,7 +255,7 @@ jobs:
dotnet: true dotnet: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Check Branch to Publish - name: Check Branch to Publish
env: env:
@@ -272,7 +272,7 @@ jobs:
########## ACRs ########## ########## ACRs ##########
- name: Login to Azure - QA Subscription - name: Login to Azure - QA Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
@@ -280,7 +280,7 @@ jobs:
run: az acr login -n bitwardenqa run: az acr login -n bitwardenqa
- name: Login to Azure - PROD Subscription - name: Login to Azure - PROD Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
@@ -288,13 +288,13 @@ jobs:
run: az acr login -n bitwardenprod run: az acr login -n bitwardenprod
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve github PAT secrets - name: Retrieve github PAT secrets
id: retrieve-secret-pat id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/get-keyvault-secrets@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope" secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@@ -302,7 +302,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
if: ${{ env.is_publish_branch == 'true' }} if: ${{ env.is_publish_branch == 'true' }}
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/get-keyvault-secrets@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "docker-password, secrets: "docker-password,
@@ -319,7 +319,7 @@ jobs:
- name: Setup Docker Content Trust (DCT) - name: Setup Docker Content Trust (DCT)
if: ${{ env.is_publish_branch == 'true' }} if: ${{ env.is_publish_branch == 'true' }}
uses: bitwarden/gh-actions/setup-docker-trust@f955298c7a982b3fb5dbb73afd582c584fd5beec uses: bitwarden/gh-actions/setup-docker-trust@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
azure-keyvault-name: "bitwarden-ci" azure-keyvault-name: "bitwarden-ci"
@@ -351,7 +351,7 @@ jobs:
- name: Get build artifact - name: Get build artifact
if: ${{ matrix.dotnet }} if: ${{ matrix.dotnet }}
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3.0.0 uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with: with:
name: ${{ matrix.project_name }}.zip name: ${{ matrix.project_name }}.zip
@@ -363,7 +363,7 @@ jobs:
-d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish -d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish
- name: Build Docker image - name: Build Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 # v3.2.0 uses: docker/build-push-action@1104d471370f9806843c095c1db02b5a90c5f8b6 # v3.3.1
with: with:
context: ${{ matrix.base_path }}/${{ matrix.project_name }} context: ${{ matrix.base_path }}/${{ matrix.project_name }}
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
@@ -393,12 +393,12 @@ jobs:
needs: build-docker needs: build-docker
steps: steps:
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829 # v3.0.3 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: "6.0.x" dotnet-version: "6.0.x"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Restore - name: Restore
run: dotnet tool restore run: dotnet tool restore
@@ -431,7 +431,7 @@ jobs:
- name: Upload Docker stub artifact - name: Upload Docker stub artifact
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: docker-stub.zip name: docker-stub.zip
path: docker-stub.zip path: docker-stub.zip
@@ -439,7 +439,7 @@ jobs:
- name: Upload Docker stub checksum artifact - name: Upload Docker stub checksum artifact
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: docker-stub-sha256.txt name: docker-stub-sha256.txt
path: docker-stub-sha256.txt path: docker-stub-sha256.txt
@@ -465,7 +465,7 @@ jobs:
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder" GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
- name: Upload Swagger artifact - name: Upload Swagger artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: swagger.json name: swagger.json
path: swagger.json path: swagger.json
@@ -490,7 +490,7 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Print environment - name: Print environment
run: | run: |
@@ -509,7 +509,7 @@ jobs:
- name: Upload project artifact Windows - name: Upload project artifact Windows
if: ${{ contains(matrix.target, 'win') == true }} if: ${{ contains(matrix.target, 'win') == true }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: MsSqlMigratorUtility-${{ matrix.target }} name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe
@@ -517,7 +517,7 @@ jobs:
- name: Upload project artifact - name: Upload project artifact
if: ${{ contains(matrix.target, 'win') == false }} if: ${{ contains(matrix.target, 'win') == false }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: MsSqlMigratorUtility-${{ matrix.target }} name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility
@@ -579,14 +579,14 @@ jobs:
fi fi
- name: Login to Azure - CI subscription - name: Login to Azure - CI subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/get-keyvault-secrets@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
########## ACR ########## ########## ACR ##########
- name: Login to Azure - QA Subscription - name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a # v1.3.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
@@ -23,7 +23,7 @@ jobs:
run: az acr login -n bitwardenqa run: az acr login -n bitwardenqa
- name: Login to Azure - PROD Subscription - name: Login to Azure - PROD Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a # v1.3.0 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Login to Azure - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
@@ -85,14 +85,14 @@ jobs:
fi fi
- name: Login to Azure - CI subscription - name: Login to Azure - CI subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/get-keyvault-secrets@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # v3.5.2 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829 # v3.0.3 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '6.0.x' dotnet-version: '6.0.x'
@@ -44,7 +44,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload DACPAC - name: Upload DACPAC
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.1.2 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: sql.dacpac name: sql.dacpac
path: Sql.dacpac path: Sql.dacpac
@@ -70,7 +70,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload Report - name: Upload Report
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.1.2 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: report.xml name: report.xml
path: | path: |

View File

@@ -38,10 +38,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with: with:
dotnet-version: '6.0.x' dotnet-version: '6.0.x'

View File

@@ -30,7 +30,7 @@ jobs:
label: "DB-migrations-changed" label: "DB-migrations-changed"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with: with:
fetch-depth: 2 fetch-depth: 2

View File

@@ -34,11 +34,11 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Check Release Version - name: Check Release Version
id: version id: version
uses: bitwarden/gh-actions/release-version-check@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/release-version-check@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
release-type: ${{ github.event.inputs.release_type }} release-type: ${{ github.event.inputs.release_type }}
project-type: dotnet project-type: dotnet
@@ -87,7 +87,7 @@ jobs:
- name: Download latest Release ${{ matrix.name }} asset - name: Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/download-artifacts@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -96,7 +96,7 @@ jobs:
- name: Download latest Release ${{ matrix.name }} asset - name: Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/download-artifacts@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -104,7 +104,7 @@ jobs:
artifacts: ${{ matrix.name }}.zip artifacts: ${{ matrix.name }}.zip
- name: Login to Azure - CI subscription - name: Login to Azure - CI subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -129,12 +129,12 @@ jobs:
echo "publish-profile=$publish_profile" >> $GITHUB_OUTPUT echo "publish-profile=$publish_profile" >> $GITHUB_OUTPUT
- name: Login to Azure - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Deploy App - name: Deploy App
uses: azure/webapps-deploy@016bdd3f9b7cec60310bcf9da98f671628795644 # v2.2.4 uses: azure/webapps-deploy@fb8292eb575db1bb18a90627e8959cd51dbb355c # v2.2.10
with: with:
app-name: ${{ steps.retrieve-secrets.outputs.webapp-name }} app-name: ${{ steps.retrieve-secrets.outputs.webapp-name }}
publish-profile: ${{ steps.retrieve-secrets.outputs.publish-profile }} publish-profile: ${{ steps.retrieve-secrets.outputs.publish-profile }}
@@ -229,7 +229,7 @@ jobs:
echo "Github Release Option: $RELEASE_OPTION" echo "Github Release Option: $RELEASE_OPTION"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Setup project name - name: Setup project name
id: setup id: setup
@@ -243,7 +243,7 @@ jobs:
- name: Setup DCT - name: Setup DCT
id: setup-dct id: setup-dct
if: matrix.origin_docker_repo == 'bitwarden' if: matrix.origin_docker_repo == 'bitwarden'
uses: bitwarden/gh-actions/setup-docker-trust@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/setup-docker-trust@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
azure-keyvault-name: "bitwarden-ci" azure-keyvault-name: "bitwarden-ci"
@@ -286,7 +286,7 @@ jobs:
########## ACR PROD ########## ########## ACR PROD ##########
- name: Login to Azure - PROD Subscription - name: Login to Azure - PROD Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
@@ -340,7 +340,7 @@ jobs:
steps: steps:
- name: Download latest Release docker-stub - name: Download latest Release docker-stub
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/download-artifacts@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -351,7 +351,7 @@ jobs:
- name: Download latest Release docker-stub - name: Download latest Release docker-stub
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/download-artifacts@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: 'Run stale action' - name: 'Run stale action'
uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 # v5.0.0 uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
with: with:
stale-issue-label: 'needs-reply' stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes' stale-pr-label: 'needs-changes'

View File

@@ -29,7 +29,7 @@ jobs:
echo "name_lower=$NAME_LOWER" >> $GITHUB_OUTPUT echo "name_lower=$NAME_LOWER" >> $GITHUB_OUTPUT
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -47,7 +47,7 @@ jobs:
echo "webapp-name=$webapp_name" >> $GITHUB_OUTPUT echo "webapp-name=$webapp_name" >> $GITHUB_OUTPUT
- name: Login to Azure - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}

View File

@@ -14,22 +14,22 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/get-keyvault-secrets@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Import GPG key - name: Import GPG key
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0 uses: crazy-max/ghaction-import-gpg@72b6676b71ab476b77e676928516f6982eef7a41 # v5.3.0
with: with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }} gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }} passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
@@ -40,7 +40,7 @@ jobs:
run: git switch -c version_bump_${{ github.event.inputs.version_number }} run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Props - name: Bump Version - Props
uses: bitwarden/gh-actions/version-bump@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/version-bump@74f4ac01c9abe0a7331c9a5de822a558fd4a4710
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "Directory.Build.props" file_path: "Directory.Build.props"

View File

@@ -8,4 +8,4 @@ on:
jobs: jobs:
call-workflow: call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@74f4ac01c9abe0a7331c9a5de822a558fd4a4710

View File

@@ -52,7 +52,7 @@ public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperation
{ {
AccessClientType.NoAccessCheck => true, AccessClientType.NoAccessCheck => true,
AccessClientType.User => true, AccessClientType.User => true,
AccessClientType.ServiceAccount => false, AccessClientType.ServiceAccount => true,
_ => false, _ => false,
}; };
@@ -67,10 +67,6 @@ public class ProjectAuthorizationHandler : AuthorizationHandler<ProjectOperation
{ {
var (accessClient, userId) = var (accessClient, userId) =
await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId); await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
if (accessClient == AccessClientType.ServiceAccount)
{
return;
}
var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient); var access = await _projectRepository.AccessToProjectAsync(resource.Id, userId, accessClient);

View File

@@ -74,7 +74,8 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
AccessClientType.NoAccessCheck => true, AccessClientType.NoAccessCheck => true,
AccessClientType.User => (await _projectRepository.AccessToProjectAsync(project!.Id, userId, accessClient)) AccessClientType.User => (await _projectRepository.AccessToProjectAsync(project!.Id, userId, accessClient))
.Write, .Write,
AccessClientType.ServiceAccount => false, AccessClientType.ServiceAccount => (await _projectRepository.AccessToProjectAsync(project!.Id, userId, accessClient))
.Write,
_ => false, _ => false,
}; };
@@ -84,6 +85,7 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
} }
} }
private async Task CanUpdateSecretAsync(AuthorizationHandlerContext context, private async Task CanUpdateSecretAsync(AuthorizationHandlerContext context,
SecretOperationRequirement requirement, Secret resource) SecretOperationRequirement requirement, Secret resource)
{ {
@@ -106,12 +108,10 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
hasAccess = true; hasAccess = true;
break; break;
case AccessClientType.User: case AccessClientType.User:
var newProject = resource.Projects?.FirstOrDefault(); hasAccess = await GetAccessToUpdateSecretAsync(resource, userId, accessClient);
var access = (await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient)).Write; break;
var accessToNew = newProject != null && case AccessClientType.ServiceAccount:
(await _projectRepository.AccessToProjectAsync(newProject.Id, userId, accessClient)) hasAccess = await GetAccessToUpdateSecretAsync(resource, userId, accessClient);
.Write;
hasAccess = access && accessToNew;
break; break;
default: default:
hasAccess = false; hasAccess = false;
@@ -129,11 +129,6 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
{ {
var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId); var (accessClient, userId) = await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId);
if (accessClient == AccessClientType.ServiceAccount)
{
return;
}
var access = await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient); var access = await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient);
if (access.Write) if (access.Write)
@@ -141,4 +136,14 @@ public class SecretAuthorizationHandler : AuthorizationHandler<SecretOperationRe
context.Succeed(requirement); context.Succeed(requirement);
} }
} }
private async Task<bool> GetAccessToUpdateSecretAsync(Secret resource, Guid userId, AccessClientType accessClient)
{
var newProject = resource.Projects?.FirstOrDefault();
var access = (await _secretRepository.AccessToSecretAsync(resource.Id, userId, accessClient)).Write;
var accessToNew = newProject != null &&
(await _projectRepository.AccessToProjectAsync(newProject.Id, userId, accessClient))
.Write;
return access && accessToNew;
}
} }

View File

@@ -1,4 +1,7 @@
using Bit.Core.Repositories; using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@@ -10,31 +13,58 @@ public class CreateProjectCommand : ICreateProjectCommand
private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly ICurrentContext _currentContext;
public CreateProjectCommand( public CreateProjectCommand(
IAccessPolicyRepository accessPolicyRepository, IAccessPolicyRepository accessPolicyRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IProjectRepository projectRepository) IProjectRepository projectRepository,
ICurrentContext currentContext)
{ {
_accessPolicyRepository = accessPolicyRepository; _accessPolicyRepository = accessPolicyRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_projectRepository = projectRepository; _projectRepository = projectRepository;
_currentContext = currentContext;
} }
public async Task<Project> CreateAsync(Project project, Guid userId) public async Task<Project> CreateAsync(Project project, Guid id, ClientType clientType)
{ {
if (clientType != ClientType.User && clientType != ClientType.ServiceAccount)
{
throw new NotFoundException();
}
var createdProject = await _projectRepository.CreateAsync(project); var createdProject = await _projectRepository.CreateAsync(project);
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(createdProject.OrganizationId, if (clientType == ClientType.User)
userId);
var accessPolicy = new UserProjectAccessPolicy()
{ {
OrganizationUserId = orgUser.Id, var orgUser = await _organizationUserRepository.GetByOrganizationAsync(createdProject.OrganizationId, id);
GrantedProjectId = createdProject.Id,
Read = true, var accessPolicy = new UserProjectAccessPolicy()
Write = true, {
}; OrganizationUserId = orgUser.Id,
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy }); GrantedProjectId = createdProject.Id,
Read = true,
Write = true,
};
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy });
}
else if (clientType == ClientType.ServiceAccount)
{
var serviceAccountProjectAccessPolicy = new ServiceAccountProjectAccessPolicy()
{
ServiceAccountId = id,
GrantedProjectId = createdProject.Id,
Read = true,
Write = true,
};
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { serviceAccountProjectAccessPolicy });
}
return createdProject; return createdProject;
} }
} }

View File

@@ -55,7 +55,7 @@ public class DeleteProjectCommand : IDeleteProjectCommand
foreach (var project in projects) foreach (var project in projects)
{ {
var access = await _projectRepository.AccessToProjectAsync(project.Id, userId, accessClient); var access = await _projectRepository.AccessToProjectAsync(project.Id, userId, accessClient);
if (!access.Write || accessClient == AccessClientType.ServiceAccount) if (!access.Write)
{ {
results.Add(new Tuple<Project, string>(project, "access denied")); results.Add(new Tuple<Project, string>(project, "access denied"));
} }

View File

@@ -2783,7 +2783,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2794,7 +2794,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -7,6 +7,9 @@
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso' " /> <PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso' " />
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso-SelfHost' " /> <PropertyGroup Condition=" '$(RunConfiguration)' == 'Sso-SelfHost' " />
<ItemGroup> <ItemGroup>
<!-- This is a transitive dependency to Sustainsys.Saml2.AspNetCore2 -->
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.22" />
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.9.0" /> <PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.9.0" />
</ItemGroup> </ItemGroup>

View File

@@ -17,7 +17,7 @@
"jquery": "3.5.1", "jquery": "3.5.1",
"merge-stream": "2.0.0", "merge-stream": "2.0.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"sass": "^1.49.7" "sass": "1.49.7"
} }
}, },
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
@@ -3924,9 +3924,9 @@
} }
}, },
"node_modules/sass/node_modules/anymatch": { "node_modules/sass/node_modules/anymatch": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
@@ -8158,9 +8158,9 @@
}, },
"dependencies": { "dependencies": {
"anymatch": { "anymatch": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "dev": true,
"requires": { "requires": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",

View File

@@ -16,6 +16,6 @@
"jquery": "3.5.1", "jquery": "3.5.1",
"merge-stream": "2.0.0", "merge-stream": "2.0.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"sass": "^1.49.7" "sass": "1.49.7"
} }
} }

View File

@@ -2,6 +2,19 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net6.0": { "net6.0": {
"Microsoft.AspNetCore.Http": {
"type": "Direct",
"requested": "[2.1.22, )",
"resolved": "2.1.22",
"contentHash": "+Blk++1JWqghbl8+3azQmKhiNZA5wAepL9dY2I6KVmu2Ri07MAcvAVC888qUvO7yd7xgRgZOMfihezKg14O/2A==",
"dependencies": {
"Microsoft.AspNetCore.Http.Abstractions": "2.1.1",
"Microsoft.AspNetCore.WebUtilities": "2.1.1",
"Microsoft.Extensions.ObjectPool": "2.1.1",
"Microsoft.Extensions.Options": "2.1.1",
"Microsoft.Net.Http.Headers": "2.1.1"
}
},
"Sustainsys.Saml2.AspNetCore2": { "Sustainsys.Saml2.AspNetCore2": {
"type": "Direct", "type": "Direct",
"requested": "[2.9.0, )", "requested": "[2.9.0, )",
@@ -418,18 +431,6 @@
"resolved": "3.1.32", "resolved": "3.1.32",
"contentHash": "MPL4iVyiaRxnOUY5VATHjvhDWaAEFb77KFiUxVRklv3Z3v+STofUr1UG/aCt1O9cgN7FVTDaC5A7U+zsLub8Xg==" "contentHash": "MPL4iVyiaRxnOUY5VATHjvhDWaAEFb77KFiUxVRklv3Z3v+STofUr1UG/aCt1O9cgN7FVTDaC5A7U+zsLub8Xg=="
}, },
"Microsoft.AspNetCore.Http": {
"type": "Transitive",
"resolved": "2.1.1",
"contentHash": "pPDcCW8spnyibK3krpxrOpaFHf5fjV6k1Hsl6gfh77N/8gRYlLU7MOQDUnjpEwdlHmtxwJKQJNxZqVQOmJGRUw==",
"dependencies": {
"Microsoft.AspNetCore.Http.Abstractions": "2.1.1",
"Microsoft.AspNetCore.WebUtilities": "2.1.1",
"Microsoft.Extensions.ObjectPool": "2.1.1",
"Microsoft.Extensions.Options": "2.1.1",
"Microsoft.Net.Http.Headers": "2.1.1"
}
},
"Microsoft.AspNetCore.Http.Abstractions": { "Microsoft.AspNetCore.Http.Abstractions": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.1.1", "resolved": "2.1.1",
@@ -2945,7 +2946,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2953,7 +2954,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2965,9 +2966,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -39,6 +39,11 @@ public class ProjectAuthorizationHandlerTests
.ReturnsForAnyArgs( .ReturnsForAnyArgs(
(AccessClientType.User, userId)); (AccessClientType.User, userId));
break; break;
case PermissionType.RunAsServiceAccountWithPermission:
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId)
.ReturnsForAnyArgs(
(AccessClientType.ServiceAccount, userId));
break;
default: default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
} }
@@ -103,7 +108,6 @@ public class ProjectAuthorizationHandlerTests
} }
[Theory] [Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)] [BitAutoData(AccessClientType.Organization)]
public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType, public async Task CanCreateProject_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal) SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
@@ -125,6 +129,7 @@ public class ProjectAuthorizationHandlerTests
[Theory] [Theory]
[BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)] [BitAutoData(PermissionType.RunAsUserWithPermission)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission)]
public async Task CanCreateProject_Success(PermissionType permissionType, public async Task CanCreateProject_Success(PermissionType permissionType,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal) SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal)
{ {
@@ -199,6 +204,8 @@ public class ProjectAuthorizationHandlerTests
[Theory] [Theory]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false)]
public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write, public async Task CanUpdateProject_ShouldNotSucceed(PermissionType permissionType, bool read, bool write,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal, SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
Guid userId) Guid userId)
@@ -221,6 +228,8 @@ public class ProjectAuthorizationHandlerTests
[BitAutoData(PermissionType.RunAsAdmin, false, true)] [BitAutoData(PermissionType.RunAsAdmin, false, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true)]
public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write, public async Task CanUpdateProject_Success(PermissionType permissionType, bool read, bool write,
SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal, SutProvider<ProjectAuthorizationHandler> sutProvider, Project project, ClaimsPrincipal claimsPrincipal,
Guid userId) Guid userId)

View File

@@ -41,6 +41,10 @@ public class SecretAuthorizationHandlerTests
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs( sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs(
(clientType, userId)); (clientType, userId));
break; break;
case PermissionType.RunAsServiceAccountWithPermission:
sutProvider.GetDependency<IAccessClientQuery>().GetAccessClientAsync(default, organizationId).ReturnsForAnyArgs(
(AccessClientType.ServiceAccount, userId));
break;
default: default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
} }
@@ -105,7 +109,6 @@ public class SecretAuthorizationHandlerTests
} }
[Theory] [Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)] [BitAutoData(AccessClientType.Organization)]
public async Task CanCreateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType, public async Task CanCreateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId,
@@ -114,7 +117,7 @@ public class SecretAuthorizationHandlerTests
var requirement = SecretOperations.Create; var requirement = SecretOperations.Create;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType); SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType);
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).Returns(
(true, true)); (true, true));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -182,6 +185,8 @@ public class SecretAuthorizationHandlerTests
[Theory] [Theory]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false)]
public async Task CanCreateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write, public async Task CanCreateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId, Guid userId,
@@ -190,7 +195,7 @@ public class SecretAuthorizationHandlerTests
var requirement = SecretOperations.Create; var requirement = SecretOperations.Create;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId); SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).ReturnsForAnyArgs(
(read, write)); (read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -207,6 +212,8 @@ public class SecretAuthorizationHandlerTests
[BitAutoData(PermissionType.RunAsAdmin, false, false)] [BitAutoData(PermissionType.RunAsAdmin, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true)]
public async Task CanCreateSecret_Success(PermissionType permissionType, bool read, bool write, public async Task CanCreateSecret_Success(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId, Guid userId,
@@ -215,7 +222,7 @@ public class SecretAuthorizationHandlerTests
var requirement = SecretOperations.Create; var requirement = SecretOperations.Create;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId); SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).ReturnsForAnyArgs(
(read, write)); (read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -243,7 +250,6 @@ public class SecretAuthorizationHandlerTests
} }
[Theory] [Theory]
[BitAutoData(AccessClientType.ServiceAccount)]
[BitAutoData(AccessClientType.Organization)] [BitAutoData(AccessClientType.Organization)]
public async Task CanUpdateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType, public async Task CanUpdateSecret_NotSupportedClientTypes_DoesNotSucceed(AccessClientType clientType,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId,
@@ -252,7 +258,7 @@ public class SecretAuthorizationHandlerTests
var requirement = SecretOperations.Update; var requirement = SecretOperations.Update;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType); SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId, clientType);
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).Returns(
(true, true)); (true, true));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -327,6 +333,15 @@ public class SecretAuthorizationHandlerTests
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false, false)]
public async Task CanUpdateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write, public async Task CanUpdateSecret_DoesNotSucceed(PermissionType permissionType, bool read, bool write,
bool projectRead, bool projectWrite, bool projectRead, bool projectWrite,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
@@ -335,10 +350,10 @@ public class SecretAuthorizationHandlerTests
{ {
var requirement = SecretOperations.Update; var requirement = SecretOperations.Update;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId); SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, default).Returns( sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, Arg.Any<AccessClientType>()).Returns(
(read, write)); (read, write));
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).Returns(
(projectRead, projectWrite)); (projectRead, projectWrite));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -355,6 +370,8 @@ public class SecretAuthorizationHandlerTests
[BitAutoData(PermissionType.RunAsAdmin, false, false)] [BitAutoData(PermissionType.RunAsAdmin, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true)]
public async Task CanUpdateSecret_Success(PermissionType permissionType, bool read, bool write, public async Task CanUpdateSecret_Success(PermissionType permissionType, bool read, bool write,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,
Guid userId, Guid userId,
@@ -362,10 +379,10 @@ public class SecretAuthorizationHandlerTests
{ {
var requirement = SecretOperations.Update; var requirement = SecretOperations.Update;
SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId); SetupPermission(sutProvider, permissionType, secret.OrganizationId, userId);
sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, default).Returns( sutProvider.GetDependency<ISecretRepository>().AccessToSecretAsync(secret.Id, userId, Arg.Any<AccessClientType>()).Returns(
(read, write)); (read, write));
sutProvider.GetDependency<IProjectRepository>() sutProvider.GetDependency<IProjectRepository>()
.AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, default).Returns( .AccessToProjectAsync(secret.Projects!.FirstOrDefault()!.Id, userId, Arg.Any<AccessClientType>()).Returns(
(read, write)); (read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement }, var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret); claimsPrincipal, secret);
@@ -409,32 +426,16 @@ public class SecretAuthorizationHandlerTests
Assert.False(authzContext.HasSucceeded); Assert.False(authzContext.HasSucceeded);
} }
[Theory]
[BitAutoData]
public async Task CanDeleteSecret_ServiceAccountClient_DoesNotSucceed(
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, Guid userId,
ClaimsPrincipal claimsPrincipal)
{
var requirement = SecretOperations.Delete;
SetupPermission(sutProvider, PermissionType.RunAsUserWithPermission, secret.OrganizationId, userId,
AccessClientType.ServiceAccount);
sutProvider.GetDependency<ISecretRepository>()
.AccessToSecretAsync(secret.Id, userId, Arg.Any<AccessClientType>())
.Returns((true, true));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, secret);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory] [Theory]
[BitAutoData(PermissionType.RunAsAdmin, true, true, true)] [BitAutoData(PermissionType.RunAsAdmin, true, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)] [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, false, true, true)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, false, false)]
[BitAutoData(PermissionType.RunAsServiceAccountWithPermission, true, true, true)]
public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write, public async Task CanDeleteProject_AccessCheck(PermissionType permissionType, bool read, bool write,
bool expected, bool expected,
SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret, SutProvider<SecretAuthorizationHandler> sutProvider, Secret secret,

View File

@@ -1,4 +1,5 @@
using Bit.Commercial.Core.SecretsManager.Commands.Projects; using Bit.Commercial.Core.SecretsManager.Commands.Projects;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@@ -29,7 +30,7 @@ public class CreateProjectCommandTests
.CreateAsync(Arg.Any<Project>()) .CreateAsync(Arg.Any<Project>())
.Returns(data); .Returns(data);
await sutProvider.Sut.CreateAsync(data, userId); await sutProvider.Sut.CreateAsync(data, userId, sutProvider.GetDependency<ICurrentContext>().ClientType);
await sutProvider.GetDependency<IProjectRepository>().Received(1) await sutProvider.GetDependency<IProjectRepository>().Received(1)
.CreateAsync(Arg.Is(data)); .CreateAsync(Arg.Is(data));

View File

@@ -4,4 +4,5 @@ public enum PermissionType
{ {
RunAsAdmin, RunAsAdmin,
RunAsUserWithPermission, RunAsUserWithPermission,
RunAsServiceAccountWithPermission
} }

View File

@@ -2818,7 +2818,7 @@
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3" "Core": "2023.5.1"
} }
}, },
"common": { "common": {
@@ -2826,7 +2826,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -2880,8 +2880,8 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Common": "2023.4.3", "Common": "2023.5.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"Moq": "4.17.2", "Moq": "4.17.2",

View File

@@ -3144,7 +3144,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3196,15 +3196,15 @@
"identity": { "identity": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0" "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3212,7 +3212,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3224,8 +3224,8 @@
"integrationtestcommon": { "integrationtestcommon": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Common": "2023.4.3", "Common": "2023.5.1",
"Identity": "2023.4.3", "Identity": "2023.5.1",
"Microsoft.AspNetCore.Mvc.Testing": "6.0.5", "Microsoft.AspNetCore.Mvc.Testing": "6.0.5",
"Microsoft.EntityFrameworkCore.InMemory": "6.0.5", "Microsoft.EntityFrameworkCore.InMemory": "6.0.5",
"Microsoft.Extensions.Configuration": "6.0.1" "Microsoft.Extensions.Configuration": "6.0.1"
@@ -3234,16 +3234,16 @@
"scim": { "scim": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3" "SharedWeb": "2023.5.1"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2989,7 +2989,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3041,7 +3041,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3049,7 +3049,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3061,16 +3061,16 @@
"scim": { "scim": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3" "SharedWeb": "2023.5.1"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Script for generating and installing the Bitwarden development certificates on Linux.
IDENTITY_SERVER_KEY=identity_server_dev.key
IDENTITY_SERVER_CERT=identity_server_dev.crt
IDENTITY_SERVER_CN="Bitwarden Identity Server Dev"
DATA_PROTECTION_KEY=data_protection_dev.key
DATA_PROTECTION_CERT=data_protection_dev.crt
DATA_PROTECTION_CN="Bitwarden Data Protection Dev"
# Detect management command to trust generated certificates.
if [ -x "$(command -v update-ca-certificates)" ]; then
# Debian based
CA_CERT_DIR=/usr/local/share/ca-certificates/
UPDATE_CA_CMD=update-ca-certificates
elif [ -x "$(command -v update-ca-trust)" ]; then
# Redhat based
CA_CERT_DIR=/etc/pki/ca-trust/source/anchors/
UPDATE_CA_CMD=update-ca-trust
else
echo 'Error: Update manager for CA certificates not found!'
exit 1
fi
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 3650 \
-keyout $IDENTITY_SERVER_KEY \
-out $IDENTITY_SERVER_CERT \
-subj "/CN=$IDENTITY_SERVER_CN"
sudo cp $IDENTITY_SERVER_CERT $CA_CERT_DIR
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 3650 \
-keyout $DATA_PROTECTION_KEY \
-out $DATA_PROTECTION_CERT \
-subj "/CN=$DATA_PROTECTION_CN"
sudo cp $DATA_PROTECTION_CERT $CA_CERT_DIR
sudo $UPDATE_CA_CMD
identity=($(openssl x509 -in $IDENTITY_SERVER_CERT -outform der | sha1sum | tr a-z A-Z))
data=($(openssl x509 -in $DATA_PROTECTION_CERT -outform der | sha1sum | tr a-z A-Z))
echo "Certificate fingerprints:"
echo "Identity Server Dev: ${identity}"
echo "Data Protection Dev: ${data}"

View File

@@ -15,7 +15,7 @@
"connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True" "connectionString": "Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True"
}, },
"postgreSql": { "postgreSql": {
"connectionString": "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev;Include Error Detail=true", "connectionString": "Host=localhost;Username=postgres;Password=SET_A_PASSWORD_HERE_123;Database=vault_dev;Include Error Detail=true"
}, },
"mySql": { "mySql": {
"connectionString": "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev" "connectionString": "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev"

View File

@@ -15,15 +15,35 @@ export const options = {
name: "Config", name: "Config",
}, },
}, },
stages: [ scenarios: {
{ duration: "30s", target: 10 }, constant_load: {
{ duration: "1m", target: 20 }, executor: "constant-arrival-rate",
{ duration: "2m", target: 25 }, rate: 1,
{ duration: "30s", target: 0 }, timeUnit: "1s", // 1 request / second
], duration: "10m",
preAllocatedVUs: 5,
},
ramping_load: {
executor: "ramping-arrival-rate",
startRate: 60,
timeUnit: "1m", // 1 request / second to start
stages: [
{ duration: "30s", target: 60 },
{ duration: "2m", target: 150 },
{ duration: "1m", target: 90 },
{ duration: "2m", target: 200 },
{ duration: "2m", target: 120 },
{ duration: "1m", target: 180 },
{ duration: "30s", target: 250 },
{ duration: "30s", target: 90 },
{ duration: "30s", target: 0 },
],
preAllocatedVUs: 40,
},
},
thresholds: { thresholds: {
http_req_failed: ["rate<0.01"], http_req_failed: ["rate<0.01"],
http_req_duration: ["p(95)<1000"], http_req_duration: ["p(95)<750"],
}, },
}; };

View File

@@ -16,15 +16,35 @@ export const options = {
name: "Groups", name: "Groups",
}, },
}, },
stages: [ scenarios: {
{ duration: "30s", target: 10 }, constant_load: {
{ duration: "1m", target: 20 }, executor: "constant-arrival-rate",
{ duration: "2m", target: 25 }, rate: 30,
{ duration: "30s", target: 0 }, timeUnit: "1m", // 0.5 requests / second
], duration: "10m",
preAllocatedVUs: 5,
},
ramping_load: {
executor: "ramping-arrival-rate",
startRate: 30,
timeUnit: "1m", // 0.5 requests / second to start
stages: [
{ duration: "30s", target: 30 },
{ duration: "2m", target: 75 },
{ duration: "1m", target: 60 },
{ duration: "2m", target: 100 },
{ duration: "2m", target: 90 },
{ duration: "1m", target: 120 },
{ duration: "30s", target: 150 },
{ duration: "30s", target: 60 },
{ duration: "30s", target: 0 },
],
preAllocatedVUs: 20,
},
},
thresholds: { thresholds: {
http_req_failed: ["rate<0.01"], http_req_failed: ["rate<0.01"],
http_req_duration: ["p(95)<1500"], http_req_duration: ["p(95)<900"],
}, },
}; };

View File

@@ -12,15 +12,33 @@ export const options = {
name: "Login", name: "Login",
}, },
}, },
stages: [ scenarios: {
{ duration: "30s", target: 10 }, constant_load: {
{ duration: "1m", target: 20 }, executor: "constant-arrival-rate",
{ duration: "2m", target: 25 }, rate: 2,
{ duration: "30s", target: 0 }, timeUnit: "1s", // 2 requests / second
], duration: "10m",
preAllocatedVUs: 10,
},
ramping_load: {
executor: "ramping-arrival-rate",
startRate: 60,
timeUnit: "1m", // 1 request / second to start
stages: [
{ duration: "30s", target: 60 },
{ duration: "5m", target: 120 },
{ duration: "2m", target: 150 },
{ duration: "1m", target: 180 },
{ duration: "30s", target: 200 },
{ duration: "30s", target: 90 },
{ duration: "30s", target: 0 },
],
preAllocatedVUs: 25,
},
},
thresholds: { thresholds: {
http_req_failed: ["rate<0.01"], http_req_failed: ["rate<0.01"],
http_req_duration: ["p(95)<3000"], http_req_duration: ["p(95)<1500"],
}, },
}; };

View File

@@ -25,7 +25,7 @@ docker_build() {
docker build -t bitwarden/$project_name_lower:$docker_tag $project_dir docker build -t bitwarden/$project_name_lower:$docker_tag $project_dir
if [ "$docker_push" == "1" ]; then if [ "$docker_push" == "1" ]; then
docker push bitwarden/$project_name_lower:$docker_tag $project_dir docker push bitwarden/$project_name_lower:$docker_tag
fi fi
} }

View File

@@ -1,6 +1,7 @@
using Bit.Core; using Bit.Core;
using Bit.Core.Jobs; using Bit.Core.Jobs;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Settings;
using Quartz; using Quartz;
namespace Bit.Admin.Auth.Jobs; namespace Bit.Admin.Auth.Jobs;
@@ -8,20 +9,26 @@ namespace Bit.Admin.Auth.Jobs;
public class DeleteAuthRequestsJob : BaseJob public class DeleteAuthRequestsJob : BaseJob
{ {
private readonly IAuthRequestRepository _authRepo; private readonly IAuthRequestRepository _authRepo;
private readonly IGlobalSettings _globalSettings;
public DeleteAuthRequestsJob( public DeleteAuthRequestsJob(
IAuthRequestRepository authrepo, IAuthRequestRepository authrepo,
IGlobalSettings globalSettings,
ILogger<DeleteAuthRequestsJob> logger) ILogger<DeleteAuthRequestsJob> logger)
: base(logger) : base(logger)
{ {
_authRepo = authrepo; _authRepo = authrepo;
_globalSettings = globalSettings;
} }
protected async override Task ExecuteJobAsync(IJobExecutionContext context) protected async override Task ExecuteJobAsync(IJobExecutionContext context)
{ {
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: Start"); _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: Start");
var count = await _authRepo.DeleteExpiredAsync(); var count = await _authRepo.DeleteExpiredAsync(
_logger.LogInformation(Constants.BypassFiltersEventId, $"{count} records deleted from AuthRequests."); _globalSettings.PasswordlessAuth.UserRequestExpiration,
_globalSettings.PasswordlessAuth.AdminRequestExpiration,
_globalSettings.PasswordlessAuth.AfterAdminApprovalExpiration);
_logger.LogInformation(Constants.BypassFiltersEventId, "{Count} records deleted from AuthRequests.", count);
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: End"); _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteAuthRequestsJob: End");
} }
} }

View File

@@ -17,8 +17,8 @@
"jquery": "3.5.1", "jquery": "3.5.1",
"merge-stream": "2.0.0", "merge-stream": "2.0.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"sass": "^1.49.7", "sass": "1.49.7",
"toastr": "^2.1.4" "toastr": "2.1.4"
} }
}, },
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
@@ -3925,9 +3925,9 @@
} }
}, },
"node_modules/sass/node_modules/anymatch": { "node_modules/sass/node_modules/anymatch": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
@@ -4694,7 +4694,7 @@
"node_modules/toastr": { "node_modules/toastr": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=", "integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"jquery": ">=1.12.0" "jquery": ">=1.12.0"
@@ -8168,9 +8168,9 @@
}, },
"dependencies": { "dependencies": {
"anymatch": { "anymatch": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true, "dev": true,
"requires": { "requires": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
@@ -8774,7 +8774,7 @@
"toastr": { "toastr": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=", "integrity": "sha512-LIy77F5n+sz4tefMmFOntcJ6HL0Fv3k1TDnNmFZ0bU/GcvIIfy6eG2v7zQmMiYgaalAiUv75ttFrPn5s0gyqlA==",
"dev": true, "dev": true,
"requires": { "requires": {
"jquery": ">=1.12.0" "jquery": ">=1.12.0"

View File

@@ -16,7 +16,7 @@
"jquery": "3.5.1", "jquery": "3.5.1",
"merge-stream": "2.0.0", "merge-stream": "2.0.0",
"popper.js": "1.16.1", "popper.js": "1.16.1",
"sass": "^1.49.7", "sass": "1.49.7",
"toastr": "^2.1.4" "toastr": "2.1.4"
} }
} }

View File

@@ -2834,7 +2834,7 @@
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3" "Core": "2023.5.1"
} }
}, },
"core": { "core": {
@@ -2882,7 +2882,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2890,7 +2890,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2902,7 +2902,7 @@
"migrator": { "migrator": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.Extensions.Logging": "6.0.0", "Microsoft.Extensions.Logging": "6.0.0",
"dbup-sqlserver": "5.0.8" "dbup-sqlserver": "5.0.8"
} }
@@ -2910,30 +2910,30 @@
"mysqlmigrations": { "mysqlmigrations": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"postgresmigrations": { "postgresmigrations": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"sqlitemigrations": { "sqlitemigrations": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -9,6 +9,7 @@ using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -19,6 +20,7 @@ namespace Bit.Api.Controllers;
[Authorize("Application")] [Authorize("Application")]
public class OrganizationUsersController : Controller public class OrganizationUsersController : Controller
{ {
private readonly IEnableAccessSecretsManagerCommand _enableAccessSecretsManagerCommand;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
@@ -29,6 +31,7 @@ public class OrganizationUsersController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
public OrganizationUsersController( public OrganizationUsersController(
IEnableAccessSecretsManagerCommand enableAccessSecretsManagerCommand,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
IOrganizationService organizationService, IOrganizationService organizationService,
@@ -38,6 +41,7 @@ public class OrganizationUsersController : Controller
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ICurrentContext currentContext) ICurrentContext currentContext)
{ {
_enableAccessSecretsManagerCommand = enableAccessSecretsManagerCommand;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_organizationService = organizationService; _organizationService = organizationService;
@@ -420,6 +424,29 @@ public class OrganizationUsersController : Controller
return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService)); return await RestoreOrRevokeUsersAsync(orgId, model, (orgId, orgUserIds, restoringUserId) => _organizationService.RestoreUsersAsync(orgId, orgUserIds, restoringUserId, _userService));
} }
[HttpPatch("enable-secrets-manager")]
[HttpPut("enable-secrets-manager")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkEnableSecretsManagerAsync(Guid orgId,
[FromBody] OrganizationUserBulkRequestModel model)
{
if (!await _currentContext.ManageUsers(orgId))
{
throw new NotFoundException();
}
var orgUsers = (await _organizationUserRepository.GetManyAsync(model.Ids))
.Where(ou => ou.OrganizationId == orgId).ToList();
if (orgUsers.Count == 0)
{
throw new BadRequestException("Users invalid.");
}
var results = await _enableAccessSecretsManagerCommand.EnableUsersAsync(orgUsers);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.organizationUser.Id, r.error)));
}
private async Task RestoreOrRevokeUserAsync( private async Task RestoreOrRevokeUserAsync(
Guid orgId, Guid orgId,
Guid id, Guid id,

View File

@@ -28,6 +28,7 @@ public class ConfigResponseModel : ResponseModel
GitHash = AssemblyHelpers.GetGitHash(); GitHash = AssemblyHelpers.GetGitHash();
Environment = new EnvironmentConfigResponseModel Environment = new EnvironmentConfigResponseModel
{ {
CloudRegion = globalSettings.BaseServiceUri.CloudRegion,
Vault = globalSettings.BaseServiceUri.Vault, Vault = globalSettings.BaseServiceUri.Vault,
Api = globalSettings.BaseServiceUri.Api, Api = globalSettings.BaseServiceUri.Api,
Identity = globalSettings.BaseServiceUri.Identity, Identity = globalSettings.BaseServiceUri.Identity,
@@ -46,6 +47,7 @@ public class ServerConfigResponseModel
public class EnvironmentConfigResponseModel public class EnvironmentConfigResponseModel
{ {
public string CloudRegion { get; set; }
public string Vault { get; set; } public string Vault { get; set; }
public string Api { get; set; } public string Api { get; set; }
public string Identity { get; set; } public string Identity { get; set; }

View File

@@ -73,9 +73,8 @@ public class ProjectsController : Controller
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var result = await _createProjectCommand.CreateAsync(project, userId); var result = await _createProjectCommand.CreateAsync(project, userId, _currentContext.ClientType);
// Creating a project means you have read & write permission. // Creating a project means you have read & write permission.
return new ProjectResponseModel(result, true, true); return new ProjectResponseModel(result, true, true);

View File

@@ -136,7 +136,10 @@ public class Startup
services.AddCoreLocalizationServices(); services.AddCoreLocalizationServices();
//health check //health check
services.AddHealthChecks(globalSettings); if (!globalSettings.SelfHosted)
{
services.AddHealthChecks(globalSettings);
}
#if OSS #if OSS
services.AddOosServices(); services.AddOosServices();
@@ -215,12 +218,15 @@ public class Startup
{ {
endpoints.MapDefaultControllerRoute(); endpoints.MapDefaultControllerRoute();
endpoints.MapHealthChecks("/healthz"); if (!globalSettings.SelfHosted)
endpoints.MapHealthChecks("/healthz/extended", new HealthCheckOptions
{ {
ResponseWriter = HealthCheckServiceExtensions.WriteResponse endpoints.MapHealthChecks("/healthz");
});
endpoints.MapHealthChecks("/healthz/extended", new HealthCheckOptions
{
ResponseWriter = HealthCheckServiceExtensions.WriteResponse
});
}
}); });
// Add Swagger // Add Swagger

View File

@@ -1,5 +1,6 @@
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.SharedWeb.Health; using Bit.SharedWeb.Health;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
@@ -80,35 +81,35 @@ public static class ServiceCollectionExtensions
builder.AddUrlGroup(identityUri, "identity"); builder.AddUrlGroup(identityUri, "identity");
if (!string.IsNullOrEmpty(globalSettings.SqlServer.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.SqlServer.ConnectionString))
{ {
builder.AddSqlServer(globalSettings.SqlServer.ConnectionString); builder.AddSqlServer(globalSettings.SqlServer.ConnectionString);
} }
if (!string.IsNullOrEmpty(globalSettings.Redis.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.Redis.ConnectionString))
{ {
builder.AddRedis(globalSettings.Redis.ConnectionString); builder.AddRedis(globalSettings.Redis.ConnectionString);
} }
if (!string.IsNullOrEmpty(globalSettings.Storage.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.Storage.ConnectionString))
{ {
builder.AddAzureQueueStorage(globalSettings.Storage.ConnectionString, name: "storage_queue") builder.AddAzureQueueStorage(globalSettings.Storage.ConnectionString, name: "storage_queue")
.AddAzureQueueStorage(globalSettings.Events.ConnectionString, name: "events_queue"); .AddAzureQueueStorage(globalSettings.Events.ConnectionString, name: "events_queue");
} }
if (!string.IsNullOrEmpty(globalSettings.Notifications.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.Notifications.ConnectionString))
{ {
builder.AddAzureQueueStorage(globalSettings.Notifications.ConnectionString, builder.AddAzureQueueStorage(globalSettings.Notifications.ConnectionString,
name: "notifications_queue"); name: "notifications_queue");
} }
if (!string.IsNullOrEmpty(globalSettings.ServiceBus.ConnectionString)) if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString))
{ {
builder.AddAzureServiceBusTopic(_ => globalSettings.ServiceBus.ConnectionString, builder.AddAzureServiceBusTopic(_ => globalSettings.ServiceBus.ConnectionString,
_ => globalSettings.ServiceBus.ApplicationCacheTopicName, name: "service_bus"); _ => globalSettings.ServiceBus.ApplicationCacheTopicName, name: "service_bus");
} }
if (!string.IsNullOrEmpty(globalSettings.Mail.SendGridApiKey)) if (CoreHelpers.SettingHasValue(globalSettings.Mail.SendGridApiKey))
{ {
builder.AddSendGrid(globalSettings.Mail.SendGridApiKey); builder.AddSendGrid(globalSettings.Mail.SendGridApiKey);
} }

View File

@@ -2907,85 +2907,85 @@
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )" "Core": "2023.5.1"
} }
}, },
"commercial.infrastructure.entityframework": { "commercial.infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"core": { "core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AWSSDK.SQS": "[3.7.2.47, )", "AWSSDK.SQS": "3.7.2.47",
"AWSSDK.SimpleEmail": "[3.7.0.150, )", "AWSSDK.SimpleEmail": "3.7.0.150",
"AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit": "4.0.2",
"AspNetCoreRateLimit.Redis": "[1.0.1, )", "AspNetCoreRateLimit.Redis": "1.0.1",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
"Azure.Storage.Blobs": "[12.14.1, )", "Azure.Storage.Blobs": "12.14.1",
"Azure.Storage.Queues": "[12.12.0, )", "Azure.Storage.Queues": "12.12.0",
"BitPay.Light": "[1.0.1907, )", "BitPay.Light": "1.0.1907",
"Braintree": "[5.12.0, )", "Braintree": "5.12.0",
"DnsClient": "[1.7.0, )", "DnsClient": "1.7.0",
"Fido2.AspNet": "[3.0.1, )", "Fido2.AspNet": "3.0.1",
"Handlebars.Net": "[2.1.2, )", "Handlebars.Net": "2.1.2",
"IdentityServer4": "[4.1.2, )", "IdentityServer4": "4.1.2",
"IdentityServer4.AccessTokenValidation": "[3.0.1, )", "IdentityServer4.AccessTokenValidation": "3.0.1",
"LaunchDarkly.ServerSdk": "[7.0.0, )", "LaunchDarkly.ServerSdk": "7.0.0",
"MailKit": "[3.2.0, )", "MailKit": "3.2.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )", "Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )", "Microsoft.Azure.Cosmos.Table": "1.0.8",
"Microsoft.Azure.NotificationHubs": "[4.1.0, )", "Microsoft.Azure.NotificationHubs": "4.1.0",
"Microsoft.Azure.ServiceBus": "[5.2.0, )", "Microsoft.Azure.ServiceBus": "5.2.0",
"Microsoft.Data.SqlClient": "[5.0.1, )", "Microsoft.Data.SqlClient": "5.0.1",
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )", "Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )", "Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )", "Microsoft.Extensions.Identity.Stores": "6.0.4",
"Newtonsoft.Json": "[13.0.1, )", "Newtonsoft.Json": "13.0.1",
"Otp.NET": "[1.2.2, )", "Otp.NET": "1.2.2",
"Quartz": "[3.4.0, )", "Quartz": "3.4.0",
"SendGrid": "[9.27.0, )", "SendGrid": "9.27.0",
"Sentry.Serilog": "[3.16.0, )", "Sentry.Serilog": "3.16.0",
"Serilog.AspNetCore": "[5.0.0, )", "Serilog.AspNetCore": "5.0.0",
"Serilog.Extensions.Logging": "[3.1.0, )", "Serilog.Extensions.Logging": "3.1.0",
"Serilog.Extensions.Logging.File": "[2.0.0, )", "Serilog.Extensions.Logging.File": "2.0.0",
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )", "Serilog.Sinks.AzureCosmosDB": "2.0.0",
"Serilog.Sinks.SyslogMessages": "[2.0.6, )", "Serilog.Sinks.SyslogMessages": "2.0.6",
"Stripe.net": "[40.0.0, )", "Stripe.net": "40.0.0",
"YubicoDotNetClient": "[1.2.0, )" "YubicoDotNetClient": "1.2.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Dapper": "[2.0.123, )" "Dapper": "2.0.123"
} }
}, },
"infrastructure.entityframework": { "infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
"Npgsql.EntityFrameworkCore.PostgreSQL": "[6.0.8, )", "Npgsql.EntityFrameworkCore.PostgreSQL": "6.0.8",
"Pomelo.EntityFrameworkCore.MySql": "[6.0.2, )", "Pomelo.EntityFrameworkCore.MySql": "6.0.2",
"linq2db.EntityFrameworkCore": "[6.11.0, )" "linq2db.EntityFrameworkCore": "6.11.0"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.Dapper": "[2023.5.0, )", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2794,7 +2794,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -5,7 +5,7 @@ namespace Bit.Core.Repositories;
public interface IAuthRequestRepository : IRepository<AuthRequest, Guid> public interface IAuthRequestRepository : IRepository<AuthRequest, Guid>
{ {
Task<int> DeleteExpiredAsync(); Task<int> DeleteExpiredAsync(TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration);
Task<ICollection<AuthRequest>> GetManyByUserIdAsync(Guid userId); Task<ICollection<AuthRequest>> GetManyByUserIdAsync(Guid userId);
Task<ICollection<OrganizationAdminAuthRequest>> GetManyPendingByOrganizationIdAsync(Guid organizationId); Task<ICollection<OrganizationAdminAuthRequest>> GetManyPendingByOrganizationIdAsync(Guid organizationId);
Task<ICollection<OrganizationAdminAuthRequest>> GetManyAdminApprovalRequestsByManyIdsAsync(Guid organizationId, IEnumerable<Guid> ids); Task<ICollection<OrganizationAdminAuthRequest>> GetManyAdminApprovalRequestsByManyIdsAsync(Guid organizationId, IEnumerable<Guid> ids);

View File

@@ -3,4 +3,7 @@
public interface IPasswordlessAuthSettings public interface IPasswordlessAuthSettings
{ {
bool KnownDevicesOnly { get; set; } bool KnownDevicesOnly { get; set; }
TimeSpan UserRequestExpiration { get; set; }
TimeSpan AdminRequestExpiration { get; set; }
TimeSpan AfterAdminApprovalExpiration { get; set; }
} }

View File

@@ -15,6 +15,8 @@ using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterpri
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tokens; using Bit.Core.Tokens;
@@ -29,6 +31,7 @@ public static class OrganizationServiceCollectionExtensions
public static void AddOrganizationServices(this IServiceCollection services, IGlobalSettings globalSettings) public static void AddOrganizationServices(this IServiceCollection services, IGlobalSettings globalSettings)
{ {
services.AddScoped<IOrganizationService, OrganizationService>(); services.AddScoped<IOrganizationService, OrganizationService>();
services.AddScoped<IEnableAccessSecretsManagerCommand, EnableAccessSecretsManagerCommand>();
services.AddTokenizers(); services.AddTokenizers();
services.AddOrganizationGroupCommands(); services.AddOrganizationGroupCommands();
services.AddOrganizationConnectionCommands(); services.AddOrganizationConnectionCommands();

View File

@@ -0,0 +1,43 @@
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
public class EnableAccessSecretsManagerCommand : IEnableAccessSecretsManagerCommand
{
private readonly IOrganizationUserRepository _organizationUserRepository;
public EnableAccessSecretsManagerCommand(IOrganizationUserRepository organizationUserRepository)
{
_organizationUserRepository = organizationUserRepository;
}
public async Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers)
{
var results = new List<(OrganizationUser organizationUser, string error)>();
var usersToEnable = new List<OrganizationUser>();
foreach (var orgUser in organizationUsers)
{
if (orgUser.AccessSecretsManager)
{
results.Add((orgUser, "User already has access to Secrets Manager"));
}
else
{
orgUser.AccessSecretsManager = true;
usersToEnable.Add(orgUser);
results.Add((orgUser, ""));
}
}
if (usersToEnable.Any())
{
await _organizationUserRepository.ReplaceManyAsync(usersToEnable);
}
return results;
}
}

View File

@@ -0,0 +1,9 @@
using Bit.Core.Entities;
namespace Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager.Interfaces;
public interface IEnableAccessSecretsManagerCommand
{
Task<List<(OrganizationUser organizationUser, string error)>> EnableUsersAsync(
IEnumerable<OrganizationUser> organizationUsers);
}

View File

@@ -1,8 +1,9 @@
using Bit.Core.SecretsManager.Entities; using Bit.Core.Identity;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces; namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces;
public interface ICreateProjectCommand public interface ICreateProjectCommand
{ {
Task<Project> CreateAsync(Project project, Guid userId); Task<Project> CreateAsync(Project project, Guid userId, ClientType clientType);
} }

View File

@@ -143,6 +143,7 @@ public class GlobalSettings : IGlobalSettings
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
public string CloudRegion { get; set; }
public string Vault { get; set; } public string Vault { get; set; }
public string VaultWithHash => $"{Vault}/#"; public string VaultWithHash => $"{Vault}/#";
@@ -534,6 +535,9 @@ public class GlobalSettings : IGlobalSettings
public class PasswordlessAuthSettings : IPasswordlessAuthSettings public class PasswordlessAuthSettings : IPasswordlessAuthSettings
{ {
public bool KnownDevicesOnly { get; set; } = true; public bool KnownDevicesOnly { get; set; } = true;
public TimeSpan UserRequestExpiration { get; set; } = TimeSpan.FromMinutes(15);
public TimeSpan AdminRequestExpiration { get; set; } = TimeSpan.FromDays(7);
public TimeSpan AfterAdminApprovalExpiration { get; set; } = TimeSpan.FromHours(12);
} }
public class DomainVerificationSettings : IDomainVerificationSettings public class DomainVerificationSettings : IDomainVerificationSettings

View File

@@ -3,6 +3,7 @@ namespace Bit.Core.Settings;
public interface IBaseServiceUriSettings public interface IBaseServiceUriSettings
{ {
string CloudRegion { get; set; }
string Vault { get; set; } string Vault { get; set; }
string VaultWithHash { get; } string VaultWithHash { get; }
string Api { get; set; } string Api { get; set; }

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2794,7 +2794,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2794,7 +2794,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2806,9 +2806,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2796,7 +2796,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2804,7 +2804,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2816,9 +2816,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2808,7 +2808,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2816,7 +2816,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2828,9 +2828,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -19,13 +19,19 @@ public class AuthRequestRepository : Repository<AuthRequest, Guid>, IAuthRequest
: base(connectionString, readOnlyConnectionString) : base(connectionString, readOnlyConnectionString)
{ } { }
public async Task<int> DeleteExpiredAsync() public async Task<int> DeleteExpiredAsync(
TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration)
{ {
using (var connection = new SqlConnection(ConnectionString)) using (var connection = new SqlConnection(ConnectionString))
{ {
return await connection.ExecuteAsync( return await connection.ExecuteAsync(
$"[{Schema}].[AuthRequest_DeleteIfExpired]", $"[{Schema}].[AuthRequest_DeleteIfExpired]",
null, new
{
UserExpirationSeconds = (int)userRequestExpiration.TotalSeconds,
AdminExpirationSeconds = (int)adminRequestExpiration.TotalSeconds,
AdminApprovalExpirationSeconds = (int)afterAdminApprovalExpiration.TotalSeconds,
},
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
} }

View File

@@ -15,15 +15,20 @@ public class AuthRequestRepository : Repository<Core.Auth.Entities.AuthRequest,
public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) public AuthRequestRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper)
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.AuthRequests) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.AuthRequests)
{ } { }
public async Task<int> DeleteExpiredAsync() public async Task<int> DeleteExpiredAsync(
TimeSpan userRequestExpiration, TimeSpan adminRequestExpiration, TimeSpan afterAdminApprovalExpiration)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var expiredRequests = await dbContext.AuthRequests.Where(a => a.CreationDate < DateTime.Now.AddMinutes(-15)).ToListAsync(); var expiredRequests = await dbContext.AuthRequests
.Where(a => (a.Type != AuthRequestType.AdminApproval && a.CreationDate.AddSeconds(userRequestExpiration.TotalSeconds) < DateTime.UtcNow)
|| (a.Type == AuthRequestType.AdminApproval && a.Approved != true && a.CreationDate.AddSeconds(adminRequestExpiration.TotalSeconds) < DateTime.UtcNow)
|| (a.Type == AuthRequestType.AdminApproval && a.Approved == true && a.ResponseDate.Value.AddSeconds(afterAdminApprovalExpiration.TotalSeconds) < DateTime.UtcNow))
.ToListAsync();
dbContext.AuthRequests.RemoveRange(expiredRequests); dbContext.AuthRequests.RemoveRange(expiredRequests);
await dbContext.SaveChangesAsync(); return await dbContext.SaveChangesAsync();
return 1;
} }
} }

View File

@@ -50,7 +50,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
where ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null where ou.AccessAll || cu.CollectionId != null || g.AccessAll || cg.CollectionId != null
select new { c, ou, o, cc, cu, gu, g, cg }.c; select c;
var query2 = from c in dbContext.Ciphers var query2 = from c in dbContext.Ciphers
where c.UserId == _userId where c.UserId == _userId
@@ -79,14 +79,23 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
private static Guid? GetFolderId(Guid? userId, Cipher cipher) private static Guid? GetFolderId(Guid? userId, Cipher cipher)
{ {
if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.Folders)) try
{ {
var folders = JsonSerializer.Deserialize<Dictionary<Guid, Guid>>(cipher.Folders); if (userId.HasValue && !string.IsNullOrWhiteSpace(cipher.Folders))
if (folders.TryGetValue(userId.Value, out var folder))
{ {
return folder; var folders = JsonSerializer.Deserialize<Dictionary<Guid, Guid>>(cipher.Folders);
if (folders.TryGetValue(userId.Value, out var folder))
{
return folder;
}
} }
return null;
}
catch
{
// Some Folders might be in an invalid format like: '{ "", "<ValidGuid>" }'
return null;
} }
return null;
} }
} }

View File

@@ -1,4 +1,6 @@
using AutoMapper; using System.Text.Json;
using System.Text.Json.Nodes;
using AutoMapper;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Vault.Enums; using Bit.Core.Vault.Enums;
@@ -13,8 +15,8 @@ using Bit.Infrastructure.EntityFramework.Vault.Repositories.Queries;
using LinqToDB.EntityFrameworkCore; using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using NS = Newtonsoft.Json;
using Newtonsoft.Json.Linq; using NSL = Newtonsoft.Json.Linq;
using User = Bit.Core.Entities.User; using User = Bit.Core.Entities.User;
namespace Bit.Infrastructure.EntityFramework.Vault.Repositories; namespace Bit.Infrastructure.EntityFramework.Vault.Repositories;
@@ -198,9 +200,9 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
{ {
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var cipher = await dbContext.Ciphers.FindAsync(cipherId); var cipher = await dbContext.Ciphers.FindAsync(cipherId);
var attachmentsJson = JObject.Parse(cipher.Attachments); var attachmentsJson = NSL.JObject.Parse(cipher.Attachments);
attachmentsJson.Remove(attachmentId); attachmentsJson.Remove(attachmentId);
cipher.Attachments = JsonConvert.SerializeObject(attachmentsJson); cipher.Attachments = NS.JsonConvert.SerializeObject(attachmentsJson);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
@@ -396,8 +398,8 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
await idsToMove.ForEachAsync(cipher => await idsToMove.ForEachAsync(cipher =>
{ {
var foldersJson = string.IsNullOrWhiteSpace(cipher.Folders) ? var foldersJson = string.IsNullOrWhiteSpace(cipher.Folders) ?
new JObject() : new NSL.JObject() :
JObject.Parse(cipher.Folders); NSL.JObject.Parse(cipher.Folders);
if (folderId.HasValue) if (folderId.HasValue)
{ {
@@ -409,7 +411,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
foldersJson.Remove(userId.ToString()); foldersJson.Remove(userId.ToString());
} }
dbContext.Attach(cipher); dbContext.Attach(cipher);
cipher.Folders = JsonConvert.SerializeObject(foldersJson); cipher.Folders = NS.JsonConvert.SerializeObject(foldersJson);
}); });
await dbContext.UserBumpAccountRevisionDateAsync(userId); await dbContext.UserBumpAccountRevisionDateAsync(userId);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
@@ -418,27 +420,27 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
public async Task ReplaceAsync(CipherDetails cipher) public async Task ReplaceAsync(CipherDetails cipher)
{ {
cipher.UserId = cipher.OrganizationId.HasValue ?
null :
cipher.UserId;
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.Ciphers.FindAsync(cipher.Id); var entity = await dbContext.Ciphers.FindAsync(cipher.Id);
if (entity != null) if (entity != null)
{ {
var userIdKey = $"\"{cipher.UserId}\"";
if (cipher.Favorite) if (cipher.Favorite)
{ {
if (cipher.Favorites == null) if (cipher.Favorites == null)
{ {
cipher.Favorites = $"{{{userIdKey}:true}}"; var jsonObject = new JsonObject(new[]
{
new KeyValuePair<string, JsonNode>(cipher.UserId.Value.ToString(), true),
});
cipher.Favorites = JsonSerializer.Serialize(jsonObject);
} }
else else
{ {
var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites);
favorites.Add(cipher.UserId.Value, true); favorites.Add(cipher.UserId.Value, true);
cipher.Favorites = JsonConvert.SerializeObject(favorites); cipher.Favorites = JsonSerializer.Serialize(favorites);
} }
} }
else else
@@ -447,32 +449,45 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
{ {
var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); var favorites = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites);
favorites.Remove(cipher.UserId.Value); favorites.Remove(cipher.UserId.Value);
cipher.Favorites = JsonConvert.SerializeObject(favorites); cipher.Favorites = JsonSerializer.Serialize(favorites);
} }
} }
if (cipher.FolderId.HasValue) if (cipher.FolderId.HasValue)
{ {
if (cipher.Folders == null) if (cipher.Folders == null)
{ {
cipher.Folders = $"{{{userIdKey}:\"{cipher.FolderId}\"}}"; var jsonObject = new JsonObject(new[]
{
new KeyValuePair<string, JsonNode>(cipher.UserId.Value.ToString(), cipher.FolderId),
});
cipher.Folders = JsonSerializer.Serialize(jsonObject);
} }
else else
{ {
var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders); var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders);
folders.Add(cipher.UserId.Value, cipher.FolderId.Value); folders.Add(cipher.UserId.Value, cipher.FolderId.Value);
cipher.Folders = JsonConvert.SerializeObject(folders); cipher.Folders = JsonSerializer.Serialize(folders);
} }
} }
else else
{ {
if (cipher.Folders != null && cipher.Folders.Contains(cipher.UserId.Value.ToString())) if (cipher.Folders != null && cipher.Folders.Contains(cipher.UserId.Value.ToString()))
{ {
var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, bool>>(cipher.Favorites); var folders = CoreHelpers.LoadClassFromJsonData<Dictionary<Guid, Guid>>(cipher.Folders);
folders.Remove(cipher.UserId.Value); folders.Remove(cipher.UserId.Value);
cipher.Favorites = JsonConvert.SerializeObject(folders); cipher.Folders = JsonSerializer.Serialize(folders);
} }
} }
var mappedEntity = Mapper.Map<Cipher>((Core.Vault.Entities.Cipher)cipher);
// Check if this cipher is a part of an organization, and if so do
// not save the UserId into the database. This must be done after we
// set the user specific data like Folders and Favorites because
// the UserId key is used for that
cipher.UserId = cipher.OrganizationId.HasValue ?
null :
cipher.UserId;
var mappedEntity = Mapper.Map<Cipher>(cipher);
dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity);
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
@@ -701,10 +716,10 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
var cipher = await dbContext.Ciphers.FindAsync(attachment.Id); var cipher = await dbContext.Ciphers.FindAsync(attachment.Id);
var attachments = string.IsNullOrWhiteSpace(cipher.Attachments) ? var attachments = string.IsNullOrWhiteSpace(cipher.Attachments) ?
new Dictionary<string, CipherAttachment.MetaData>() : new Dictionary<string, CipherAttachment.MetaData>() :
JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments); NS.JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(cipher.Attachments);
var metaData = JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData); var metaData = NS.JsonConvert.DeserializeObject<CipherAttachment.MetaData>(attachment.AttachmentData);
attachments[attachment.AttachmentId] = metaData; attachments[attachment.AttachmentId] = metaData;
cipher.Attachments = JsonConvert.SerializeObject(attachments); cipher.Attachments = NS.JsonConvert.SerializeObject(attachments);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
if (attachment.OrganizationId.HasValue) if (attachment.OrganizationId.HasValue)
@@ -744,7 +759,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
var cipher = await dbContext.Ciphers.FindAsync(id); var cipher = await dbContext.Ciphers.FindAsync(id);
var foldersJson = JObject.Parse(cipher.Folders); var foldersJson = NSL.JObject.Parse(cipher.Folders);
if (foldersJson == null && folderId.HasValue) if (foldersJson == null && folderId.HasValue)
{ {
foldersJson.Add(userId.ToString(), folderId.Value); foldersJson.Add(userId.ToString(), folderId.Value);
@@ -758,7 +773,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
foldersJson.Remove(userId.ToString()); foldersJson.Remove(userId.ToString());
} }
var favoritesJson = JObject.Parse(cipher.Favorites); var favoritesJson = NSL.JObject.Parse(cipher.Favorites);
if (favorite) if (favorite)
{ {
favoritesJson.Add(userId.ToString(), favorite); favoritesJson.Add(userId.ToString(), favorite);

View File

@@ -2836,7 +2836,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2844,7 +2844,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2856,9 +2856,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2786,7 +2786,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2794,7 +2794,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -1,6 +1,19 @@
CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired] -- UserExpirationSeconds to 15 minutes (15 * 60)
-- AdminExpirationSeconds to 7 days (7 * 24 * 60 * 60)
-- AdminApprovalExpirationSeconds to 12 hour (12 * 60 * 60)
CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired]
@UserExpirationSeconds INT = 900,
@AdminExpirationSeconds INT = 604800,
@AdminApprovalExpirationSeconds INT = 43200
AS AS
BEGIN BEGIN
SET NOCOUNT OFF SET NOCOUNT OFF
DELETE FROM [dbo].[AuthRequest] WHERE [CreationDate] < DATEADD(minute, -15, GETUTCDATE()); DELETE FROM [dbo].[AuthRequest]
-- User requests expire after 15 minutes (by default) of their creation
WHERE ([Type] != 2 AND DATEADD(second, @UserExpirationSeconds, [CreationDate]) < GETUTCDATE())
-- Admin requests expire after 7 days (by default) of their creation if they have not been approved
OR ([Type] = 2 AND ([Approved] IS NULL OR [Approved] = 0) AND DATEADD(second, @AdminExpirationSeconds,[CreationDate]) < GETUTCDATE())
-- Admin requests expire after 12 hours (by default) of their approval
OR ([Type] = 2 AND [Approved] = 1 AND DATEADD(second, @AdminApprovalExpirationSeconds, [ResponseDate]) < GETUTCDATE());
END END

View File

@@ -3273,133 +3273,133 @@
"api": { "api": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AspNetCore.HealthChecks.AzureServiceBus": "[6.1.0, )", "AspNetCore.HealthChecks.AzureServiceBus": "6.1.0",
"AspNetCore.HealthChecks.AzureStorage": "[6.1.2, )", "AspNetCore.HealthChecks.AzureStorage": "6.1.2",
"AspNetCore.HealthChecks.Network": "[6.0.4, )", "AspNetCore.HealthChecks.Network": "6.0.4",
"AspNetCore.HealthChecks.Redis": "[6.0.4, )", "AspNetCore.HealthChecks.Redis": "6.0.4",
"AspNetCore.HealthChecks.SendGrid": "[6.0.2, )", "AspNetCore.HealthChecks.SendGrid": "6.0.2",
"AspNetCore.HealthChecks.SqlServer": "[6.0.2, )", "AspNetCore.HealthChecks.SqlServer": "6.0.2",
"AspNetCore.HealthChecks.Uris": "[6.0.3, )", "AspNetCore.HealthChecks.Uris": "6.0.3",
"Azure.Messaging.EventGrid": "[4.10.0, )", "Azure.Messaging.EventGrid": "4.10.0",
"Commercial.Core": "[2023.5.0, )", "Commercial.Core": "2023.5.1",
"Commercial.Infrastructure.EntityFramework": "[2023.5.0, )", "Commercial.Infrastructure.EntityFramework": "2023.5.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"SharedWeb": "[2023.5.0, )", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore": "[6.5.0, )" "Swashbuckle.AspNetCore": "6.5.0"
} }
}, },
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )" "Core": "2023.5.1"
} }
}, },
"commercial.infrastructure.entityframework": { "commercial.infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"common": { "common": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "[4.17.0, )", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "[4.17.0, )", "AutoFixture.Xunit2": "4.17.0",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "[17.1.0, )", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "[4.3.0, )", "NSubstitute": "4.3.0",
"xunit": "[2.4.1, )" "xunit": "2.4.1"
} }
}, },
"core": { "core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AWSSDK.SQS": "[3.7.2.47, )", "AWSSDK.SQS": "3.7.2.47",
"AWSSDK.SimpleEmail": "[3.7.0.150, )", "AWSSDK.SimpleEmail": "3.7.0.150",
"AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit": "4.0.2",
"AspNetCoreRateLimit.Redis": "[1.0.1, )", "AspNetCoreRateLimit.Redis": "1.0.1",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
"Azure.Storage.Blobs": "[12.14.1, )", "Azure.Storage.Blobs": "12.14.1",
"Azure.Storage.Queues": "[12.12.0, )", "Azure.Storage.Queues": "12.12.0",
"BitPay.Light": "[1.0.1907, )", "BitPay.Light": "1.0.1907",
"Braintree": "[5.12.0, )", "Braintree": "5.12.0",
"DnsClient": "[1.7.0, )", "DnsClient": "1.7.0",
"Fido2.AspNet": "[3.0.1, )", "Fido2.AspNet": "3.0.1",
"Handlebars.Net": "[2.1.2, )", "Handlebars.Net": "2.1.2",
"IdentityServer4": "[4.1.2, )", "IdentityServer4": "4.1.2",
"IdentityServer4.AccessTokenValidation": "[3.0.1, )", "IdentityServer4.AccessTokenValidation": "3.0.1",
"LaunchDarkly.ServerSdk": "[7.0.0, )", "LaunchDarkly.ServerSdk": "7.0.0",
"MailKit": "[3.2.0, )", "MailKit": "3.2.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )", "Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )", "Microsoft.Azure.Cosmos.Table": "1.0.8",
"Microsoft.Azure.NotificationHubs": "[4.1.0, )", "Microsoft.Azure.NotificationHubs": "4.1.0",
"Microsoft.Azure.ServiceBus": "[5.2.0, )", "Microsoft.Azure.ServiceBus": "5.2.0",
"Microsoft.Data.SqlClient": "[5.0.1, )", "Microsoft.Data.SqlClient": "5.0.1",
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )", "Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )", "Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )", "Microsoft.Extensions.Identity.Stores": "6.0.4",
"Newtonsoft.Json": "[13.0.1, )", "Newtonsoft.Json": "13.0.1",
"Otp.NET": "[1.2.2, )", "Otp.NET": "1.2.2",
"Quartz": "[3.4.0, )", "Quartz": "3.4.0",
"SendGrid": "[9.27.0, )", "SendGrid": "9.27.0",
"Sentry.Serilog": "[3.16.0, )", "Sentry.Serilog": "3.16.0",
"Serilog.AspNetCore": "[5.0.0, )", "Serilog.AspNetCore": "5.0.0",
"Serilog.Extensions.Logging": "[3.1.0, )", "Serilog.Extensions.Logging": "3.1.0",
"Serilog.Extensions.Logging.File": "[2.0.0, )", "Serilog.Extensions.Logging.File": "2.0.0",
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )", "Serilog.Sinks.AzureCosmosDB": "2.0.0",
"Serilog.Sinks.SyslogMessages": "[2.0.6, )", "Serilog.Sinks.SyslogMessages": "2.0.6",
"Stripe.net": "[40.0.0, )", "Stripe.net": "40.0.0",
"YubicoDotNetClient": "[1.2.0, )" "YubicoDotNetClient": "1.2.0"
} }
}, },
"identity": { "identity": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"SharedWeb": "[2023.5.0, )", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore.SwaggerGen": "[6.5.0, )" "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Dapper": "[2.0.123, )" "Dapper": "2.0.123"
} }
}, },
"infrastructure.entityframework": { "infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
"Npgsql.EntityFrameworkCore.PostgreSQL": "[6.0.8, )", "Npgsql.EntityFrameworkCore.PostgreSQL": "6.0.8",
"Pomelo.EntityFrameworkCore.MySql": "[6.0.2, )", "Pomelo.EntityFrameworkCore.MySql": "6.0.2",
"linq2db.EntityFrameworkCore": "[6.11.0, )" "linq2db.EntityFrameworkCore": "6.11.0"
} }
}, },
"integrationtestcommon": { "integrationtestcommon": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Common": "[2023.5.0, )", "Common": "2023.5.1",
"Identity": "[2023.5.0, )", "Identity": "2023.5.1",
"Microsoft.AspNetCore.Mvc.Testing": "[6.0.5, )", "Microsoft.AspNetCore.Mvc.Testing": "6.0.5",
"Microsoft.EntityFrameworkCore.InMemory": "[6.0.5, )", "Microsoft.EntityFrameworkCore.InMemory": "6.0.5",
"Microsoft.Extensions.Configuration": "[6.0.1, )" "Microsoft.Extensions.Configuration": "6.0.1"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.Dapper": "[2023.5.0, )", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -114,12 +114,12 @@ public class ProjectsControllerTests
var resultProject = data.ToProject(orgId); var resultProject = data.ToProject(orgId);
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default) sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default, sutProvider.GetDependency<ICurrentContext>().ClientType)
.ReturnsForAnyArgs(resultProject); .ReturnsForAnyArgs(resultProject);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(orgId, data)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(orgId, data));
await sutProvider.GetDependency<ICreateProjectCommand>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<ICreateProjectCommand>().DidNotReceiveWithAnyArgs()
.CreateAsync(Arg.Any<Project>(), Arg.Any<Guid>()); .CreateAsync(Arg.Any<Project>(), Arg.Any<Guid>(), sutProvider.GetDependency<ICurrentContext>().ClientType);
} }
[Theory] [Theory]
@@ -134,13 +134,13 @@ public class ProjectsControllerTests
var resultProject = data.ToProject(orgId); var resultProject = data.ToProject(orgId);
sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default) sutProvider.GetDependency<ICreateProjectCommand>().CreateAsync(default, default, sutProvider.GetDependency<ICurrentContext>().ClientType)
.ReturnsForAnyArgs(resultProject); .ReturnsForAnyArgs(resultProject);
await sutProvider.Sut.CreateAsync(orgId, data); await sutProvider.Sut.CreateAsync(orgId, data);
await sutProvider.GetDependency<ICreateProjectCommand>().Received(1) await sutProvider.GetDependency<ICreateProjectCommand>().Received(1)
.CreateAsync(Arg.Any<Project>(), Arg.Any<Guid>()); .CreateAsync(Arg.Any<Project>(), Arg.Any<Guid>(), sutProvider.GetDependency<ICurrentContext>().ClientType);
} }
[Theory] [Theory]

View File

@@ -3151,129 +3151,129 @@
"api": { "api": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AspNetCore.HealthChecks.AzureServiceBus": "[6.1.0, )", "AspNetCore.HealthChecks.AzureServiceBus": "6.1.0",
"AspNetCore.HealthChecks.AzureStorage": "[6.1.2, )", "AspNetCore.HealthChecks.AzureStorage": "6.1.2",
"AspNetCore.HealthChecks.Network": "[6.0.4, )", "AspNetCore.HealthChecks.Network": "6.0.4",
"AspNetCore.HealthChecks.Redis": "[6.0.4, )", "AspNetCore.HealthChecks.Redis": "6.0.4",
"AspNetCore.HealthChecks.SendGrid": "[6.0.2, )", "AspNetCore.HealthChecks.SendGrid": "6.0.2",
"AspNetCore.HealthChecks.SqlServer": "[6.0.2, )", "AspNetCore.HealthChecks.SqlServer": "6.0.2",
"AspNetCore.HealthChecks.Uris": "[6.0.3, )", "AspNetCore.HealthChecks.Uris": "6.0.3",
"Azure.Messaging.EventGrid": "[4.10.0, )", "Azure.Messaging.EventGrid": "4.10.0",
"Commercial.Core": "[2023.5.0, )", "Commercial.Core": "2023.5.1",
"Commercial.Infrastructure.EntityFramework": "[2023.5.0, )", "Commercial.Infrastructure.EntityFramework": "2023.5.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"SharedWeb": "[2023.5.0, )", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore": "[6.5.0, )" "Swashbuckle.AspNetCore": "6.5.0"
} }
}, },
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )" "Core": "2023.5.1"
} }
}, },
"commercial.infrastructure.entityframework": { "commercial.infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"common": { "common": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "[4.17.0, )", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "[4.17.0, )", "AutoFixture.Xunit2": "4.17.0",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "[17.1.0, )", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "[4.3.0, )", "NSubstitute": "4.3.0",
"xunit": "[2.4.1, )" "xunit": "2.4.1"
} }
}, },
"core": { "core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AWSSDK.SQS": "[3.7.2.47, )", "AWSSDK.SQS": "3.7.2.47",
"AWSSDK.SimpleEmail": "[3.7.0.150, )", "AWSSDK.SimpleEmail": "3.7.0.150",
"AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit": "4.0.2",
"AspNetCoreRateLimit.Redis": "[1.0.1, )", "AspNetCoreRateLimit.Redis": "1.0.1",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
"Azure.Storage.Blobs": "[12.14.1, )", "Azure.Storage.Blobs": "12.14.1",
"Azure.Storage.Queues": "[12.12.0, )", "Azure.Storage.Queues": "12.12.0",
"BitPay.Light": "[1.0.1907, )", "BitPay.Light": "1.0.1907",
"Braintree": "[5.12.0, )", "Braintree": "5.12.0",
"DnsClient": "[1.7.0, )", "DnsClient": "1.7.0",
"Fido2.AspNet": "[3.0.1, )", "Fido2.AspNet": "3.0.1",
"Handlebars.Net": "[2.1.2, )", "Handlebars.Net": "2.1.2",
"IdentityServer4": "[4.1.2, )", "IdentityServer4": "4.1.2",
"IdentityServer4.AccessTokenValidation": "[3.0.1, )", "IdentityServer4.AccessTokenValidation": "3.0.1",
"LaunchDarkly.ServerSdk": "[7.0.0, )", "LaunchDarkly.ServerSdk": "7.0.0",
"MailKit": "[3.2.0, )", "MailKit": "3.2.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )", "Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )", "Microsoft.Azure.Cosmos.Table": "1.0.8",
"Microsoft.Azure.NotificationHubs": "[4.1.0, )", "Microsoft.Azure.NotificationHubs": "4.1.0",
"Microsoft.Azure.ServiceBus": "[5.2.0, )", "Microsoft.Azure.ServiceBus": "5.2.0",
"Microsoft.Data.SqlClient": "[5.0.1, )", "Microsoft.Data.SqlClient": "5.0.1",
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )", "Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )", "Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )", "Microsoft.Extensions.Identity.Stores": "6.0.4",
"Newtonsoft.Json": "[13.0.1, )", "Newtonsoft.Json": "13.0.1",
"Otp.NET": "[1.2.2, )", "Otp.NET": "1.2.2",
"Quartz": "[3.4.0, )", "Quartz": "3.4.0",
"SendGrid": "[9.27.0, )", "SendGrid": "9.27.0",
"Sentry.Serilog": "[3.16.0, )", "Sentry.Serilog": "3.16.0",
"Serilog.AspNetCore": "[5.0.0, )", "Serilog.AspNetCore": "5.0.0",
"Serilog.Extensions.Logging": "[3.1.0, )", "Serilog.Extensions.Logging": "3.1.0",
"Serilog.Extensions.Logging.File": "[2.0.0, )", "Serilog.Extensions.Logging.File": "2.0.0",
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )", "Serilog.Sinks.AzureCosmosDB": "2.0.0",
"Serilog.Sinks.SyslogMessages": "[2.0.6, )", "Serilog.Sinks.SyslogMessages": "2.0.6",
"Stripe.net": "[40.0.0, )", "Stripe.net": "40.0.0",
"YubicoDotNetClient": "[1.2.0, )" "YubicoDotNetClient": "1.2.0"
} }
}, },
"core.test": { "core.test": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "[4.17.0, )", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "[4.17.0, )", "AutoFixture.Xunit2": "4.17.0",
"Common": "[2023.5.0, )", "Common": "2023.5.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "[1.2.0, )", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "[17.1.0, )", "Microsoft.NET.Test.Sdk": "17.1.0",
"Moq": "[4.17.2, )", "Moq": "4.17.2",
"NSubstitute": "[4.3.0, )", "NSubstitute": "4.3.0",
"xunit": "[2.4.1, )" "xunit": "2.4.1"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Dapper": "[2.0.123, )" "Dapper": "2.0.123"
} }
}, },
"infrastructure.entityframework": { "infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
"Npgsql.EntityFrameworkCore.PostgreSQL": "[6.0.8, )", "Npgsql.EntityFrameworkCore.PostgreSQL": "6.0.8",
"Pomelo.EntityFrameworkCore.MySql": "[6.0.2, )", "Pomelo.EntityFrameworkCore.MySql": "6.0.2",
"linq2db.EntityFrameworkCore": "[6.11.0, )" "linq2db.EntityFrameworkCore": "6.11.0"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.Dapper": "[2023.5.0, )", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2988,8 +2988,8 @@
"billing": { "billing": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3" "SharedWeb": "2023.5.1"
} }
}, },
"common": { "common": {
@@ -2997,7 +2997,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3049,7 +3049,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3057,7 +3057,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3069,9 +3069,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -0,0 +1,81 @@
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.EnableAccessSecretsManager;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.SecretsManager.Commands.EnableAccessSecretsManager;
[SutProviderCustomize]
public class EnableAccessSecretsManagerCommandTests
{
[Theory]
[BitAutoData]
public async Task EnableUsers_UsersAlreadyEnabled_DoesNotCallRepository(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
foreach (var item in data)
{
item.AccessSecretsManager = true;
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.ReplaceManyAsync(default);
Assert.Equal(data.Count, result.Count);
Assert.Equal(data.Count,
result.Where(x => x.error == "User already has access to Secrets Manager").ToList().Count);
}
[Theory]
[BitAutoData]
public async Task EnableUsers_OneUserNotEnabled_CallsRepositoryForOne(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
var firstUser = new List<OrganizationUser>();
foreach (var item in data)
{
if (item == data.First())
{
item.AccessSecretsManager = false;
firstUser.Add(item);
}
else
{
item.AccessSecretsManager = true;
}
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.ReplaceManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(firstUser)));
Assert.Equal(data.Count, result.Count);
Assert.Equal(data.Count - 1,
result.Where(x => x.error == "User already has access to Secrets Manager").ToList().Count);
}
[Theory]
[BitAutoData]
public async Task EnableUsers_Success(
SutProvider<EnableAccessSecretsManagerCommand> sutProvider, ICollection<OrganizationUser> data)
{
foreach (var item in data)
{
item.AccessSecretsManager = false;
}
var result = await sutProvider.Sut.EnableUsersAsync(data);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
.ReplaceManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
Assert.Equal(data.Count, result.Count);
}
}

View File

@@ -2825,7 +2825,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",

View File

@@ -2972,14 +2972,14 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AngleSharp": "0.16.1", "AngleSharp": "0.16.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3" "SharedWeb": "2023.5.1"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2987,7 +2987,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -2999,9 +2999,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -3144,7 +3144,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3196,15 +3196,15 @@
"identity": { "identity": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0" "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3212,7 +3212,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3224,8 +3224,8 @@
"integrationtestcommon": { "integrationtestcommon": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Common": "2023.4.3", "Common": "2023.5.1",
"Identity": "2023.4.3", "Identity": "2023.5.1",
"Microsoft.AspNetCore.Mvc.Testing": "6.0.5", "Microsoft.AspNetCore.Mvc.Testing": "6.0.5",
"Microsoft.EntityFrameworkCore.InMemory": "6.0.5", "Microsoft.EntityFrameworkCore.InMemory": "6.0.5",
"Microsoft.Extensions.Configuration": "6.0.1" "Microsoft.Extensions.Configuration": "6.0.1"
@@ -3234,9 +3234,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -3011,7 +3011,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3063,15 +3063,15 @@
"identity": { "identity": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0" "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3079,7 +3079,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3091,9 +3091,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -3001,7 +3001,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3055,8 +3055,8 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Common": "2023.4.3", "Common": "2023.5.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"Moq": "4.17.2", "Moq": "4.17.2",
@@ -3067,7 +3067,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3075,7 +3075,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -0,0 +1,99 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Xunit;
namespace Bit.Infrastructure.IntegrationTest.Auth.Repositories;
public class AuthRequestRepositoryTests
{
private readonly static TimeSpan _userRequestExpiration = TimeSpan.FromMinutes(15);
private readonly static TimeSpan _adminRequestExpiration = TimeSpan.FromDays(6);
private readonly static TimeSpan _afterAdminApprovalExpiration = TimeSpan.FromHours(12);
[DatabaseTheory, DatabaseData]
public async Task DeleteExpiredAsync_Works(
IAuthRequestRepository authRequestRepository,
IUserRepository userRepository,
ITestDatabaseHelper helper)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
// A user auth request type that has passed it's expiration time, should be deleted.
var userExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AuthenticateAndUnlock, CreateExpiredDate(_userRequestExpiration)));
// An AdminApproval request that hasn't had any action taken on it and has passed it's expiration time, should be deleted.
var adminApprovalExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration)));
// An AdminApproval request that was approved before it expired but the user has been approved for too long, should be deleted.
var adminApprovedExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.AddDays(-6), true, CreateExpiredDate(_afterAdminApprovalExpiration)));
// An AdminApproval request that was rejected within it's allowed lifetime but has no gone past it's expiration time, should be deleted.
var adminRejectedExpiredAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, CreateExpiredDate(_adminRequestExpiration), false, DateTime.UtcNow.AddHours(-1)));
// A User AuthRequest that was created just a minute ago.
var notExpiredUserAuthRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.Unlock, DateTime.UtcNow.AddMinutes(-1)));
// An AdminApproval AuthRequest that was create 6 days 23 hours 59 minutes 59 seconds ago which is right on the edge of still being valid
var notExpiredAdminApprovalRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.Add(new TimeSpan(days: 6, hours: 23, minutes: 59, seconds: 59))));
// An AdminApproval AuthRequest that was created a week ago but just approved 11 hours ago.
var notExpiredApprovedAdminApprovalRequest = await authRequestRepository.CreateAsync(
CreateAuthRequest(user.Id, AuthRequestType.AdminApproval, DateTime.UtcNow.AddDays(7), true, DateTime.UtcNow.AddHours(11)));
helper.ClearTracker();
var numberOfDeleted = await authRequestRepository.DeleteExpiredAsync(_userRequestExpiration, _adminRequestExpiration, _afterAdminApprovalExpiration);
// Ensure all the AuthRequests that should have been deleted, have been deleted.
Assert.Null(await authRequestRepository.GetByIdAsync(userExpiredAuthRequest.Id));
Assert.Null(await authRequestRepository.GetByIdAsync(adminApprovalExpiredAuthRequest.Id));
Assert.Null(await authRequestRepository.GetByIdAsync(adminApprovedExpiredAuthRequest.Id));
Assert.Null(await authRequestRepository.GetByIdAsync(adminRejectedExpiredAuthRequest.Id));
// Ensure that all the AuthRequests that should have been left alone, were.
Assert.NotNull(await authRequestRepository.GetByIdAsync(notExpiredUserAuthRequest.Id));
Assert.NotNull(await authRequestRepository.GetByIdAsync(notExpiredAdminApprovalRequest.Id));
Assert.NotNull(await authRequestRepository.GetByIdAsync(notExpiredApprovedAdminApprovalRequest.Id));
// Ensure the repository responds with the amount of items it deleted and it deleted the right amount.
// NOTE: On local development this might fail on it's first run because the developer could have expired AuthRequests
// on their machine but aren't running the job that would delete them. The second run of this test should succeed.
Assert.Equal(4, numberOfDeleted);
}
private static AuthRequest CreateAuthRequest(Guid userId, AuthRequestType authRequestType, DateTime creationDate, bool? approved = null, DateTime? responseDate = null)
{
return new AuthRequest
{
UserId = userId,
Type = authRequestType,
Approved = approved,
RequestDeviceIdentifier = "something", // TODO: EF Doesn't enforce this as not null
RequestIpAddress = "1.1.1.1", // TODO: EF Doesn't enforce this as not null
AccessCode = "test_access_code", // TODO: EF Doesn't enforce this as not null
PublicKey = "test_public_key", // TODO: EF Doesn't enforce this as not null
CreationDate = creationDate,
ResponseDate = responseDate,
};
}
private static DateTime CreateExpiredDate(TimeSpan expirationPeriod)
{
var exp = expirationPeriod + TimeSpan.FromMinutes(1);
return DateTime.UtcNow.Add(exp.Negate());
}
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.Entities; using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@@ -121,4 +122,84 @@ public class CipherRepositoryTests
var collectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id); var collectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
Assert.NotEmpty(collectionCiphers); Assert.NotEmpty(collectionCiphers);
} }
[DatabaseTheory, DatabaseData]
public async Task ReplaceAsync_SuccessfullyMovesCipherToOrganization(IUserRepository userRepository,
ICipherRepository cipherRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IFolderRepository folderRepository,
ITestDatabaseHelper helper)
{
// This tests what happens when a cipher is moved into an organizations
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
user = await userRepository.GetByIdAsync(user.Id);
// Create cipher in personal vault
var createdCipher = await cipherRepository.CreateAsync(new Cipher
{
UserId = user.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test" // TODO: EF does not enforce this as NOT NULL
});
_ = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var folder = await folderRepository.CreateAsync(new Folder
{
Name = "FolderName",
UserId = user.Id,
});
helper.ClearTracker();
// Move cipher to organization vault
await cipherRepository.ReplaceAsync(new CipherDetails
{
Id = createdCipher.Id,
UserId = user.Id,
OrganizationId = organization.Id,
FolderId = folder.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
});
var updatedCipher = await cipherRepository.GetByIdAsync(createdCipher.Id);
Assert.Null(updatedCipher.UserId);
Assert.Equal(organization.Id, updatedCipher.OrganizationId);
Assert.NotNull(updatedCipher.Folders);
using var foldersJsonDocument = JsonDocument.Parse(updatedCipher.Folders);
var foldersJsonElement = foldersJsonDocument.RootElement;
Assert.Equal(JsonValueKind.Object, foldersJsonElement.ValueKind);
// TODO: Should we force similar casing for guids across DB's
// I'd rather we only interact with them as the actual Guid type
var userProperty = foldersJsonElement
.EnumerateObject()
.FirstOrDefault(jp => string.Equals(jp.Name, user.Id.ToString(), StringComparison.OrdinalIgnoreCase));
Assert.NotEqual(default, userProperty);
Assert.Equal(folder.Id, userProperty.Value.GetGuid());
}
} }

View File

@@ -2901,7 +2901,7 @@
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -2909,7 +2909,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -3130,7 +3130,7 @@
"dependencies": { "dependencies": {
"AutoFixture.AutoNSubstitute": "4.17.0", "AutoFixture.AutoNSubstitute": "4.17.0",
"AutoFixture.Xunit2": "4.17.0", "AutoFixture.Xunit2": "4.17.0",
"Core": "2023.4.3", "Core": "2023.5.1",
"Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0", "Kralizek.AutoFixture.Extensions.MockHttp": "1.2.0",
"Microsoft.NET.Test.Sdk": "17.1.0", "Microsoft.NET.Test.Sdk": "17.1.0",
"NSubstitute": "4.3.0", "NSubstitute": "4.3.0",
@@ -3182,15 +3182,15 @@
"identity": { "identity": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"SharedWeb": "2023.4.3", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore.SwaggerGen": "6.5.0" "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Dapper": "2.0.123" "Dapper": "2.0.123"
} }
}, },
@@ -3198,7 +3198,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
@@ -3210,9 +3210,9 @@
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Infrastructure.Dapper": "2023.4.3", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "2023.4.3" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -0,0 +1,25 @@
IF OBJECT_ID('[dbo].[AuthRequest_DeleteIfExpired]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[AuthRequest_DeleteIfExpired]
END
GO
-- UserExpirationSeconds to 15 minutes (15 * 60)
-- AdminExpirationSeconds to 7 days (7 * 24 * 60 * 60)
-- AdminApprovalExpirationSeconds to 12 hour (12 * 60 * 60)
CREATE PROCEDURE [dbo].[AuthRequest_DeleteIfExpired]
@UserExpirationSeconds INT = 900,
@AdminExpirationSeconds INT = 604800,
@AdminApprovalExpirationSeconds INT = 43200
AS
BEGIN
SET NOCOUNT OFF
DELETE FROM [dbo].[AuthRequest]
-- User requests expire after 15 minutes (by default) of their creation
WHERE ([Type] != 2 AND DATEADD(second, @UserExpirationSeconds, [CreationDate]) < GETUTCDATE())
-- Admin requests expire after 7 days (by default) of their creation if they have not been approved
OR ([Type] = 2 AND ([Approved] IS NULL OR [Approved] = 0) AND DATEADD(second, @AdminExpirationSeconds,[CreationDate]) < GETUTCDATE())
-- Admin requests expire after 12 hours (by default) of their approval
OR ([Type] = 2 AND [Approved] = 1 AND DATEADD(second, @AdminApprovalExpirationSeconds, [ResponseDate]) < GETUTCDATE());
END

View File

@@ -2729,7 +2729,7 @@
"migrator": { "migrator": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.Extensions.Logging": "6.0.0", "Microsoft.Extensions.Logging": "6.0.0",
"dbup-sqlserver": "5.0.8" "dbup-sqlserver": "5.0.8"
} }

View File

@@ -2797,7 +2797,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -2797,7 +2797,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",

View File

@@ -73,6 +73,7 @@ public class EnvironmentFileBuilder
_globalOverrideValues = new Dictionary<string, string> _globalOverrideValues = new Dictionary<string, string>
{ {
["globalSettings__baseServiceUri__vault"] = _context.Config.Url, ["globalSettings__baseServiceUri__vault"] = _context.Config.Url,
["globalSettings__baseServiceUri__cloudVaultRegion"] = "US",
["globalSettings__sqlServer__connectionString"] = $"\"{dbConnectionString.Replace("\"", "\\\"")}\"", ["globalSettings__sqlServer__connectionString"] = $"\"{dbConnectionString.Replace("\"", "\\\"")}\"",
["globalSettings__identityServer__certificatePassword"] = _context.Install?.IdentityCertPassword, ["globalSettings__identityServer__certificatePassword"] = _context.Install?.IdentityCertPassword,
["globalSettings__internalIdentityKey"] = _context.Stub ? "RANDOM_IDENTITY_KEY" : ["globalSettings__internalIdentityKey"] = _context.Stub ? "RANDOM_IDENTITY_KEY" :

View File

@@ -2706,7 +2706,7 @@
"migrator": { "migrator": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.Extensions.Logging": "6.0.0", "Microsoft.Extensions.Logging": "6.0.0",
"dbup-sqlserver": "5.0.8" "dbup-sqlserver": "5.0.8"
} }

View File

@@ -2913,103 +2913,103 @@
"api": { "api": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AspNetCore.HealthChecks.AzureServiceBus": "[6.1.0, )", "AspNetCore.HealthChecks.AzureServiceBus": "6.1.0",
"AspNetCore.HealthChecks.AzureStorage": "[6.1.2, )", "AspNetCore.HealthChecks.AzureStorage": "6.1.2",
"AspNetCore.HealthChecks.Network": "[6.0.4, )", "AspNetCore.HealthChecks.Network": "6.0.4",
"AspNetCore.HealthChecks.Redis": "[6.0.4, )", "AspNetCore.HealthChecks.Redis": "6.0.4",
"AspNetCore.HealthChecks.SendGrid": "[6.0.2, )", "AspNetCore.HealthChecks.SendGrid": "6.0.2",
"AspNetCore.HealthChecks.SqlServer": "[6.0.2, )", "AspNetCore.HealthChecks.SqlServer": "6.0.2",
"AspNetCore.HealthChecks.Uris": "[6.0.3, )", "AspNetCore.HealthChecks.Uris": "6.0.3",
"Azure.Messaging.EventGrid": "[4.10.0, )", "Azure.Messaging.EventGrid": "4.10.0",
"Commercial.Core": "[2023.5.0, )", "Commercial.Core": "2023.5.1",
"Commercial.Infrastructure.EntityFramework": "[2023.5.0, )", "Commercial.Infrastructure.EntityFramework": "2023.5.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"SharedWeb": "[2023.5.0, )", "SharedWeb": "2023.5.1",
"Swashbuckle.AspNetCore": "[6.5.0, )" "Swashbuckle.AspNetCore": "6.5.0"
} }
}, },
"commercial.core": { "commercial.core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )" "Core": "2023.5.1"
} }
}, },
"commercial.infrastructure.entityframework": { "commercial.infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
}, },
"core": { "core": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AWSSDK.SQS": "[3.7.2.47, )", "AWSSDK.SQS": "3.7.2.47",
"AWSSDK.SimpleEmail": "[3.7.0.150, )", "AWSSDK.SimpleEmail": "3.7.0.150",
"AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit": "4.0.2",
"AspNetCoreRateLimit.Redis": "[1.0.1, )", "AspNetCoreRateLimit.Redis": "1.0.1",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.3.2, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "1.3.2",
"Azure.Storage.Blobs": "[12.14.1, )", "Azure.Storage.Blobs": "12.14.1",
"Azure.Storage.Queues": "[12.12.0, )", "Azure.Storage.Queues": "12.12.0",
"BitPay.Light": "[1.0.1907, )", "BitPay.Light": "1.0.1907",
"Braintree": "[5.12.0, )", "Braintree": "5.12.0",
"DnsClient": "[1.7.0, )", "DnsClient": "1.7.0",
"Fido2.AspNet": "[3.0.1, )", "Fido2.AspNet": "3.0.1",
"Handlebars.Net": "[2.1.2, )", "Handlebars.Net": "2.1.2",
"IdentityServer4": "[4.1.2, )", "IdentityServer4": "4.1.2",
"IdentityServer4.AccessTokenValidation": "[3.0.1, )", "IdentityServer4.AccessTokenValidation": "3.0.1",
"LaunchDarkly.ServerSdk": "[7.0.0, )", "LaunchDarkly.ServerSdk": "7.0.0",
"MailKit": "[3.2.0, )", "MailKit": "3.2.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "[6.0.4, )", "Microsoft.AspNetCore.Authentication.JwtBearer": "6.0.4",
"Microsoft.Azure.Cosmos.Table": "[1.0.8, )", "Microsoft.Azure.Cosmos.Table": "1.0.8",
"Microsoft.Azure.NotificationHubs": "[4.1.0, )", "Microsoft.Azure.NotificationHubs": "4.1.0",
"Microsoft.Azure.ServiceBus": "[5.2.0, )", "Microsoft.Azure.ServiceBus": "5.2.0",
"Microsoft.Data.SqlClient": "[5.0.1, )", "Microsoft.Data.SqlClient": "5.0.1",
"Microsoft.Extensions.Caching.StackExchangeRedis": "[6.0.6, )", "Microsoft.Extensions.Caching.StackExchangeRedis": "6.0.6",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "[6.0.1, )", "Microsoft.Extensions.Configuration.EnvironmentVariables": "6.0.1",
"Microsoft.Extensions.Configuration.UserSecrets": "[6.0.1, )", "Microsoft.Extensions.Configuration.UserSecrets": "6.0.1",
"Microsoft.Extensions.Identity.Stores": "[6.0.4, )", "Microsoft.Extensions.Identity.Stores": "6.0.4",
"Newtonsoft.Json": "[13.0.1, )", "Newtonsoft.Json": "13.0.1",
"Otp.NET": "[1.2.2, )", "Otp.NET": "1.2.2",
"Quartz": "[3.4.0, )", "Quartz": "3.4.0",
"SendGrid": "[9.27.0, )", "SendGrid": "9.27.0",
"Sentry.Serilog": "[3.16.0, )", "Sentry.Serilog": "3.16.0",
"Serilog.AspNetCore": "[5.0.0, )", "Serilog.AspNetCore": "5.0.0",
"Serilog.Extensions.Logging": "[3.1.0, )", "Serilog.Extensions.Logging": "3.1.0",
"Serilog.Extensions.Logging.File": "[2.0.0, )", "Serilog.Extensions.Logging.File": "2.0.0",
"Serilog.Sinks.AzureCosmosDB": "[2.0.0, )", "Serilog.Sinks.AzureCosmosDB": "2.0.0",
"Serilog.Sinks.SyslogMessages": "[2.0.6, )", "Serilog.Sinks.SyslogMessages": "2.0.6",
"Stripe.net": "[40.0.0, )", "Stripe.net": "40.0.0",
"YubicoDotNetClient": "[1.2.0, )" "YubicoDotNetClient": "1.2.0"
} }
}, },
"infrastructure.dapper": { "infrastructure.dapper": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Dapper": "[2.0.123, )" "Dapper": "2.0.123"
} }
}, },
"infrastructure.entityframework": { "infrastructure.entityframework": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",
"Npgsql.EntityFrameworkCore.PostgreSQL": "[6.0.8, )", "Npgsql.EntityFrameworkCore.PostgreSQL": "6.0.8",
"Pomelo.EntityFrameworkCore.MySql": "[6.0.2, )", "Pomelo.EntityFrameworkCore.MySql": "6.0.2",
"linq2db.EntityFrameworkCore": "[6.11.0, )" "linq2db.EntityFrameworkCore": "6.11.0"
} }
}, },
"sharedweb": { "sharedweb": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"Core": "[2023.5.0, )", "Core": "2023.5.1",
"Infrastructure.Dapper": "[2023.5.0, )", "Infrastructure.Dapper": "2023.5.1",
"Infrastructure.EntityFramework": "[2023.5.0, )" "Infrastructure.EntityFramework": "2023.5.1"
} }
} }
} }

View File

@@ -2797,7 +2797,7 @@
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1", "AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.1",
"Core": "2023.4.3", "Core": "2023.5.1",
"Microsoft.EntityFrameworkCore.Relational": "6.0.12", "Microsoft.EntityFrameworkCore.Relational": "6.0.12",
"Microsoft.EntityFrameworkCore.SqlServer": "6.0.12", "Microsoft.EntityFrameworkCore.SqlServer": "6.0.12",
"Microsoft.EntityFrameworkCore.Sqlite": "6.0.12", "Microsoft.EntityFrameworkCore.Sqlite": "6.0.12",