mirror of
https://github.com/bitwarden/server.git
synced 2025-02-08 00:31:27 +01:00
Merge branch 'main' into km/userkey-rotation-v2
This commit is contained in:
commit
80f8780109
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -71,6 +71,7 @@ src/Admin/Views/Tools @bitwarden/team-billing-dev
|
|||||||
.github/workflows/repository-management.yml @bitwarden/team-platform-dev
|
.github/workflows/repository-management.yml @bitwarden/team-platform-dev
|
||||||
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
||||||
.github/workflows/test.yml @bitwarden/team-platform-dev
|
.github/workflows/test.yml @bitwarden/team-platform-dev
|
||||||
|
**/*Platform* @bitwarden/team-platform-dev
|
||||||
|
|
||||||
# Multiple owners - DO NOT REMOVE (BRE)
|
# Multiple owners - DO NOT REMOVE (BRE)
|
||||||
**/packages.lock.json
|
**/packages.lock.json
|
||||||
|
34
.github/workflows/build.yml
vendored
34
.github/workflows/build.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Verify format
|
- name: Verify format
|
||||||
run: dotnet format --verify-no-changes
|
run: dotnet format --verify-no-changes
|
||||||
@ -81,7 +81,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
@ -120,7 +120,7 @@ jobs:
|
|||||||
ls -atlh ../../../
|
ls -atlh ../../../
|
||||||
|
|
||||||
- name: Upload project artifact
|
- name: Upload project artifact
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
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
|
||||||
@ -278,7 +278,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
id: build-docker
|
id: build-docker
|
||||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0
|
||||||
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
|
||||||
@ -307,14 +307,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Scan Docker image
|
- name: Scan Docker image
|
||||||
id: container-scan
|
id: container-scan
|
||||||
uses: anchore/scan-action@5ed195cc06065322983cae4bb31e2a751feb86fd # v5.2.0
|
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 # v6.0.0
|
||||||
with:
|
with:
|
||||||
image: ${{ steps.image-tags.outputs.primary_tag }}
|
image: ${{ steps.image-tags.outputs.primary_tag }}
|
||||||
fail-build: false
|
fail-build: false
|
||||||
output-format: sarif
|
output-format: sarif
|
||||||
|
|
||||||
- name: Upload Grype results to GitHub
|
- name: Upload Grype results to GitHub
|
||||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{ steps.container-scan.outputs.sarif }}
|
sarif_file: ${{ steps.container-scan.outputs.sarif }}
|
||||||
|
|
||||||
@ -329,7 +329,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Log in to Azure - production subscription
|
- name: Log in to Azure - production subscription
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
@ -393,7 +393,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event_name != 'pull_request_target'
|
github.event_name != 'pull_request_target'
|
||||||
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: docker-stub-US.zip
|
name: docker-stub-US.zip
|
||||||
path: docker-stub-US.zip
|
path: docker-stub-US.zip
|
||||||
@ -403,7 +403,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event_name != 'pull_request_target'
|
github.event_name != 'pull_request_target'
|
||||||
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: docker-stub-EU.zip
|
name: docker-stub-EU.zip
|
||||||
path: docker-stub-EU.zip
|
path: docker-stub-EU.zip
|
||||||
@ -413,7 +413,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event_name != 'pull_request_target'
|
github.event_name != 'pull_request_target'
|
||||||
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: docker-stub-US-sha256.txt
|
name: docker-stub-US-sha256.txt
|
||||||
path: docker-stub-US-sha256.txt
|
path: docker-stub-US-sha256.txt
|
||||||
@ -423,7 +423,7 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event_name != 'pull_request_target'
|
github.event_name != 'pull_request_target'
|
||||||
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: docker-stub-EU-sha256.txt
|
name: docker-stub-EU-sha256.txt
|
||||||
path: docker-stub-EU-sha256.txt
|
path: docker-stub-EU-sha256.txt
|
||||||
@ -447,7 +447,7 @@ jobs:
|
|||||||
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
||||||
|
|
||||||
- name: Upload Public API Swagger artifact
|
- name: Upload Public API Swagger artifact
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: swagger.json
|
name: swagger.json
|
||||||
path: swagger.json
|
path: swagger.json
|
||||||
@ -481,14 +481,14 @@ jobs:
|
|||||||
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
||||||
|
|
||||||
- name: Upload Internal API Swagger artifact
|
- name: Upload Internal API Swagger artifact
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: internal.json
|
name: internal.json
|
||||||
path: internal.json
|
path: internal.json
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Identity Swagger artifact
|
- name: Upload Identity Swagger artifact
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: identity.json
|
name: identity.json
|
||||||
path: identity.json
|
path: identity.json
|
||||||
@ -517,7 +517,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@ -533,7 +533,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload project artifact for Windows
|
- name: Upload project artifact for Windows
|
||||||
if: ${{ contains(matrix.target, 'win') == true }}
|
if: ${{ contains(matrix.target, 'win') == true }}
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
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
|
||||||
@ -541,7 +541,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
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
|
||||||
|
2
.github/workflows/code-references.yml
vendored
2
.github/workflows/code-references.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Collect
|
- name: Collect
|
||||||
id: collect
|
id: collect
|
||||||
uses: launchdarkly/find-code-references-in-pull-request@d008aa4f321d8cd35314d9cb095388dcfde84439 # v2.0.0
|
uses: launchdarkly/find-code-references-in-pull-request@b2d44bb453e13c11fd1a6ada7b1e5f9fb0ace629 # v2.0.1
|
||||||
with:
|
with:
|
||||||
project-key: default
|
project-key: default
|
||||||
environment-key: dev
|
environment-key: dev
|
||||||
|
6
.github/workflows/repository-management.yml
vendored
6
.github/workflows/repository-management.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Generate GH App token
|
- name: Generate GH App token
|
||||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||||
@ -98,7 +98,7 @@ jobs:
|
|||||||
version: ${{ inputs.version_number_override }}
|
version: ${{ inputs.version_number_override }}
|
||||||
|
|
||||||
- name: Generate GH App token
|
- name: Generate GH App token
|
||||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||||
@ -197,7 +197,7 @@ jobs:
|
|||||||
- setup
|
- setup
|
||||||
steps:
|
steps:
|
||||||
- name: Generate GH App token
|
- name: Generate GH App token
|
||||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||||
|
8
.github/workflows/scan.yml
vendored
8
.github/workflows/scan.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Scan with Checkmarx
|
- name: Scan with Checkmarx
|
||||||
uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36
|
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
|
||||||
env:
|
env:
|
||||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||||
with:
|
with:
|
||||||
@ -46,7 +46,7 @@ jobs:
|
|||||||
--output-path . ${{ env.INCREMENTAL }}
|
--output-path . ${{ env.INCREMENTAL }}
|
||||||
|
|
||||||
- name: Upload Checkmarx results to GitHub
|
- name: Upload Checkmarx results to GitHub
|
||||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
uses: github/codeql-action/upload-sarif@48ab28a6f5dbc2a99bf1e0131198dd8f1df78169 # v3.28.0
|
||||||
with:
|
with:
|
||||||
sarif_file: cx_result.sarif
|
sarif_file: cx_result.sarif
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
|
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
@ -72,7 +72,7 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Install SonarCloud scanner
|
- name: Install SonarCloud scanner
|
||||||
run: dotnet tool install dotnet-sonarscanner -g
|
run: dotnet tool install dotnet-sonarscanner -g
|
||||||
|
12
.github/workflows/test-database.yml
vendored
12
.github/workflows/test-database.yml
vendored
@ -57,7 +57,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Restore tools
|
- name: Restore tools
|
||||||
run: dotnet tool restore
|
run: dotnet tool restore
|
||||||
@ -107,7 +107,7 @@ jobs:
|
|||||||
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"'
|
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"'
|
||||||
env:
|
env:
|
||||||
CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true"
|
CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true"
|
||||||
|
|
||||||
- name: Migrate MariaDB
|
- name: Migrate MariaDB
|
||||||
working-directory: "util/MySqlMigrations"
|
working-directory: "util/MySqlMigrations"
|
||||||
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"'
|
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"'
|
||||||
@ -186,7 +186,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@ -200,7 +200,7 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Upload DACPAC
|
- name: Upload DACPAC
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: sql.dacpac
|
name: sql.dacpac
|
||||||
path: Sql.dacpac
|
path: Sql.dacpac
|
||||||
@ -226,7 +226,7 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Report validation results
|
- name: Report validation results
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
|
||||||
with:
|
with:
|
||||||
name: report.xml
|
name: report.xml
|
||||||
path: |
|
path: |
|
||||||
@ -237,7 +237,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if grep -q "<Operations>" "report.xml"; then
|
if grep -q "<Operations>" "report.xml"; then
|
||||||
echo
|
echo
|
||||||
echo "Migrations are out of sync with sqlproj!"
|
echo "Migration files are not in sync with the files in the Sql project. Review to make sure that any stored procedures / other db changes match with the stored procedures in the Sql project."
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "Report looks good"
|
echo "Report looks good"
|
||||||
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
@ -77,7 +77,7 @@ jobs:
|
|||||||
fail-on-error: true
|
fail-on-error: true
|
||||||
|
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||||
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
|
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
|
||||||
env:
|
env:
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<Version>2025.1.0</Version>
|
<Version>2025.1.1</Version>
|
||||||
|
|
||||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@ -64,4 +64,4 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
@ -32,7 +32,8 @@ public class ProviderBillingService(
|
|||||||
IProviderOrganizationRepository providerOrganizationRepository,
|
IProviderOrganizationRepository providerOrganizationRepository,
|
||||||
IProviderPlanRepository providerPlanRepository,
|
IProviderPlanRepository providerPlanRepository,
|
||||||
IStripeAdapter stripeAdapter,
|
IStripeAdapter stripeAdapter,
|
||||||
ISubscriberService subscriberService) : IProviderBillingService
|
ISubscriberService subscriberService,
|
||||||
|
ITaxService taxService) : IProviderBillingService
|
||||||
{
|
{
|
||||||
public async Task ChangePlan(ChangeProviderPlanCommand command)
|
public async Task ChangePlan(ChangeProviderPlanCommand command)
|
||||||
{
|
{
|
||||||
@ -335,14 +336,30 @@ public class ProviderBillingService(
|
|||||||
Metadata = new Dictionary<string, string>
|
Metadata = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "region", globalSettings.BaseServiceUri.CloudRegion }
|
{ "region", globalSettings.BaseServiceUri.CloudRegion }
|
||||||
},
|
}
|
||||||
TaxIdData = taxInfo.HasTaxId ?
|
|
||||||
[
|
|
||||||
new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber }
|
|
||||||
]
|
|
||||||
: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(taxInfo.TaxIdNumber))
|
||||||
|
{
|
||||||
|
var taxIdType = taxService.GetStripeTaxCode(taxInfo.BillingAddressCountry,
|
||||||
|
taxInfo.TaxIdNumber);
|
||||||
|
|
||||||
|
if (taxIdType == null)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||||
|
taxInfo.BillingAddressCountry,
|
||||||
|
taxInfo.TaxIdNumber);
|
||||||
|
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
||||||
|
}
|
||||||
|
|
||||||
|
customerCreateOptions.TaxIdData = taxInfo.HasTaxId
|
||||||
|
?
|
||||||
|
[
|
||||||
|
new CustomerTaxIdDataOptions { Type = taxIdType, Value = taxInfo.TaxIdNumber }
|
||||||
|
]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
return await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||||
|
120
bitwarden_license/src/Sso/package-lock.json
generated
120
bitwarden_license/src/Sso/package-lock.json
generated
@ -779,9 +779,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.2",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
|
||||||
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -799,9 +799,9 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001669",
|
"caniuse-lite": "^1.0.30001688",
|
||||||
"electron-to-chromium": "^1.5.41",
|
"electron-to-chromium": "^1.5.73",
|
||||||
"node-releases": "^2.0.18",
|
"node-releases": "^2.0.19",
|
||||||
"update-browserslist-db": "^1.1.1"
|
"update-browserslist-db": "^1.1.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -819,9 +819,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001688",
|
"version": "1.0.30001690",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
|
||||||
"integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==",
|
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -840,9 +840,9 @@
|
|||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -972,16 +972,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.73",
|
"version": "1.5.75",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz",
|
||||||
"integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==",
|
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.17.1",
|
"version": "5.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1271,9 +1271,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1792,19 +1792,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"resolve": "bin/resolve"
|
"resolve": "bin/resolve"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@ -2082,17 +2085,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin": {
|
"node_modules/terser-webpack-plugin": {
|
||||||
"version": "5.3.10",
|
"version": "5.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
|
||||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
"integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/trace-mapping": "^0.3.20",
|
"@jridgewell/trace-mapping": "^0.3.25",
|
||||||
"jest-worker": "^27.4.5",
|
"jest-worker": "^27.4.5",
|
||||||
"schema-utils": "^3.1.1",
|
"schema-utils": "^4.3.0",
|
||||||
"serialize-javascript": "^6.0.1",
|
"serialize-javascript": "^6.0.2",
|
||||||
"terser": "^5.26.0"
|
"terser": "^5.31.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.13.0"
|
"node": ">= 10.13.0"
|
||||||
@ -2116,59 +2119,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
|
||||||
"version": "6.12.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.1",
|
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
|
||||||
"json-schema-traverse": "^0.4.1",
|
|
||||||
"uri-js": "^4.2.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
|
|
||||||
"version": "3.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^6.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/json-schema": "^7.0.8",
|
|
||||||
"ajv": "^6.12.5",
|
|
||||||
"ajv-keywords": "^3.5.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/webpack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
@ -746,6 +746,12 @@ public class ProviderBillingServiceTests
|
|||||||
{
|
{
|
||||||
provider.Name = "MSP";
|
provider.Name = "MSP";
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITaxService>()
|
||||||
|
.GetStripeTaxCode(Arg.Is<string>(
|
||||||
|
p => p == taxInfo.BillingAddressCountry),
|
||||||
|
Arg.Is<string>(p => p == taxInfo.TaxIdNumber))
|
||||||
|
.Returns(taxInfo.TaxIdType);
|
||||||
|
|
||||||
taxInfo.BillingAddressCountry = "AD";
|
taxInfo.BillingAddressCountry = "AD";
|
||||||
|
|
||||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||||
@ -777,6 +783,29 @@ public class ProviderBillingServiceTests
|
|||||||
Assert.Equivalent(expected, actual);
|
Assert.Equivalent(expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SetupCustomer_Throws_BadRequestException_WhenTaxIdIsInvalid(
|
||||||
|
SutProvider<ProviderBillingService> sutProvider,
|
||||||
|
Provider provider,
|
||||||
|
TaxInfo taxInfo)
|
||||||
|
{
|
||||||
|
provider.Name = "MSP";
|
||||||
|
|
||||||
|
taxInfo.BillingAddressCountry = "AD";
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITaxService>()
|
||||||
|
.GetStripeTaxCode(Arg.Is<string>(
|
||||||
|
p => p == taxInfo.BillingAddressCountry),
|
||||||
|
Arg.Is<string>(p => p == taxInfo.TaxIdNumber))
|
||||||
|
.Returns((string)null);
|
||||||
|
|
||||||
|
var actual = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||||
|
await sutProvider.Sut.SetupCustomer(provider, taxInfo));
|
||||||
|
|
||||||
|
Assert.IsType<BadRequestException>(actual);
|
||||||
|
Assert.Equal("billingTaxIdTypeInferenceError", actual.Message);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region SetupSubscription
|
#region SetupSubscription
|
||||||
|
@ -3,7 +3,6 @@ using Bit.Admin.AdminConsole.Models;
|
|||||||
using Bit.Admin.Enums;
|
using Bit.Admin.Enums;
|
||||||
using Bit.Admin.Services;
|
using Bit.Admin.Services;
|
||||||
using Bit.Admin.Utilities;
|
using Bit.Admin.Utilities;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||||
@ -476,14 +475,6 @@ public class OrganizationsController : Controller
|
|||||||
Organization organization,
|
Organization organization,
|
||||||
OrganizationEditModel update)
|
OrganizationEditModel update)
|
||||||
{
|
{
|
||||||
var scaleMSPOnClientOrganizationUpdate =
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate);
|
|
||||||
|
|
||||||
if (!scaleMSPOnClientOrganizationUpdate)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
|
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
|
||||||
|
|
||||||
// No scaling required
|
// No scaling required
|
||||||
|
@ -10,4 +10,6 @@ public class OrganizationsModel : PagedModel<Organization>
|
|||||||
public bool? Paid { get; set; }
|
public bool? Paid { get; set; }
|
||||||
public string Action { get; set; }
|
public string Action { get; set; }
|
||||||
public bool SelfHosted { get; set; }
|
public bool SelfHosted { get; set; }
|
||||||
|
|
||||||
|
public double StorageGB(Organization org) => org.Storage.HasValue ? Math.Round(org.Storage.Value / 1073741824D, 2) : 0;
|
||||||
}
|
}
|
||||||
|
@ -81,16 +81,7 @@
|
|||||||
<i class="fa fa-smile-o fa-lg fa-fw text-body-secondary" title="Freeloader"></i>
|
<i class="fa fa-smile-o fa-lg fa-fw text-body-secondary" title="Freeloader"></i>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if(org.MaxStorageGb.HasValue && org.MaxStorageGb > 1)
|
<i class="fa fa-hdd-o fa-lg fa-fw" title="Used Storage, @Model.StorageGB(org) GB"></i>
|
||||||
{
|
|
||||||
<i class="fa fa-plus-square fa-lg fa-fw"
|
|
||||||
title="Additional Storage, @(org.MaxStorageGb - 1) GB"></i>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<i class="fa fa-plus-square-o fa-lg fa-fw text-body-secondary"
|
|
||||||
title="No Additional Storage"></i>
|
|
||||||
}
|
|
||||||
@if(org.Enabled)
|
@if(org.Enabled)
|
||||||
{
|
{
|
||||||
<i class="fa fa-check-circle fa-lg fa-fw"
|
<i class="fa fa-check-circle fa-lg fa-fw"
|
||||||
|
@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Models.BitStripe;
|
using Bit.Core.Models.BitStripe;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||||
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
@ -92,7 +92,7 @@
|
|||||||
@if (canPromoteAdmin)
|
@if (canPromoteAdmin)
|
||||||
{
|
{
|
||||||
<a class="dropdown-item" asp-controller="Tools" asp-action="PromoteAdmin">
|
<a class="dropdown-item" asp-controller="Tools" asp-action="PromoteAdmin">
|
||||||
Promote Admin
|
Promote Organization Admin
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
@if (canPromoteProviderServiceUser)
|
@if (canPromoteProviderServiceUser)
|
||||||
|
120
src/Admin/package-lock.json
generated
120
src/Admin/package-lock.json
generated
@ -780,9 +780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.24.2",
|
"version": "4.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
|
||||||
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -800,9 +800,9 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001669",
|
"caniuse-lite": "^1.0.30001688",
|
||||||
"electron-to-chromium": "^1.5.41",
|
"electron-to-chromium": "^1.5.73",
|
||||||
"node-releases": "^2.0.18",
|
"node-releases": "^2.0.19",
|
||||||
"update-browserslist-db": "^1.1.1"
|
"update-browserslist-db": "^1.1.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -820,9 +820,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001688",
|
"version": "1.0.30001690",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
|
||||||
"integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==",
|
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -841,9 +841,9 @@
|
|||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -973,16 +973,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.73",
|
"version": "1.5.75",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz",
|
||||||
"integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==",
|
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.17.1",
|
"version": "5.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1272,9 +1272,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1793,19 +1793,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"resolve": "bin/resolve"
|
"resolve": "bin/resolve"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@ -2083,17 +2086,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin": {
|
"node_modules/terser-webpack-plugin": {
|
||||||
"version": "5.3.10",
|
"version": "5.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
|
||||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
"integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/trace-mapping": "^0.3.20",
|
"@jridgewell/trace-mapping": "^0.3.25",
|
||||||
"jest-worker": "^27.4.5",
|
"jest-worker": "^27.4.5",
|
||||||
"schema-utils": "^3.1.1",
|
"schema-utils": "^4.3.0",
|
||||||
"serialize-javascript": "^6.0.1",
|
"serialize-javascript": "^6.0.2",
|
||||||
"terser": "^5.26.0"
|
"terser": "^5.31.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.13.0"
|
"node": ">= 10.13.0"
|
||||||
@ -2117,59 +2120,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
|
||||||
"version": "6.12.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.1",
|
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
|
||||||
"json-schema-traverse": "^0.4.1",
|
|
||||||
"uri-js": "^4.2.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
|
|
||||||
"version": "3.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"ajv": "^6.9.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
|
||||||
"version": "0.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/json-schema": "^7.0.8",
|
|
||||||
"ajv": "^6.12.5",
|
|
||||||
"ajv-keywords": "^3.5.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.13.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/webpack"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using Bit.Api.AdminConsole.Models.Response;
|
using Bit.Api.AdminConsole.Models.Response;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||||
@ -90,7 +89,7 @@ public class GroupsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroups(Guid orgId)
|
public async Task<ListResponseModel<GroupResponseModel>> GetOrganizationGroups(Guid orgId)
|
||||||
{
|
{
|
||||||
var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll);
|
var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll);
|
||||||
if (!authResult.Succeeded)
|
if (!authResult.Succeeded)
|
||||||
@ -98,24 +97,15 @@ public class GroupsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails))
|
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId);
|
||||||
{
|
var responses = groups.Select(g => new GroupResponseModel(g));
|
||||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId);
|
return new ListResponseModel<GroupResponseModel>(responses);
|
||||||
var responses = groups.Select(g => new GroupDetailsResponseModel(g, []));
|
|
||||||
return new ListResponseModel<GroupDetailsResponseModel>(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupDetails = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId);
|
|
||||||
var detailResponses = groupDetails.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2));
|
|
||||||
return new ListResponseModel<GroupDetailsResponseModel>(detailResponses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("details")]
|
[HttpGet("details")]
|
||||||
public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroupDetails(Guid orgId)
|
public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroupDetails(Guid orgId)
|
||||||
{
|
{
|
||||||
var authResult = _featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails)
|
var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails);
|
||||||
? await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails)
|
|
||||||
: await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll);
|
|
||||||
|
|
||||||
if (!authResult.Succeeded)
|
if (!authResult.Succeeded)
|
||||||
{
|
{
|
||||||
|
@ -43,6 +43,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
|||||||
UserId = organization.UserId;
|
UserId = organization.UserId;
|
||||||
ProviderId = organization.ProviderId;
|
ProviderId = organization.ProviderId;
|
||||||
ProviderName = organization.ProviderName;
|
ProviderName = organization.ProviderName;
|
||||||
|
ProviderType = organization.ProviderType;
|
||||||
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
|
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
|
||||||
LimitCollectionCreation = organization.LimitCollectionCreation;
|
LimitCollectionCreation = organization.LimitCollectionCreation;
|
||||||
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
||||||
|
@ -1023,11 +1023,28 @@ public class AccountsController : Controller
|
|||||||
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("resend-new-device-otp")]
|
[HttpPost("resend-new-device-otp")]
|
||||||
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificatioRequestModel request)
|
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificationRequestModel request)
|
||||||
{
|
{
|
||||||
await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret);
|
await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||||
|
[HttpPost("verify-devices")]
|
||||||
|
[HttpPut("verify-devices")]
|
||||||
|
public async Task SetUserVerifyDevicesAsync([FromBody] SetVerifyDevicesRequestModel request)
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException();
|
||||||
|
|
||||||
|
if (!await _userService.VerifySecretAsync(user, request.Secret))
|
||||||
|
{
|
||||||
|
await Task.Delay(2000);
|
||||||
|
throw new BadRequestException(string.Empty, "User verification failed.");
|
||||||
|
}
|
||||||
|
user.VerifyDevices = request.VerifyDevices;
|
||||||
|
|
||||||
|
await _userService.SaveUserAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
||||||
{
|
{
|
||||||
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||||
|
|
||||||
|
public class SetVerifyDevicesRequestModel : SecretVerificationRequestModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public bool VerifyDevices { get; set; }
|
||||||
|
}
|
@ -3,7 +3,7 @@ using Bit.Core.Utilities;
|
|||||||
|
|
||||||
namespace Bit.Api.Auth.Models.Request.Accounts;
|
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||||
|
|
||||||
public class UnauthenticatedSecretVerificatioRequestModel : SecretVerificationRequestModel
|
public class UnauthenticatedSecretVerificationRequestModel : SecretVerificationRequestModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[StrictEmailAddress]
|
[StrictEmailAddress]
|
@ -1,7 +1,9 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||||
using Bit.Api.Billing.Models.Requests;
|
using Bit.Api.Billing.Models.Requests;
|
||||||
using Bit.Api.Billing.Models.Responses;
|
using Bit.Api.Billing.Models.Responses;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Billing.Models.Sales;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
@ -21,7 +23,8 @@ public class OrganizationBillingController(
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
ISubscriberService subscriberService,
|
ISubscriberService subscriberService,
|
||||||
IPaymentHistoryService paymentHistoryService) : BaseBillingController
|
IPaymentHistoryService paymentHistoryService,
|
||||||
|
IUserService userService) : BaseBillingController
|
||||||
{
|
{
|
||||||
[HttpGet("metadata")]
|
[HttpGet("metadata")]
|
||||||
public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId)
|
public async Task<IResult> GetMetadataAsync([FromRoute] Guid organizationId)
|
||||||
@ -278,4 +281,37 @@ public class OrganizationBillingController(
|
|||||||
|
|
||||||
return TypedResults.Ok();
|
return TypedResults.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("restart-subscription")]
|
||||||
|
public async Task<IResult> RestartSubscriptionAsync([FromRoute] Guid organizationId,
|
||||||
|
[FromBody] OrganizationCreateRequestModel model)
|
||||||
|
{
|
||||||
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
|
||||||
|
{
|
||||||
|
return Error.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await currentContext.EditPaymentMethods(organizationId))
|
||||||
|
{
|
||||||
|
return Error.Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var organization = await organizationRepository.GetByIdAsync(organizationId);
|
||||||
|
|
||||||
|
if (organization == null)
|
||||||
|
{
|
||||||
|
return Error.NotFound();
|
||||||
|
}
|
||||||
|
var organizationSignup = model.ToOrganizationSignup(user);
|
||||||
|
var sale = OrganizationSale.From(organization, organizationSignup);
|
||||||
|
await organizationBillingService.Finalize(sale);
|
||||||
|
|
||||||
|
return TypedResults.Ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ public record OrganizationMetadataResponse(
|
|||||||
bool IsSubscriptionUnpaid,
|
bool IsSubscriptionUnpaid,
|
||||||
bool HasSubscription,
|
bool HasSubscription,
|
||||||
bool HasOpenInvoice,
|
bool HasOpenInvoice,
|
||||||
|
bool IsSubscriptionCanceled,
|
||||||
DateTime? InvoiceDueDate,
|
DateTime? InvoiceDueDate,
|
||||||
DateTime? InvoiceCreatedDate,
|
DateTime? InvoiceCreatedDate,
|
||||||
DateTime? SubPeriodEndDate)
|
DateTime? SubPeriodEndDate)
|
||||||
@ -21,6 +22,7 @@ public record OrganizationMetadataResponse(
|
|||||||
metadata.IsSubscriptionUnpaid,
|
metadata.IsSubscriptionUnpaid,
|
||||||
metadata.HasSubscription,
|
metadata.HasSubscription,
|
||||||
metadata.HasOpenInvoice,
|
metadata.HasOpenInvoice,
|
||||||
|
metadata.IsSubscriptionCanceled,
|
||||||
metadata.InvoiceDueDate,
|
metadata.InvoiceDueDate,
|
||||||
metadata.InvoiceCreatedDate,
|
metadata.InvoiceCreatedDate,
|
||||||
metadata.SubPeriodEndDate);
|
metadata.SubPeriodEndDate);
|
||||||
|
@ -6,7 +6,6 @@ using Bit.Api.Models.Response;
|
|||||||
using Bit.Core.Auth.Models.Api.Request;
|
using Bit.Core.Auth.Models.Api.Request;
|
||||||
using Bit.Core.Auth.Models.Api.Response;
|
using Bit.Core.Auth.Models.Api.Response;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -70,11 +69,17 @@ public class DevicesController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
public async Task<ListResponseModel<DeviceResponseModel>> Get()
|
public async Task<ListResponseModel<DeviceAuthRequestResponseModel>> Get()
|
||||||
{
|
{
|
||||||
ICollection<Device> devices = await _deviceRepository.GetManyByUserIdAsync(_userService.GetProperUserId(User).Value);
|
var devicesWithPendingAuthData = await _deviceRepository.GetManyByUserIdWithDeviceAuth(_userService.GetProperUserId(User).Value);
|
||||||
var responses = devices.Select(d => new DeviceResponseModel(d));
|
|
||||||
return new ListResponseModel<DeviceResponseModel>(responses);
|
// Convert from DeviceAuthDetails to DeviceAuthRequestResponseModel
|
||||||
|
var deviceAuthRequestResponseList = devicesWithPendingAuthData
|
||||||
|
.Select(DeviceAuthRequestResponseModel.From)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var response = new ListResponseModel<DeviceAuthRequestResponseModel>(deviceAuthRequestResponseList);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("")]
|
[HttpPost("")]
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
using Bit.Api.Models.Request;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Exceptions;
|
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers;
|
namespace Bit.Api.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Routes used to manipulate `Installation` objects: a type used to manage
|
||||||
|
/// a record of a self hosted installation.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This controller is not called from any clients. It's primarily referenced
|
||||||
|
/// in the `Setup` project for creating a new self hosted installation.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso>Bit.Setup.Program</seealso>
|
||||||
[Route("installations")]
|
[Route("installations")]
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[SelfHosted(NotSelfHostedOnly = true)]
|
||||||
public class InstallationsController : Controller
|
public class InstallationsController : Controller
|
@ -1,8 +1,8 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Api.Models.Request;
|
namespace Bit.Api.Platform.Installations;
|
||||||
|
|
||||||
public class InstallationRequestModel
|
public class InstallationRequestModel
|
||||||
{
|
{
|
@ -1,7 +1,7 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
namespace Bit.Api.Models.Response;
|
namespace Bit.Api.Platform.Installations;
|
||||||
|
|
||||||
public class InstallationResponseModel : ResponseModel
|
public class InstallationResponseModel : ResponseModel
|
||||||
{
|
{
|
@ -1,14 +1,18 @@
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers;
|
namespace Bit.Api.Platform.Push;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Routes for push relay: functionality that facilitates communication
|
||||||
|
/// between self hosted organizations and Bitwarden cloud.
|
||||||
|
/// </summary>
|
||||||
[Route("push")]
|
[Route("push")]
|
||||||
[Authorize("Push")]
|
[Authorize("Push")]
|
||||||
[SelfHosted(NotSelfHostedOnly = true)]
|
[SelfHosted(NotSelfHostedOnly = true)]
|
@ -34,6 +34,9 @@ public static class ServiceCollectionExtensions
|
|||||||
Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt")
|
Url = new Uri("https://github.com/bitwarden/server/blob/master/LICENSE.txt")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
config.CustomSchemaIds(type => type.FullName);
|
||||||
|
|
||||||
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
|
config.SwaggerDoc("internal", new OpenApiInfo { Title = "Bitwarden Internal API", Version = "latest" });
|
||||||
|
|
||||||
config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme
|
config.AddSecurityDefinition("oauth2-client-credentials", new OpenApiSecurityScheme
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Enums;
|
using Bit.Core.Tools.Enums;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using Bit.Billing.Constants;
|
using Bit.Billing.Constants;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
@ -4,10 +4,10 @@ namespace Bit.Core.AdminConsole.Enums.Provider;
|
|||||||
|
|
||||||
public enum ProviderType : byte
|
public enum ProviderType : byte
|
||||||
{
|
{
|
||||||
[Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Access to clients organization", Order = 0)]
|
[Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Creates provider portal for client organization management", Order = 0)]
|
||||||
Msp = 0,
|
Msp = 0,
|
||||||
[Display(ShortName = "Reseller", Name = "Reseller", Description = "Access to clients billing", Order = 1000)]
|
[Display(ShortName = "Reseller", Name = "Reseller", Description = "Creates Bitwarden Portal page for client organization billing management", Order = 1000)]
|
||||||
Reseller = 1,
|
Reseller = 1,
|
||||||
[Display(ShortName = "MOE", Name = "Multi-organization Enterprise", Description = "Access to multiple organizations", Order = 1)]
|
[Display(ShortName = "MOE", Name = "Multi-organization Enterprises", Description = "Creates provider portal for multi-organization management", Order = 1)]
|
||||||
MultiOrganizationEnterprise = 2,
|
MultiOrganizationEnterprise = 2,
|
||||||
}
|
}
|
||||||
|
@ -44,4 +44,5 @@ public class ProviderUserOrganizationDetails
|
|||||||
public bool LimitCollectionDeletion { get; set; }
|
public bool LimitCollectionDeletion { get; set; }
|
||||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||||
public bool UseRiskInsights { get; set; }
|
public bool UseRiskInsights { get; set; }
|
||||||
|
public ProviderType ProviderType { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
|||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.Services;
|
using Bit.Core.Auth.Services;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
@ -4,6 +4,7 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Enums;
|
using Bit.Core.Tools.Enums;
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.StaticStore;
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Enums;
|
using Bit.Core.Tools.Enums;
|
||||||
|
@ -27,6 +27,7 @@ using Bit.Core.Models.Data;
|
|||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Models.Mail;
|
using Bit.Core.Models.Mail;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tokens;
|
using Bit.Core.Tokens;
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
namespace Bit.Core.Auth.Enums;
|
namespace Bit.Core.Auth.Enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of auth request.
|
||||||
|
*
|
||||||
|
* Note:
|
||||||
|
* Used by the Device_ReadActiveWithPendingAuthRequestsByUserId.sql stored procedure.
|
||||||
|
* If the enum changes be aware of this reference.
|
||||||
|
*/
|
||||||
public enum AuthRequestType : byte
|
public enum AuthRequestType : byte
|
||||||
{
|
{
|
||||||
AuthenticateAndUnlock = 0,
|
AuthenticateAndUnlock = 0,
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
using Bit.Core.Auth.Models.Data;
|
||||||
|
using Bit.Core.Auth.Utilities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Api;
|
||||||
|
|
||||||
|
namespace Bit.Core.Auth.Models.Api.Response;
|
||||||
|
|
||||||
|
public class DeviceAuthRequestResponseModel : ResponseModel
|
||||||
|
{
|
||||||
|
public DeviceAuthRequestResponseModel()
|
||||||
|
: base("device") { }
|
||||||
|
|
||||||
|
public static DeviceAuthRequestResponseModel From(DeviceAuthDetails deviceAuthDetails)
|
||||||
|
{
|
||||||
|
var converted = new DeviceAuthRequestResponseModel
|
||||||
|
{
|
||||||
|
Id = deviceAuthDetails.Id,
|
||||||
|
Name = deviceAuthDetails.Name,
|
||||||
|
Type = deviceAuthDetails.Type,
|
||||||
|
Identifier = deviceAuthDetails.Identifier,
|
||||||
|
CreationDate = deviceAuthDetails.CreationDate,
|
||||||
|
IsTrusted = deviceAuthDetails.IsTrusted()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreatedAt != null)
|
||||||
|
{
|
||||||
|
converted.DevicePendingAuthRequest = new PendingAuthRequest
|
||||||
|
{
|
||||||
|
Id = (Guid)deviceAuthDetails.AuthRequestId,
|
||||||
|
CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreatedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public DeviceType Type { get; set; }
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public DateTime CreationDate { get; set; }
|
||||||
|
public bool IsTrusted { get; set; }
|
||||||
|
|
||||||
|
public PendingAuthRequest DevicePendingAuthRequest { get; set; }
|
||||||
|
|
||||||
|
public class PendingAuthRequest
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public DateTime CreationDate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
81
src/Core/Auth/Models/Data/DeviceAuthDetails.cs
Normal file
81
src/Core/Auth/Models/Data/DeviceAuthDetails.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
using Bit.Core.Auth.Utilities;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Auth.Models.Data;
|
||||||
|
|
||||||
|
public class DeviceAuthDetails : Device
|
||||||
|
{
|
||||||
|
public bool IsTrusted { get; set; }
|
||||||
|
public Guid? AuthRequestId { get; set; }
|
||||||
|
public DateTime? AuthRequestCreatedAt { get; set; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for EF response.
|
||||||
|
*/
|
||||||
|
public DeviceAuthDetails(
|
||||||
|
Device device,
|
||||||
|
Guid? authRequestId,
|
||||||
|
DateTime? authRequestCreationDate)
|
||||||
|
{
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
Id = device.Id;
|
||||||
|
Name = device.Name;
|
||||||
|
Type = device.Type;
|
||||||
|
Identifier = device.Identifier;
|
||||||
|
CreationDate = device.CreationDate;
|
||||||
|
IsTrusted = device.IsTrusted();
|
||||||
|
AuthRequestId = authRequestId;
|
||||||
|
AuthRequestCreatedAt = authRequestCreationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for dapper response.
|
||||||
|
* Note: if the authRequestId or authRequestCreationDate is null it comes back as
|
||||||
|
* an empty guid and a min value for datetime. That could change if the stored
|
||||||
|
* procedure runs on a different kind of db.
|
||||||
|
*/
|
||||||
|
public DeviceAuthDetails(
|
||||||
|
Guid id,
|
||||||
|
Guid userId,
|
||||||
|
string name,
|
||||||
|
short type,
|
||||||
|
string identifier,
|
||||||
|
string pushToken,
|
||||||
|
DateTime creationDate,
|
||||||
|
DateTime revisionDate,
|
||||||
|
string encryptedUserKey,
|
||||||
|
string encryptedPublicKey,
|
||||||
|
string encryptedPrivateKey,
|
||||||
|
bool active,
|
||||||
|
Guid authRequestId,
|
||||||
|
DateTime authRequestCreationDate)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Type = (DeviceType)type;
|
||||||
|
Identifier = identifier;
|
||||||
|
CreationDate = creationDate;
|
||||||
|
IsTrusted = new Device
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
UserId = userId,
|
||||||
|
Name = name,
|
||||||
|
Type = (DeviceType)type,
|
||||||
|
Identifier = identifier,
|
||||||
|
PushToken = pushToken,
|
||||||
|
RevisionDate = revisionDate,
|
||||||
|
EncryptedUserKey = encryptedUserKey,
|
||||||
|
EncryptedPublicKey = encryptedPublicKey,
|
||||||
|
EncryptedPrivateKey = encryptedPrivateKey,
|
||||||
|
Active = active
|
||||||
|
}.IsTrusted();
|
||||||
|
AuthRequestId = authRequestId != Guid.Empty ? authRequestId : null;
|
||||||
|
AuthRequestCreatedAt =
|
||||||
|
authRequestCreationDate != DateTime.MinValue ? authRequestCreationDate : null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Entities;
|
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Models.Data;
|
namespace Bit.Core.Auth.Models.Data;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
@ -23,7 +23,6 @@ namespace Bit.Core.Auth.UserFeatures.Registration.Implementations;
|
|||||||
|
|
||||||
public class RegisterUserCommand : IRegisterUserCommand
|
public class RegisterUserCommand : IRegisterUserCommand
|
||||||
{
|
{
|
||||||
|
|
||||||
private readonly IGlobalSettings _globalSettings;
|
private readonly IGlobalSettings _globalSettings;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IPolicyRepository _policyRepository;
|
private readonly IPolicyRepository _policyRepository;
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
@ -17,6 +17,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
|
services.AddTransient<IPremiumUserBillingService, PremiumUserBillingService>();
|
||||||
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
|
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
|
||||||
services.AddTransient<ISubscriberService, SubscriberService>();
|
services.AddTransient<ISubscriberService, SubscriberService>();
|
||||||
|
// services.AddSingleton<IPricingClient, PricingClient>();
|
||||||
services.AddLicenseServices();
|
services.AddLicenseServices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,26 +12,42 @@ public class UserLicenseClaimsFactory : ILicenseClaimsFactory<User>
|
|||||||
{
|
{
|
||||||
var subscriptionInfo = licenseContext.SubscriptionInfo;
|
var subscriptionInfo = licenseContext.SubscriptionInfo;
|
||||||
|
|
||||||
var expires = subscriptionInfo.UpcomingInvoice?.Date?.AddDays(7) ?? entity.PremiumExpirationDate?.AddDays(7);
|
var expires = subscriptionInfo?.UpcomingInvoice?.Date?.AddDays(7) ?? entity.PremiumExpirationDate?.AddDays(7);
|
||||||
var refresh = subscriptionInfo.UpcomingInvoice?.Date ?? entity.PremiumExpirationDate;
|
var refresh = subscriptionInfo?.UpcomingInvoice?.Date ?? entity.PremiumExpirationDate;
|
||||||
var trial = (subscriptionInfo.Subscription?.TrialEndDate.HasValue ?? false) &&
|
var trial = (subscriptionInfo?.Subscription?.TrialEndDate.HasValue ?? false) &&
|
||||||
subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow;
|
subscriptionInfo.Subscription.TrialEndDate.Value > DateTime.UtcNow;
|
||||||
|
|
||||||
var claims = new List<Claim>
|
var claims = new List<Claim>
|
||||||
{
|
{
|
||||||
new(nameof(UserLicenseConstants.LicenseType), LicenseType.User.ToString()),
|
new(nameof(UserLicenseConstants.LicenseType), LicenseType.User.ToString()),
|
||||||
new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey),
|
|
||||||
new(nameof(UserLicenseConstants.Id), entity.Id.ToString()),
|
new(nameof(UserLicenseConstants.Id), entity.Id.ToString()),
|
||||||
new(nameof(UserLicenseConstants.Name), entity.Name),
|
new(nameof(UserLicenseConstants.Name), entity.Name),
|
||||||
new(nameof(UserLicenseConstants.Email), entity.Email),
|
new(nameof(UserLicenseConstants.Email), entity.Email),
|
||||||
new(nameof(UserLicenseConstants.Premium), entity.Premium.ToString()),
|
new(nameof(UserLicenseConstants.Premium), entity.Premium.ToString()),
|
||||||
new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString()),
|
|
||||||
new(nameof(UserLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
|
new(nameof(UserLicenseConstants.Issued), DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)),
|
||||||
new(nameof(UserLicenseConstants.Expires), expires.ToString()),
|
|
||||||
new(nameof(UserLicenseConstants.Refresh), refresh.ToString()),
|
|
||||||
new(nameof(UserLicenseConstants.Trial), trial.ToString()),
|
new(nameof(UserLicenseConstants.Trial), trial.ToString()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (entity.LicenseKey is not null)
|
||||||
|
{
|
||||||
|
claims.Add(new(nameof(UserLicenseConstants.LicenseKey), entity.LicenseKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.MaxStorageGb is not null)
|
||||||
|
{
|
||||||
|
claims.Add(new(nameof(UserLicenseConstants.MaxStorageGb), entity.MaxStorageGb.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expires is not null)
|
||||||
|
{
|
||||||
|
claims.Add(new(nameof(UserLicenseConstants.Expires), expires.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh is not null)
|
||||||
|
{
|
||||||
|
claims.Add(new(nameof(UserLicenseConstants.Refresh), refresh.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult(claims);
|
return Task.FromResult(claims);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ public record OrganizationMetadata(
|
|||||||
bool IsSubscriptionUnpaid,
|
bool IsSubscriptionUnpaid,
|
||||||
bool HasSubscription,
|
bool HasSubscription,
|
||||||
bool HasOpenInvoice,
|
bool HasOpenInvoice,
|
||||||
|
bool IsSubscriptionCanceled,
|
||||||
DateTime? InvoiceDueDate,
|
DateTime? InvoiceDueDate,
|
||||||
DateTime? InvoiceCreatedDate,
|
DateTime? InvoiceCreatedDate,
|
||||||
DateTime? SubPeriodEndDate);
|
DateTime? SubPeriodEndDate);
|
||||||
|
@ -8,8 +8,11 @@ public abstract record Plan
|
|||||||
public ProductTierType ProductTier { get; protected init; }
|
public ProductTierType ProductTier { get; protected init; }
|
||||||
public string Name { get; protected init; }
|
public string Name { get; protected init; }
|
||||||
public bool IsAnnual { get; protected init; }
|
public bool IsAnnual { get; protected init; }
|
||||||
|
// TODO: Move to the client
|
||||||
public string NameLocalizationKey { get; protected init; }
|
public string NameLocalizationKey { get; protected init; }
|
||||||
|
// TODO: Move to the client
|
||||||
public string DescriptionLocalizationKey { get; protected init; }
|
public string DescriptionLocalizationKey { get; protected init; }
|
||||||
|
// TODO: Remove
|
||||||
public bool CanBeUsedByBusiness { get; protected init; }
|
public bool CanBeUsedByBusiness { get; protected init; }
|
||||||
public int? TrialPeriodDays { get; protected init; }
|
public int? TrialPeriodDays { get; protected init; }
|
||||||
public bool HasSelfHost { get; protected init; }
|
public bool HasSelfHost { get; protected init; }
|
||||||
@ -27,7 +30,9 @@ public abstract record Plan
|
|||||||
public bool UsersGetPremium { get; protected init; }
|
public bool UsersGetPremium { get; protected init; }
|
||||||
public bool HasCustomPermissions { get; protected init; }
|
public bool HasCustomPermissions { get; protected init; }
|
||||||
public int UpgradeSortOrder { get; protected init; }
|
public int UpgradeSortOrder { get; protected init; }
|
||||||
|
// TODO: Move to the client
|
||||||
public int DisplaySortOrder { get; protected init; }
|
public int DisplaySortOrder { get; protected init; }
|
||||||
|
// TODO: Remove
|
||||||
public int? LegacyYear { get; protected init; }
|
public int? LegacyYear { get; protected init; }
|
||||||
public bool Disabled { get; protected init; }
|
public bool Disabled { get; protected init; }
|
||||||
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
|
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
|
||||||
@ -45,15 +50,19 @@ public abstract record Plan
|
|||||||
public string StripeServiceAccountPlanId { get; init; }
|
public string StripeServiceAccountPlanId { get; init; }
|
||||||
public decimal? AdditionalPricePerServiceAccount { get; init; }
|
public decimal? AdditionalPricePerServiceAccount { get; init; }
|
||||||
public short BaseServiceAccount { get; init; }
|
public short BaseServiceAccount { get; init; }
|
||||||
|
// TODO: Unused, remove
|
||||||
public short? MaxAdditionalServiceAccount { get; init; }
|
public short? MaxAdditionalServiceAccount { get; init; }
|
||||||
public bool HasAdditionalServiceAccountOption { get; init; }
|
public bool HasAdditionalServiceAccountOption { get; init; }
|
||||||
// Seats
|
// Seats
|
||||||
public string StripeSeatPlanId { get; init; }
|
public string StripeSeatPlanId { get; init; }
|
||||||
public bool HasAdditionalSeatsOption { get; init; }
|
public bool HasAdditionalSeatsOption { get; init; }
|
||||||
|
// TODO: Remove, SM is never packaged
|
||||||
public decimal BasePrice { get; init; }
|
public decimal BasePrice { get; init; }
|
||||||
public decimal SeatPrice { get; init; }
|
public decimal SeatPrice { get; init; }
|
||||||
|
// TODO: Remove, SM is never packaged
|
||||||
public int BaseSeats { get; init; }
|
public int BaseSeats { get; init; }
|
||||||
public short? MaxSeats { get; init; }
|
public short? MaxSeats { get; init; }
|
||||||
|
// TODO: Unused, remove
|
||||||
public int? MaxAdditionalSeats { get; init; }
|
public int? MaxAdditionalSeats { get; init; }
|
||||||
public bool AllowSeatAutoscale { get; init; }
|
public bool AllowSeatAutoscale { get; init; }
|
||||||
|
|
||||||
@ -72,8 +81,10 @@ public abstract record Plan
|
|||||||
public decimal ProviderPortalSeatPrice { get; init; }
|
public decimal ProviderPortalSeatPrice { get; init; }
|
||||||
public bool AllowSeatAutoscale { get; init; }
|
public bool AllowSeatAutoscale { get; init; }
|
||||||
public bool HasAdditionalSeatsOption { get; init; }
|
public bool HasAdditionalSeatsOption { get; init; }
|
||||||
|
// TODO: Remove, never set.
|
||||||
public int? MaxAdditionalSeats { get; init; }
|
public int? MaxAdditionalSeats { get; init; }
|
||||||
public int BaseSeats { get; init; }
|
public int BaseSeats { get; init; }
|
||||||
|
// TODO: Remove premium access as it's deprecated
|
||||||
public bool HasPremiumAccessOption { get; init; }
|
public bool HasPremiumAccessOption { get; init; }
|
||||||
public string StripePremiumAccessPlanId { get; init; }
|
public string StripePremiumAccessPlanId { get; init; }
|
||||||
public decimal PremiumAccessOptionPrice { get; init; }
|
public decimal PremiumAccessOptionPrice { get; init; }
|
||||||
@ -83,6 +94,7 @@ public abstract record Plan
|
|||||||
public bool HasAdditionalStorageOption { get; init; }
|
public bool HasAdditionalStorageOption { get; init; }
|
||||||
public decimal AdditionalStoragePricePerGb { get; init; }
|
public decimal AdditionalStoragePricePerGb { get; init; }
|
||||||
public string StripeStoragePlanId { get; init; }
|
public string StripeStoragePlanId { get; init; }
|
||||||
|
// TODO: Remove
|
||||||
public short? MaxAdditionalStorage { get; init; }
|
public short? MaxAdditionalStorage { get; init; }
|
||||||
// Feature
|
// Feature
|
||||||
public short? MaxCollections { get; init; }
|
public short? MaxCollections { get; init; }
|
||||||
|
12
src/Core/Billing/Pricing/IPricingClient.cs
Normal file
12
src/Core/Billing/Pricing/IPricingClient.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.Billing.Pricing;
|
||||||
|
|
||||||
|
public interface IPricingClient
|
||||||
|
{
|
||||||
|
Task<Plan?> GetPlan(PlanType planType);
|
||||||
|
Task<List<Plan>> ListPlans();
|
||||||
|
}
|
232
src/Core/Billing/Pricing/PlanAdapter.cs
Normal file
232
src/Core/Billing/Pricing/PlanAdapter.cs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Proto.Billing.Pricing;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.Billing.Pricing;
|
||||||
|
|
||||||
|
public record PlanAdapter : Plan
|
||||||
|
{
|
||||||
|
public PlanAdapter(PlanResponse planResponse)
|
||||||
|
{
|
||||||
|
Type = ToPlanType(planResponse.LookupKey);
|
||||||
|
ProductTier = ToProductTierType(Type);
|
||||||
|
Name = planResponse.Name;
|
||||||
|
IsAnnual = !string.IsNullOrEmpty(planResponse.Cadence) && planResponse.Cadence == "annually";
|
||||||
|
NameLocalizationKey = planResponse.AdditionalData?["nameLocalizationKey"];
|
||||||
|
DescriptionLocalizationKey = planResponse.AdditionalData?["descriptionLocalizationKey"];
|
||||||
|
TrialPeriodDays = planResponse.TrialPeriodDays;
|
||||||
|
HasSelfHost = HasFeature("selfHost");
|
||||||
|
HasPolicies = HasFeature("policies");
|
||||||
|
HasGroups = HasFeature("groups");
|
||||||
|
HasDirectory = HasFeature("directory");
|
||||||
|
HasEvents = HasFeature("events");
|
||||||
|
HasTotp = HasFeature("totp");
|
||||||
|
Has2fa = HasFeature("2fa");
|
||||||
|
HasApi = HasFeature("api");
|
||||||
|
HasSso = HasFeature("sso");
|
||||||
|
HasKeyConnector = HasFeature("keyConnector");
|
||||||
|
HasScim = HasFeature("scim");
|
||||||
|
HasResetPassword = HasFeature("resetPassword");
|
||||||
|
UsersGetPremium = HasFeature("usersGetPremium");
|
||||||
|
UpgradeSortOrder = planResponse.AdditionalData != null
|
||||||
|
? int.Parse(planResponse.AdditionalData["upgradeSortOrder"])
|
||||||
|
: 0;
|
||||||
|
DisplaySortOrder = planResponse.AdditionalData != null
|
||||||
|
? int.Parse(planResponse.AdditionalData["displaySortOrder"])
|
||||||
|
: 0;
|
||||||
|
HasCustomPermissions = HasFeature("customPermissions");
|
||||||
|
Disabled = !planResponse.Available;
|
||||||
|
PasswordManager = ToPasswordManagerPlanFeatures(planResponse);
|
||||||
|
SecretsManager = planResponse.SecretsManager != null ? ToSecretsManagerPlanFeatures(planResponse) : null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool HasFeature(string lookupKey) => planResponse.Features.Any(feature => feature.LookupKey == lookupKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Mappings
|
||||||
|
|
||||||
|
private static PlanType ToPlanType(string lookupKey)
|
||||||
|
=> lookupKey switch
|
||||||
|
{
|
||||||
|
"enterprise-annually" => PlanType.EnterpriseAnnually,
|
||||||
|
"enterprise-annually-2019" => PlanType.EnterpriseAnnually2019,
|
||||||
|
"enterprise-annually-2020" => PlanType.EnterpriseAnnually2020,
|
||||||
|
"enterprise-annually-2023" => PlanType.EnterpriseAnnually2023,
|
||||||
|
"enterprise-monthly" => PlanType.EnterpriseMonthly,
|
||||||
|
"enterprise-monthly-2019" => PlanType.EnterpriseMonthly2019,
|
||||||
|
"enterprise-monthly-2020" => PlanType.EnterpriseMonthly2020,
|
||||||
|
"enterprise-monthly-2023" => PlanType.EnterpriseMonthly2023,
|
||||||
|
"families" => PlanType.FamiliesAnnually,
|
||||||
|
"families-2019" => PlanType.FamiliesAnnually2019,
|
||||||
|
"free" => PlanType.Free,
|
||||||
|
"teams-annually" => PlanType.TeamsAnnually,
|
||||||
|
"teams-annually-2019" => PlanType.TeamsAnnually2019,
|
||||||
|
"teams-annually-2020" => PlanType.TeamsAnnually2020,
|
||||||
|
"teams-annually-2023" => PlanType.TeamsAnnually2023,
|
||||||
|
"teams-monthly" => PlanType.TeamsMonthly,
|
||||||
|
"teams-monthly-2019" => PlanType.TeamsMonthly2019,
|
||||||
|
"teams-monthly-2020" => PlanType.TeamsMonthly2020,
|
||||||
|
"teams-monthly-2023" => PlanType.TeamsMonthly2023,
|
||||||
|
"teams-starter" => PlanType.TeamsStarter,
|
||||||
|
"teams-starter-2023" => PlanType.TeamsStarter2023,
|
||||||
|
_ => throw new BillingException() // TODO: Flesh out
|
||||||
|
};
|
||||||
|
|
||||||
|
private static ProductTierType ToProductTierType(PlanType planType)
|
||||||
|
=> planType switch
|
||||||
|
{
|
||||||
|
PlanType.Free => ProductTierType.Free,
|
||||||
|
PlanType.FamiliesAnnually or PlanType.FamiliesAnnually2019 => ProductTierType.Families,
|
||||||
|
PlanType.TeamsStarter or PlanType.TeamsStarter2023 => ProductTierType.TeamsStarter,
|
||||||
|
_ when planType.ToString().Contains("Teams") => ProductTierType.Teams,
|
||||||
|
_ when planType.ToString().Contains("Enterprise") => ProductTierType.Enterprise,
|
||||||
|
_ => throw new BillingException() // TODO: Flesh out
|
||||||
|
};
|
||||||
|
|
||||||
|
private static PasswordManagerPlanFeatures ToPasswordManagerPlanFeatures(PlanResponse planResponse)
|
||||||
|
{
|
||||||
|
var stripePlanId = GetStripePlanId(planResponse.Seats);
|
||||||
|
var stripeSeatPlanId = GetStripeSeatPlanId(planResponse.Seats);
|
||||||
|
var stripeProviderPortalSeatPlanId = planResponse.ManagedSeats?.StripePriceId;
|
||||||
|
var basePrice = GetBasePrice(planResponse.Seats);
|
||||||
|
var seatPrice = GetSeatPrice(planResponse.Seats);
|
||||||
|
var providerPortalSeatPrice =
|
||||||
|
planResponse.ManagedSeats != null ? decimal.Parse(planResponse.ManagedSeats.Price) : 0;
|
||||||
|
var scales = planResponse.Seats.KindCase switch
|
||||||
|
{
|
||||||
|
PurchasableDTO.KindOneofCase.Scalable => true,
|
||||||
|
PurchasableDTO.KindOneofCase.Packaged => planResponse.Seats.Packaged.Additional != null,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
var baseSeats = GetBaseSeats(planResponse.Seats);
|
||||||
|
var maxSeats = GetMaxSeats(planResponse.Seats);
|
||||||
|
var baseStorageGb = (short?)planResponse.Storage?.Provided;
|
||||||
|
var hasAdditionalStorageOption = planResponse.Storage != null;
|
||||||
|
var stripeStoragePlanId = planResponse.Storage?.StripePriceId;
|
||||||
|
short? maxCollections =
|
||||||
|
planResponse.AdditionalData != null &&
|
||||||
|
planResponse.AdditionalData.TryGetValue("passwordManager.maxCollections", out var value) ? short.Parse(value) : null;
|
||||||
|
|
||||||
|
return new PasswordManagerPlanFeatures
|
||||||
|
{
|
||||||
|
StripePlanId = stripePlanId,
|
||||||
|
StripeSeatPlanId = stripeSeatPlanId,
|
||||||
|
StripeProviderPortalSeatPlanId = stripeProviderPortalSeatPlanId,
|
||||||
|
BasePrice = basePrice,
|
||||||
|
SeatPrice = seatPrice,
|
||||||
|
ProviderPortalSeatPrice = providerPortalSeatPrice,
|
||||||
|
AllowSeatAutoscale = scales,
|
||||||
|
HasAdditionalSeatsOption = scales,
|
||||||
|
BaseSeats = baseSeats,
|
||||||
|
MaxSeats = maxSeats,
|
||||||
|
BaseStorageGb = baseStorageGb,
|
||||||
|
HasAdditionalStorageOption = hasAdditionalStorageOption,
|
||||||
|
StripeStoragePlanId = stripeStoragePlanId,
|
||||||
|
MaxCollections = maxCollections
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SecretsManagerPlanFeatures ToSecretsManagerPlanFeatures(PlanResponse planResponse)
|
||||||
|
{
|
||||||
|
var seats = planResponse.SecretsManager.Seats;
|
||||||
|
var serviceAccounts = planResponse.SecretsManager.ServiceAccounts;
|
||||||
|
|
||||||
|
var maxServiceAccounts = GetMaxServiceAccounts(serviceAccounts);
|
||||||
|
var allowServiceAccountsAutoscale = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||||
|
var stripeServiceAccountPlanId = GetStripeServiceAccountPlanId(serviceAccounts);
|
||||||
|
var additionalPricePerServiceAccount = GetAdditionalPricePerServiceAccount(serviceAccounts);
|
||||||
|
var baseServiceAccount = GetBaseServiceAccount(serviceAccounts);
|
||||||
|
var hasAdditionalServiceAccountOption = serviceAccounts.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||||
|
var stripeSeatPlanId = GetStripeSeatPlanId(seats);
|
||||||
|
var hasAdditionalSeatsOption = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||||
|
var seatPrice = GetSeatPrice(seats);
|
||||||
|
var maxSeats = GetMaxSeats(seats);
|
||||||
|
var allowSeatAutoscale = seats.KindCase == FreeOrScalableDTO.KindOneofCase.Scalable;
|
||||||
|
var maxProjects =
|
||||||
|
planResponse.AdditionalData != null &&
|
||||||
|
planResponse.AdditionalData.TryGetValue("secretsManager.maxProjects", out var value) ? short.Parse(value) : 0;
|
||||||
|
|
||||||
|
return new SecretsManagerPlanFeatures
|
||||||
|
{
|
||||||
|
MaxServiceAccounts = maxServiceAccounts,
|
||||||
|
AllowServiceAccountsAutoscale = allowServiceAccountsAutoscale,
|
||||||
|
StripeServiceAccountPlanId = stripeServiceAccountPlanId,
|
||||||
|
AdditionalPricePerServiceAccount = additionalPricePerServiceAccount,
|
||||||
|
BaseServiceAccount = baseServiceAccount,
|
||||||
|
HasAdditionalServiceAccountOption = hasAdditionalServiceAccountOption,
|
||||||
|
StripeSeatPlanId = stripeSeatPlanId,
|
||||||
|
HasAdditionalSeatsOption = hasAdditionalSeatsOption,
|
||||||
|
SeatPrice = seatPrice,
|
||||||
|
MaxSeats = maxSeats,
|
||||||
|
AllowSeatAutoscale = allowSeatAutoscale,
|
||||||
|
MaxProjects = maxProjects
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal? GetAdditionalPricePerServiceAccount(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||||
|
? null
|
||||||
|
: decimal.Parse(freeOrScalable.Scalable.Price);
|
||||||
|
|
||||||
|
private static decimal GetBasePrice(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : decimal.Parse(purchasable.Packaged.Price);
|
||||||
|
|
||||||
|
private static int GetBaseSeats(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? 0 : purchasable.Packaged.Quantity;
|
||||||
|
|
||||||
|
private static short GetBaseServiceAccount(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase switch
|
||||||
|
{
|
||||||
|
FreeOrScalableDTO.KindOneofCase.Free => (short)freeOrScalable.Free.Quantity,
|
||||||
|
FreeOrScalableDTO.KindOneofCase.Scalable => (short)freeOrScalable.Scalable.Provided,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
private static short? GetMaxSeats(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Free ? null : (short)purchasable.Free.Quantity;
|
||||||
|
|
||||||
|
private static short? GetMaxSeats(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity;
|
||||||
|
|
||||||
|
private static short? GetMaxServiceAccounts(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Free ? null : (short)freeOrScalable.Free.Quantity;
|
||||||
|
|
||||||
|
private static decimal GetSeatPrice(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase switch
|
||||||
|
{
|
||||||
|
PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional != null ? decimal.Parse(purchasable.Packaged.Additional.Price) : 0,
|
||||||
|
PurchasableDTO.KindOneofCase.Scalable => decimal.Parse(purchasable.Scalable.Price),
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
private static decimal GetSeatPrice(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||||
|
? 0
|
||||||
|
: decimal.Parse(freeOrScalable.Scalable.Price);
|
||||||
|
|
||||||
|
private static string? GetStripePlanId(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase != PurchasableDTO.KindOneofCase.Packaged ? null : purchasable.Packaged.StripePriceId;
|
||||||
|
|
||||||
|
private static string? GetStripeSeatPlanId(PurchasableDTO purchasable)
|
||||||
|
=> purchasable.KindCase switch
|
||||||
|
{
|
||||||
|
PurchasableDTO.KindOneofCase.Packaged => purchasable.Packaged.Additional?.StripePriceId,
|
||||||
|
PurchasableDTO.KindOneofCase.Scalable => purchasable.Scalable.StripePriceId,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string? GetStripeSeatPlanId(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||||
|
? null
|
||||||
|
: freeOrScalable.Scalable.StripePriceId;
|
||||||
|
|
||||||
|
private static string? GetStripeServiceAccountPlanId(FreeOrScalableDTO freeOrScalable)
|
||||||
|
=> freeOrScalable.KindCase != FreeOrScalableDTO.KindOneofCase.Scalable
|
||||||
|
? null
|
||||||
|
: freeOrScalable.Scalable.StripePriceId;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
92
src/Core/Billing/Pricing/PricingClient.cs
Normal file
92
src/Core/Billing/Pricing/PricingClient.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using Proto.Billing.Pricing;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.Billing.Pricing;
|
||||||
|
|
||||||
|
public class PricingClient(
|
||||||
|
IFeatureService featureService,
|
||||||
|
GlobalSettings globalSettings) : IPricingClient
|
||||||
|
{
|
||||||
|
public async Task<Plan?> GetPlan(PlanType planType)
|
||||||
|
{
|
||||||
|
var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService);
|
||||||
|
|
||||||
|
if (!usePricingService)
|
||||||
|
{
|
||||||
|
return StaticStore.GetPlan(planType);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri);
|
||||||
|
var client = new PasswordManager.PasswordManagerClient(channel);
|
||||||
|
|
||||||
|
var lookupKey = ToLookupKey(planType);
|
||||||
|
if (string.IsNullOrEmpty(lookupKey))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response =
|
||||||
|
await client.GetPlanByLookupKeyAsync(new GetPlanByLookupKeyRequest { LookupKey = lookupKey });
|
||||||
|
|
||||||
|
return new PlanAdapter(response);
|
||||||
|
}
|
||||||
|
catch (RpcException rpcException) when (rpcException.StatusCode == StatusCode.NotFound)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Plan>> ListPlans()
|
||||||
|
{
|
||||||
|
var usePricingService = featureService.IsEnabled(FeatureFlagKeys.UsePricingService);
|
||||||
|
|
||||||
|
if (!usePricingService)
|
||||||
|
{
|
||||||
|
return StaticStore.Plans.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var channel = GrpcChannel.ForAddress(globalSettings.PricingUri);
|
||||||
|
var client = new PasswordManager.PasswordManagerClient(channel);
|
||||||
|
|
||||||
|
var response = await client.ListPlansAsync(new Empty());
|
||||||
|
return response.Plans.Select(Plan (plan) => new PlanAdapter(plan)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ToLookupKey(PlanType planType)
|
||||||
|
=> planType switch
|
||||||
|
{
|
||||||
|
PlanType.EnterpriseAnnually => "enterprise-annually",
|
||||||
|
PlanType.EnterpriseAnnually2019 => "enterprise-annually-2019",
|
||||||
|
PlanType.EnterpriseAnnually2020 => "enterprise-annually-2020",
|
||||||
|
PlanType.EnterpriseAnnually2023 => "enterprise-annually-2023",
|
||||||
|
PlanType.EnterpriseMonthly => "enterprise-monthly",
|
||||||
|
PlanType.EnterpriseMonthly2019 => "enterprise-monthly-2019",
|
||||||
|
PlanType.EnterpriseMonthly2020 => "enterprise-monthly-2020",
|
||||||
|
PlanType.EnterpriseMonthly2023 => "enterprise-monthly-2023",
|
||||||
|
PlanType.FamiliesAnnually => "families",
|
||||||
|
PlanType.FamiliesAnnually2019 => "families-2019",
|
||||||
|
PlanType.Free => "free",
|
||||||
|
PlanType.TeamsAnnually => "teams-annually",
|
||||||
|
PlanType.TeamsAnnually2019 => "teams-annually-2019",
|
||||||
|
PlanType.TeamsAnnually2020 => "teams-annually-2020",
|
||||||
|
PlanType.TeamsAnnually2023 => "teams-annually-2023",
|
||||||
|
PlanType.TeamsMonthly => "teams-monthly",
|
||||||
|
PlanType.TeamsMonthly2019 => "teams-monthly-2019",
|
||||||
|
PlanType.TeamsMonthly2020 => "teams-monthly-2020",
|
||||||
|
PlanType.TeamsMonthly2023 => "teams-monthly-2023",
|
||||||
|
PlanType.TeamsStarter => "teams-starter",
|
||||||
|
PlanType.TeamsStarter2023 => "teams-starter-2023",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
92
src/Core/Billing/Pricing/Protos/password-manager.proto
Normal file
92
src/Core/Billing/Pricing/Protos/password-manager.proto
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "Proto.Billing.Pricing";
|
||||||
|
|
||||||
|
package plans;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "google/protobuf/wrappers.proto";
|
||||||
|
|
||||||
|
service PasswordManager {
|
||||||
|
rpc GetPlanByLookupKey (GetPlanByLookupKeyRequest) returns (PlanResponse);
|
||||||
|
rpc ListPlans (google.protobuf.Empty) returns (ListPlansResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requests
|
||||||
|
message GetPlanByLookupKeyRequest {
|
||||||
|
string lookupKey = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responses
|
||||||
|
message PlanResponse {
|
||||||
|
string name = 1;
|
||||||
|
string lookupKey = 2;
|
||||||
|
string tier = 4;
|
||||||
|
optional string cadence = 6;
|
||||||
|
optional google.protobuf.Int32Value legacyYear = 8;
|
||||||
|
bool available = 9;
|
||||||
|
repeated FeatureDTO features = 10;
|
||||||
|
PurchasableDTO seats = 11;
|
||||||
|
optional ScalableDTO managedSeats = 12;
|
||||||
|
optional ScalableDTO storage = 13;
|
||||||
|
optional SecretsManagerPurchasablesDTO secretsManager = 14;
|
||||||
|
optional google.protobuf.Int32Value trialPeriodDays = 15;
|
||||||
|
repeated string canUpgradeTo = 16;
|
||||||
|
map<string, string> additionalData = 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListPlansResponse {
|
||||||
|
repeated PlanResponse plans = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTOs
|
||||||
|
message FeatureDTO {
|
||||||
|
string name = 1;
|
||||||
|
string lookupKey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FreeDTO {
|
||||||
|
int32 quantity = 2;
|
||||||
|
string type = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PackagedDTO {
|
||||||
|
message AdditionalSeats {
|
||||||
|
string stripePriceId = 1;
|
||||||
|
string price = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 quantity = 2;
|
||||||
|
string stripePriceId = 3;
|
||||||
|
string price = 4;
|
||||||
|
optional AdditionalSeats additional = 5;
|
||||||
|
string type = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ScalableDTO {
|
||||||
|
int32 provided = 2;
|
||||||
|
string stripePriceId = 6;
|
||||||
|
string price = 7;
|
||||||
|
string type = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PurchasableDTO {
|
||||||
|
oneof kind {
|
||||||
|
FreeDTO free = 1;
|
||||||
|
PackagedDTO packaged = 2;
|
||||||
|
ScalableDTO scalable = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FreeOrScalableDTO {
|
||||||
|
oneof kind {
|
||||||
|
FreeDTO free = 1;
|
||||||
|
ScalableDTO scalable = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message SecretsManagerPurchasablesDTO {
|
||||||
|
FreeOrScalableDTO seats = 1;
|
||||||
|
FreeOrScalableDTO serviceAccounts = 2;
|
||||||
|
}
|
@ -69,7 +69,7 @@ public class OrganizationBillingService(
|
|||||||
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
|
||||||
{
|
{
|
||||||
return new OrganizationMetadata(isEligibleForSelfHost, isManaged, false,
|
return new OrganizationMetadata(isEligibleForSelfHost, isManaged, false,
|
||||||
false, false, false, null, null, null);
|
false, false, false, false, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var customer = await subscriberService.GetCustomer(organization,
|
var customer = await subscriberService.GetCustomer(organization,
|
||||||
@ -79,6 +79,7 @@ public class OrganizationBillingService(
|
|||||||
|
|
||||||
var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription);
|
var isOnSecretsManagerStandalone = IsOnSecretsManagerStandalone(organization, customer, subscription);
|
||||||
var isSubscriptionUnpaid = IsSubscriptionUnpaid(subscription);
|
var isSubscriptionUnpaid = IsSubscriptionUnpaid(subscription);
|
||||||
|
var isSubscriptionCanceled = IsSubscriptionCanceled(subscription);
|
||||||
var hasSubscription = true;
|
var hasSubscription = true;
|
||||||
var openInvoice = await HasOpenInvoiceAsync(subscription);
|
var openInvoice = await HasOpenInvoiceAsync(subscription);
|
||||||
var hasOpenInvoice = openInvoice.HasOpenInvoice;
|
var hasOpenInvoice = openInvoice.HasOpenInvoice;
|
||||||
@ -87,7 +88,7 @@ public class OrganizationBillingService(
|
|||||||
var subPeriodEndDate = subscription?.CurrentPeriodEnd;
|
var subPeriodEndDate = subscription?.CurrentPeriodEnd;
|
||||||
|
|
||||||
return new OrganizationMetadata(isEligibleForSelfHost, isManaged, isOnSecretsManagerStandalone,
|
return new OrganizationMetadata(isEligibleForSelfHost, isManaged, isOnSecretsManagerStandalone,
|
||||||
isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate);
|
isSubscriptionUnpaid, hasSubscription, hasOpenInvoice, isSubscriptionCanceled, invoiceDueDate, invoiceCreatedDate, subPeriodEndDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdatePaymentMethod(
|
public async Task UpdatePaymentMethod(
|
||||||
@ -437,5 +438,15 @@ public class OrganizationBillingService(
|
|||||||
? (true, invoice.Created, invoice.DueDate)
|
? (true, invoice.Created, invoice.DueDate)
|
||||||
: (false, null, null);
|
: (false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsSubscriptionCanceled(Subscription subscription)
|
||||||
|
{
|
||||||
|
if (subscription == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return subscription.Status == "canceled";
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -133,6 +133,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string NativeCreateAccountFlow = "native-create-account-flow";
|
public const string NativeCreateAccountFlow = "native-create-account-flow";
|
||||||
public const string AccountDeprovisioning = "pm-10308-account-deprovisioning";
|
public const string AccountDeprovisioning = "pm-10308-account-deprovisioning";
|
||||||
public const string NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements";
|
public const string NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements";
|
||||||
|
public const string BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain";
|
||||||
public const string AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api";
|
public const string AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api";
|
||||||
public const string PersistPopupView = "persist-popup-view";
|
public const string PersistPopupView = "persist-popup-view";
|
||||||
public const string CipherKeyEncryption = "cipher-key-encryption";
|
public const string CipherKeyEncryption = "cipher-key-encryption";
|
||||||
@ -140,7 +141,6 @@ public static class FeatureFlagKeys
|
|||||||
public const string StorageReseedRefactor = "storage-reseed-refactor";
|
public const string StorageReseedRefactor = "storage-reseed-refactor";
|
||||||
public const string TrialPayment = "PM-8163-trial-payment";
|
public const string TrialPayment = "PM-8163-trial-payment";
|
||||||
public const string RemoveServerVersionHeader = "remove-server-version-header";
|
public const string RemoveServerVersionHeader = "remove-server-version-header";
|
||||||
public const string SecureOrgGroupDetails = "pm-3479-secure-org-group-details";
|
|
||||||
public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint";
|
public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint";
|
||||||
public const string PM12275_MultiOrganizationEnterprises = "pm-12275-multi-organization-enterprises";
|
public const string PM12275_MultiOrganizationEnterprises = "pm-12275-multi-organization-enterprises";
|
||||||
public const string GeneratorToolsModernization = "generator-tools-modernization";
|
public const string GeneratorToolsModernization = "generator-tools-modernization";
|
||||||
@ -150,7 +150,6 @@ public static class FeatureFlagKeys
|
|||||||
public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss";
|
public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss";
|
||||||
public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss";
|
public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss";
|
||||||
public const string SecurityTasks = "security-tasks";
|
public const string SecurityTasks = "security-tasks";
|
||||||
public const string PM14401_ScaleMSPOnClientOrganizationUpdate = "PM-14401-scale-msp-on-client-organization-update";
|
|
||||||
public const string PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission";
|
public const string PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission";
|
||||||
public const string DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship";
|
public const string DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship";
|
||||||
public const string MacOsNativeCredentialSync = "macos-native-credential-sync";
|
public const string MacOsNativeCredentialSync = "macos-native-credential-sync";
|
||||||
@ -164,6 +163,8 @@ public static class FeatureFlagKeys
|
|||||||
public const string AuthenticatorSyncAndroid = "enable-authenticator-sync-android";
|
public const string AuthenticatorSyncAndroid = "enable-authenticator-sync-android";
|
||||||
public const string AppReviewPrompt = "app-review-prompt";
|
public const string AppReviewPrompt = "app-review-prompt";
|
||||||
public const string ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs";
|
public const string ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs";
|
||||||
|
public const string UsePricingService = "use-pricing-service";
|
||||||
|
public const string RecordInstallationLastActivityDate = "installation-last-activity-date";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
{
|
{
|
||||||
|
@ -21,10 +21,16 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" />
|
<PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" />
|
||||||
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.402.7" />
|
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.402.18" />
|
||||||
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.64" />
|
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.75" />
|
||||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.29.2" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
||||||
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
|
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.18.1" />
|
||||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
|
||||||
@ -41,9 +47,10 @@
|
|||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.7.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.7.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="8.0.10" />
|
||||||
|
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||||
<PackageReference Include="Quartz" Version="3.13.1" />
|
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||||
@ -62,6 +69,10 @@
|
|||||||
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" />
|
<PackageReference Include="LaunchDarkly.ServerSdk" Version="8.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Protobuf Include="Billing\Pricing\Protos\password-manager.proto" GrpcServices="Client" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Resources\" />
|
<Folder Include="Resources\" />
|
||||||
<Folder Include="Properties\" />
|
<Folder Include="Properties\" />
|
||||||
|
@ -72,6 +72,7 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
public DateTime? LastKdfChangeDate { get; set; }
|
public DateTime? LastKdfChangeDate { get; set; }
|
||||||
public DateTime? LastKeyRotationDate { get; set; }
|
public DateTime? LastKeyRotationDate { get; set; }
|
||||||
public DateTime? LastEmailChangeDate { get; set; }
|
public DateTime? LastEmailChangeDate { get; set; }
|
||||||
|
public bool VerifyDevices { get; set; } = true;
|
||||||
|
|
||||||
public void SetNewId()
|
public void SetNewId()
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||||
using Bit.Core.KeyManagement.Models.Data;
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
using Bit.Core.KeyManagement.Repositories;
|
using Bit.Core.KeyManagement.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Platform.Push;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.KeyManagement.Commands;
|
namespace Bit.Core.KeyManagement.Commands;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.KeyManagement.Models.Data;
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Repositories;
|
using Bit.Core.Tools.Repositories;
|
||||||
|
@ -6,8 +6,8 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -4,7 +4,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
|
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Command interface responsible for updating data on an `Installation`
|
||||||
|
/// record.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This interface is implemented by `UpdateInstallationCommand`
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="Bit.Core.Platform.Installations.UpdateInstallationCommand"/>
|
||||||
|
public interface IUpdateInstallationCommand
|
||||||
|
{
|
||||||
|
Task UpdateLastActivityDateAsync(Guid installationId);
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Commands responsible for updating an installation from
|
||||||
|
/// `InstallationRepository`.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If referencing: you probably want the interface
|
||||||
|
/// `IUpdateInstallationCommand` instead of directly calling this class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="IUpdateInstallationCommand"/>
|
||||||
|
public class UpdateInstallationCommand : IUpdateInstallationCommand
|
||||||
|
{
|
||||||
|
private readonly IGetInstallationQuery _getInstallationQuery;
|
||||||
|
private readonly IInstallationRepository _installationRepository;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
|
public UpdateInstallationCommand(
|
||||||
|
IGetInstallationQuery getInstallationQuery,
|
||||||
|
IInstallationRepository installationRepository,
|
||||||
|
TimeProvider timeProvider
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_getInstallationQuery = getInstallationQuery;
|
||||||
|
_installationRepository = installationRepository;
|
||||||
|
_timeProvider = timeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateLastActivityDateAsync(Guid installationId)
|
||||||
|
{
|
||||||
|
if (installationId == default)
|
||||||
|
{
|
||||||
|
throw new Exception
|
||||||
|
(
|
||||||
|
"Tried to update the last activity date for " +
|
||||||
|
"an installation, but an invalid installation id was " +
|
||||||
|
"provided."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var installation = await _getInstallationQuery.GetByIdAsync(installationId);
|
||||||
|
if (installation == null)
|
||||||
|
{
|
||||||
|
throw new Exception
|
||||||
|
(
|
||||||
|
"Tried to update the last activity date for " +
|
||||||
|
$"installation {installationId.ToString()}, but no " +
|
||||||
|
"installation was found for that id."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
installation.LastActivityDate = _timeProvider.GetUtcNow().UtcDateTime;
|
||||||
|
await _installationRepository.UpsertAsync(installation);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace Bit.Core.Entities;
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The base entity for the SQL table `dbo.Installation`. Used to store
|
||||||
|
/// information pertinent to self hosted Bitwarden installations.
|
||||||
|
/// </summary>
|
||||||
public class Installation : ITableObject<Guid>
|
public class Installation : ITableObject<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
@ -14,6 +19,7 @@ public class Installation : ITableObject<Guid>
|
|||||||
public string Key { get; set; } = null!;
|
public string Key { get; set; } = null!;
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||||
|
public DateTime? LastActivityDate { get; internal set; }
|
||||||
|
|
||||||
public void SetNewId()
|
public void SetNewId()
|
||||||
{
|
{
|
@ -0,0 +1,30 @@
|
|||||||
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries responsible for fetching an installation from
|
||||||
|
/// `InstallationRepository`.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If referencing: you probably want the interface `IGetInstallationQuery`
|
||||||
|
/// instead of directly calling this class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="IGetInstallationQuery"/>
|
||||||
|
public class GetInstallationQuery : IGetInstallationQuery
|
||||||
|
{
|
||||||
|
private readonly IInstallationRepository _installationRepository;
|
||||||
|
|
||||||
|
public GetInstallationQuery(IInstallationRepository installationRepository)
|
||||||
|
{
|
||||||
|
_installationRepository = installationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IGetInstallationQuery.GetByIdAsync"/>
|
||||||
|
public async Task<Installation> GetByIdAsync(Guid installationId)
|
||||||
|
{
|
||||||
|
if (installationId == default(Guid))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await _installationRepository.GetByIdAsync(installationId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query interface responsible for fetching an installation from
|
||||||
|
/// `InstallationRepository`.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This interface is implemented by `GetInstallationQuery`
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="GetInstallationQuery"/>
|
||||||
|
public interface IGetInstallationQuery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an installation from the `InstallationRepository` by its id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="installationId">The GUID id of the installation.</param>
|
||||||
|
/// <returns>A task containing an `Installation`.</returns>
|
||||||
|
/// <seealso cref="T:Bit.Core.Platform.Installations.Repositories.IInstallationRepository"/>
|
||||||
|
Task<Installation> GetByIdAsync(Guid installationId);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using Bit.Core.Repositories;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.Platform.Installations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRUD repository interface for communicating with `dbo.Installation`,
|
||||||
|
/// which is used to store information pertinent to self-hosted
|
||||||
|
/// installations.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This interface is implemented by `InstallationRepository` in the Dapper
|
||||||
|
/// and Entity Framework projects.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="T:Bit.Infrastructure.Dapper.Platform.Installations.Repositories.InstallationRepository"/>
|
||||||
|
public interface IInstallationRepository : IRepository<Installation, Guid>
|
||||||
|
{
|
||||||
|
}
|
19
src/Core/Platform/PlatformServiceCollectionExtensions.cs
Normal file
19
src/Core/Platform/PlatformServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Bit.Core.Platform.Installations;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Bit.Core.Platform;
|
||||||
|
|
||||||
|
public static class PlatformServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extend DI to include commands and queries exported from the Platform
|
||||||
|
/// domain.
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddPlatformServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IGetInstallationQuery, GetInstallationQuery>();
|
||||||
|
services.AddScoped<IUpdateInstallationCommand, UpdateInstallationCommand>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using Bit.Core.Utilities;
|
|||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class AzureQueuePushNotificationService : IPushNotificationService
|
public class AzureQueuePushNotificationService : IPushNotificationService
|
||||||
{
|
{
|
@ -4,7 +4,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
public interface IPushNotificationService
|
public interface IPushNotificationService
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
public interface IPushRegistrationService
|
public interface IPushRegistrationService
|
||||||
{
|
{
|
@ -7,7 +7,7 @@ using Bit.Core.Vault.Entities;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class MultiServicePushNotificationService : IPushNotificationService
|
public class MultiServicePushNotificationService : IPushNotificationService
|
||||||
{
|
{
|
@ -4,7 +4,7 @@ using Bit.Core.Enums;
|
|||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class NoopPushNotificationService : IPushNotificationService
|
public class NoopPushNotificationService : IPushNotificationService
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class NoopPushRegistrationService : IPushRegistrationService
|
public class NoopPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
@ -3,13 +3,15 @@ using Bit.Core.Auth.Entities;
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
// This service is not in the `Internal` namespace because it has direct external references.
|
||||||
|
namespace Bit.Core.Platform.Push;
|
||||||
|
|
||||||
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService
|
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService
|
||||||
{
|
{
|
@ -6,13 +6,14 @@ using Bit.Core.IdentityServer;
|
|||||||
using Bit.Core.Models;
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService
|
public class RelayPushNotificationService : BaseIdentityClientService, IPushNotificationService
|
||||||
{
|
{
|
@ -1,14 +1,14 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
using Bit.Core.Models.Api;
|
using Bit.Core.Models.Api;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Platform.Push.Internal;
|
||||||
|
|
||||||
public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService
|
public class RelayPushRegistrationService : BaseIdentityClientService, IPushRegistrationService
|
||||||
{
|
{
|
||||||
|
|
||||||
public RelayPushRegistrationService(
|
public RelayPushRegistrationService(
|
||||||
IHttpClientFactory httpFactory,
|
IHttpClientFactory httpFactory,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Auth.Models.Data;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -10,5 +11,9 @@ public interface IDeviceRepository : IRepository<Device, Guid>
|
|||||||
Task<Device?> GetByIdentifierAsync(string identifier);
|
Task<Device?> GetByIdentifierAsync(string identifier);
|
||||||
Task<Device?> GetByIdentifierAsync(string identifier, Guid userId);
|
Task<Device?> GetByIdentifierAsync(string identifier, Guid userId);
|
||||||
Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId);
|
Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId);
|
||||||
|
// DeviceAuthDetails is passed back to decouple the response model from the
|
||||||
|
// repository in case more fields are ever added to the details response for
|
||||||
|
// other requests.
|
||||||
|
Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId);
|
||||||
Task ClearPushTokenAsync(Guid id);
|
Task ClearPushTokenAsync(Guid id);
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
using Bit.Core.Entities;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
namespace Bit.Core.Repositories;
|
|
||||||
|
|
||||||
public interface IInstallationRepository : IRepository<Installation, Guid>
|
|
||||||
{
|
|
||||||
}
|
|
@ -2,6 +2,7 @@
|
|||||||
using Bit.Core.Auth.Utilities;
|
using Bit.Core.Auth.Utilities;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
@ -16,6 +16,7 @@ public class LaunchDarklyFeatureService : IFeatureService
|
|||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private const string _anonymousUser = "25a15cac-58cf-4ac0-ad0f-b17c4bd92294";
|
private const string _anonymousUser = "25a15cac-58cf-4ac0-ad0f-b17c4bd92294";
|
||||||
|
|
||||||
|
private const string _contextKindDevice = "device";
|
||||||
private const string _contextKindOrganization = "organization";
|
private const string _contextKindOrganization = "organization";
|
||||||
private const string _contextKindServiceAccount = "service-account";
|
private const string _contextKindServiceAccount = "service-account";
|
||||||
|
|
||||||
@ -158,6 +159,16 @@ public class LaunchDarklyFeatureService : IFeatureService
|
|||||||
|
|
||||||
var builder = LaunchDarkly.Sdk.Context.MultiBuilder();
|
var builder = LaunchDarkly.Sdk.Context.MultiBuilder();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_currentContext.DeviceIdentifier))
|
||||||
|
{
|
||||||
|
var ldDevice = LaunchDarkly.Sdk.Context.Builder(_currentContext.DeviceIdentifier);
|
||||||
|
|
||||||
|
ldDevice.Kind(_contextKindDevice);
|
||||||
|
SetCommonContextAttributes(ldDevice);
|
||||||
|
|
||||||
|
builder.Add(ldDevice.Build());
|
||||||
|
}
|
||||||
|
|
||||||
switch (_currentContext.IdentityClientType)
|
switch (_currentContext.IdentityClientType)
|
||||||
{
|
{
|
||||||
case IdentityClientType.User:
|
case IdentityClientType.User:
|
||||||
|
@ -18,6 +18,7 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tokens;
|
using Bit.Core.Tokens;
|
||||||
@ -1143,7 +1144,10 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
? new UserLicense(user, _licenseService)
|
? new UserLicense(user, _licenseService)
|
||||||
: new UserLicense(user, subscriptionInfo, _licenseService);
|
: new UserLicense(user, subscriptionInfo, _licenseService);
|
||||||
|
|
||||||
userLicense.Token = await _licenseService.CreateUserTokenAsync(user, subscriptionInfo);
|
if (_featureService.IsEnabled(FeatureFlagKeys.SelfHostLicenseRefactor))
|
||||||
|
{
|
||||||
|
userLicense.Token = await _licenseService.CreateUserTokenAsync(user, subscriptionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
return userLicense;
|
return userLicense;
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings();
|
public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings();
|
||||||
public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings();
|
public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings();
|
||||||
public virtual string DevelopmentDirectory { get; set; }
|
public virtual string DevelopmentDirectory { get; set; }
|
||||||
|
|
||||||
public virtual bool EnableEmailVerification { get; set; }
|
public virtual bool EnableEmailVerification { get; set; }
|
||||||
|
public virtual string PricingUri { get; set; }
|
||||||
|
|
||||||
public string BuildExternalUri(string explicitValue, string name)
|
public string BuildExternalUri(string explicitValue, string name)
|
||||||
{
|
{
|
||||||
|
@ -24,5 +24,7 @@ public interface IGlobalSettings
|
|||||||
IPasswordlessAuthSettings PasswordlessAuth { get; set; }
|
IPasswordlessAuthSettings PasswordlessAuth { get; set; }
|
||||||
IDomainVerificationSettings DomainVerification { get; set; }
|
IDomainVerificationSettings DomainVerification { get; set; }
|
||||||
ILaunchDarklySettings LaunchDarkly { get; set; }
|
ILaunchDarklySettings LaunchDarkly { get; set; }
|
||||||
|
string DatabaseProvider { get; set; }
|
||||||
|
GlobalSettings.SqlSettings SqlServer { get; set; }
|
||||||
string DevelopmentDirectory { get; set; }
|
string DevelopmentDirectory { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.Services;
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Identity;
|
using Bit.Core.Identity;
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.SecretsManager.Models.Data;
|
using Bit.Core.SecretsManager.Models.Data;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.Models.Api.Response;
|
using Bit.Core.Auth.Models.Api.Response;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.IdentityServer;
|
using Bit.Core.IdentityServer;
|
||||||
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
@ -23,6 +25,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
ICustomTokenRequestValidator
|
ICustomTokenRequestValidator
|
||||||
{
|
{
|
||||||
private readonly UserManager<User> _userManager;
|
private readonly UserManager<User> _userManager;
|
||||||
|
private readonly IUpdateInstallationCommand _updateInstallationCommand;
|
||||||
|
|
||||||
public CustomTokenRequestValidator(
|
public CustomTokenRequestValidator(
|
||||||
UserManager<User> userManager,
|
UserManager<User> userManager,
|
||||||
@ -39,7 +42,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
ISsoConfigRepository ssoConfigRepository,
|
ISsoConfigRepository ssoConfigRepository,
|
||||||
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder
|
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
|
||||||
|
IUpdateInstallationCommand updateInstallationCommand
|
||||||
)
|
)
|
||||||
: base(
|
: base(
|
||||||
userManager,
|
userManager,
|
||||||
@ -59,6 +63,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
userDecryptionOptionsBuilder)
|
userDecryptionOptionsBuilder)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_updateInstallationCommand = updateInstallationCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
|
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
|
||||||
@ -76,16 +81,24 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
string[] allowedGrantTypes = ["authorization_code", "client_credentials"];
|
string[] allowedGrantTypes = ["authorization_code", "client_credentials"];
|
||||||
|
string clientId = context.Result.ValidatedRequest.ClientId;
|
||||||
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|
if (!allowedGrantTypes.Contains(context.Result.ValidatedRequest.GrantType)
|
||||||
|| context.Result.ValidatedRequest.ClientId.StartsWith("organization")
|
|| clientId.StartsWith("organization")
|
||||||
|| context.Result.ValidatedRequest.ClientId.StartsWith("installation")
|
|| clientId.StartsWith("installation")
|
||||||
|| context.Result.ValidatedRequest.ClientId.StartsWith("internal")
|
|| clientId.StartsWith("internal")
|
||||||
|| context.Result.ValidatedRequest.Client.AllowedScopes.Contains(ApiScopes.ApiSecrets))
|
|| context.Result.ValidatedRequest.Client.AllowedScopes.Contains(ApiScopes.ApiSecrets))
|
||||||
{
|
{
|
||||||
if (context.Result.ValidatedRequest.Client.Properties.TryGetValue("encryptedPayload", out var payload) &&
|
if (context.Result.ValidatedRequest.Client.Properties.TryGetValue("encryptedPayload", out var payload) &&
|
||||||
!string.IsNullOrWhiteSpace(payload))
|
!string.IsNullOrWhiteSpace(payload))
|
||||||
{
|
{
|
||||||
context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } };
|
context.Result.CustomResponse = new Dictionary<string, object> { { "encrypted_payload", payload } };
|
||||||
|
|
||||||
|
}
|
||||||
|
if (FeatureService.IsEnabled(FeatureFlagKeys.RecordInstallationLastActivityDate)
|
||||||
|
&& context.Result.ValidatedRequest.ClientId.StartsWith("installation"))
|
||||||
|
{
|
||||||
|
var installationIdPart = clientId.Split(".")[1];
|
||||||
|
await RecordActivityForInstallation(clientId.Split(".")[1]);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -152,6 +165,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
context.Result.CustomResponse["KeyConnectorUrl"] = userDecryptionOptions.KeyConnectorOption.KeyConnectorUrl;
|
context.Result.CustomResponse["KeyConnectorUrl"] = userDecryptionOptions.KeyConnectorOption.KeyConnectorUrl;
|
||||||
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
context.Result.CustomResponse["ResetMasterPassword"] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,4 +216,25 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
|||||||
context.Result.ErrorDescription = requestContext.ValidationErrorResult.ErrorDescription;
|
context.Result.ErrorDescription = requestContext.ValidationErrorResult.ErrorDescription;
|
||||||
context.Result.CustomResponse = requestContext.CustomResponse;
|
context.Result.CustomResponse = requestContext.CustomResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// To help mentally separate organizations that self host from abandoned
|
||||||
|
/// organizations we hook in to the token refresh event for installations
|
||||||
|
/// to write a simple `DateTime.Now` to the database.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This works well because installations don't phone home very often.
|
||||||
|
/// Currently self hosted installations only refresh tokens every 24
|
||||||
|
/// hours or so for the sake of hooking in to cloud's push relay service.
|
||||||
|
/// If installations ever start refreshing tokens more frequently we may need to
|
||||||
|
/// adjust this to avoid making a bunch of unnecessary database calls!
|
||||||
|
/// </remarks>
|
||||||
|
private async Task RecordActivityForInstallation(string? installationIdString)
|
||||||
|
{
|
||||||
|
if (!Guid.TryParse(installationIdString, out var installationId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _updateInstallationCommand.UpdateLastActivityDateAsync(installationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ public class DeviceValidator(
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">user attempting to authenticate</param>
|
/// <param name="user">user attempting to authenticate</param>
|
||||||
/// <param name="ValidatedRequest">The Request is used to check for the NewDeviceOtp and for the raw device data</param>
|
/// <param name="ValidatedRequest">The Request is used to check for the NewDeviceOtp and for the raw device data</param>
|
||||||
/// <returns>returns deviceValtaionResultType</returns>
|
/// <returns>returns deviceValidationResultType</returns>
|
||||||
private async Task<DeviceValidationResultType> HandleNewDeviceVerificationAsync(User user, ValidatedRequest request)
|
private async Task<DeviceValidationResultType> HandleNewDeviceVerificationAsync(User user, ValidatedRequest request)
|
||||||
{
|
{
|
||||||
// currently unreachable due to backward compatibility
|
// currently unreachable due to backward compatibility
|
||||||
@ -125,6 +125,12 @@ public class DeviceValidator(
|
|||||||
return DeviceValidationResultType.InvalidUser;
|
return DeviceValidationResultType.InvalidUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has the User opted out of new device verification
|
||||||
|
if (!user.VerifyDevices)
|
||||||
|
{
|
||||||
|
return DeviceValidationResultType.Success;
|
||||||
|
}
|
||||||
|
|
||||||
// CS exception flow
|
// CS exception flow
|
||||||
// Check cache for user information
|
// Check cache for user information
|
||||||
var cacheKey = string.Format(AuthConstants.NewDeviceVerificationExceptionCacheKeyFormat, user.Id.ToString());
|
var cacheKey = string.Format(AuthConstants.NewDeviceVerificationExceptionCacheKeyFormat, user.Id.ToString());
|
||||||
@ -146,6 +152,12 @@ public class DeviceValidator(
|
|||||||
var otpValid = await _userService.VerifyOTPAsync(user, newDeviceOtp);
|
var otpValid = await _userService.VerifyOTPAsync(user, newDeviceOtp);
|
||||||
if (otpValid)
|
if (otpValid)
|
||||||
{
|
{
|
||||||
|
// In order to get here they would have to have access to their email so we verify it if it's not already
|
||||||
|
if (!user.EmailVerified)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
await _userService.SaveUserAsync(user);
|
||||||
|
}
|
||||||
return DeviceValidationResultType.Success;
|
return DeviceValidationResultType.Success;
|
||||||
}
|
}
|
||||||
return DeviceValidationResultType.InvalidNewDeviceOtp;
|
return DeviceValidationResultType.InvalidNewDeviceOtp;
|
||||||
|
@ -44,8 +44,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
|
|||||||
IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector,
|
IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> assertionOptionsDataProtector,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
|
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
|
||||||
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand
|
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand)
|
||||||
)
|
|
||||||
: base(
|
: base(
|
||||||
userManager,
|
userManager,
|
||||||
userService,
|
userService,
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Core.Auth.Repositories;
|
|||||||
using Bit.Core.Billing.Repositories;
|
using Bit.Core.Billing.Repositories;
|
||||||
using Bit.Core.KeyManagement.Repositories;
|
using Bit.Core.KeyManagement.Repositories;
|
||||||
using Bit.Core.NotificationCenter.Repositories;
|
using Bit.Core.NotificationCenter.Repositories;
|
||||||
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Tools.Repositories;
|
using Bit.Core.Tools.Repositories;
|
||||||
@ -12,6 +13,7 @@ using Bit.Infrastructure.Dapper.Auth.Repositories;
|
|||||||
using Bit.Infrastructure.Dapper.Billing.Repositories;
|
using Bit.Infrastructure.Dapper.Billing.Repositories;
|
||||||
using Bit.Infrastructure.Dapper.KeyManagement.Repositories;
|
using Bit.Infrastructure.Dapper.KeyManagement.Repositories;
|
||||||
using Bit.Infrastructure.Dapper.NotificationCenter.Repositories;
|
using Bit.Infrastructure.Dapper.NotificationCenter.Repositories;
|
||||||
|
using Bit.Infrastructure.Dapper.Platform;
|
||||||
using Bit.Infrastructure.Dapper.Repositories;
|
using Bit.Infrastructure.Dapper.Repositories;
|
||||||
using Bit.Infrastructure.Dapper.SecretsManager.Repositories;
|
using Bit.Infrastructure.Dapper.SecretsManager.Repositories;
|
||||||
using Bit.Infrastructure.Dapper.Tools.Repositories;
|
using Bit.Infrastructure.Dapper.Tools.Repositories;
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Infrastructure.Dapper.Repositories;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace Bit.Infrastructure.Dapper.Repositories;
|
namespace Bit.Infrastructure.Dapper.Platform;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The CRUD repository for communicating with `dbo.Installation`.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If referencing: you probably want the interface `IInstallationRepository`
|
||||||
|
/// instead of directly calling this class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="IInstallationRepository"/>
|
||||||
public class InstallationRepository : Repository<Installation, Guid>, IInstallationRepository
|
public class InstallationRepository : Repository<Installation, Guid>, IInstallationRepository
|
||||||
{
|
{
|
||||||
public InstallationRepository(GlobalSettings globalSettings)
|
public InstallationRepository(GlobalSettings globalSettings)
|
@ -1,4 +1,5 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
@ -11,9 +12,13 @@ namespace Bit.Infrastructure.Dapper.Repositories;
|
|||||||
|
|
||||||
public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository
|
public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository
|
||||||
{
|
{
|
||||||
|
private readonly IGlobalSettings _globalSettings;
|
||||||
|
|
||||||
public DeviceRepository(GlobalSettings globalSettings)
|
public DeviceRepository(GlobalSettings globalSettings)
|
||||||
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
||||||
{ }
|
{
|
||||||
|
_globalSettings = globalSettings;
|
||||||
|
}
|
||||||
|
|
||||||
public DeviceRepository(string connectionString, string readOnlyConnectionString)
|
public DeviceRepository(string connectionString, string readOnlyConnectionString)
|
||||||
: base(connectionString, readOnlyConnectionString)
|
: base(connectionString, readOnlyConnectionString)
|
||||||
@ -76,6 +81,24 @@ public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId)
|
||||||
|
{
|
||||||
|
var expirationMinutes = _globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes;
|
||||||
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<DeviceAuthDetails>(
|
||||||
|
$"[{Schema}].[{Table}_ReadActiveWithPendingAuthRequestsByUserId]",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
ExpirationMinutes = expirationMinutes
|
||||||
|
},
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ClearPushTokenAsync(Guid id)
|
public async Task ClearPushTokenAsync(Guid id)
|
||||||
{
|
{
|
||||||
using (var connection = new SqlConnection(ConnectionString))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
|
@ -51,7 +51,7 @@ public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId>
|
|||||||
var parameters = new DynamicParameters();
|
var parameters = new DynamicParameters();
|
||||||
parameters.AddDynamicParams(obj);
|
parameters.AddDynamicParams(obj);
|
||||||
parameters.Add("Id", obj.Id, direction: ParameterDirection.InputOutput);
|
parameters.Add("Id", obj.Id, direction: ParameterDirection.InputOutput);
|
||||||
var results = await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
$"[{Schema}].[{Table}_Create]",
|
$"[{Schema}].[{Table}_Create]",
|
||||||
parameters,
|
parameters,
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
@ -64,7 +64,7 @@ public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId>
|
|||||||
{
|
{
|
||||||
using (var connection = new SqlConnection(ConnectionString))
|
using (var connection = new SqlConnection(ConnectionString))
|
||||||
{
|
{
|
||||||
var results = await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
$"[{Schema}].[{Table}_Update]",
|
$"[{Schema}].[{Table}_Update]",
|
||||||
obj,
|
obj,
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
|
@ -48,6 +48,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan
|
|||||||
LimitCollectionDeletion = x.o.LimitCollectionDeletion,
|
LimitCollectionDeletion = x.o.LimitCollectionDeletion,
|
||||||
AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems,
|
AllowAdminAccessToAllCollectionItems = x.o.AllowAdminAccessToAllCollectionItems,
|
||||||
UseRiskInsights = x.o.UseRiskInsights,
|
UseRiskInsights = x.o.UseRiskInsights,
|
||||||
|
ProviderType = x.p.Type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
using Bit.Core.Auth.Enums;
|
||||||
|
using Bit.Core.Auth.Models.Data;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries;
|
||||||
|
|
||||||
|
public class DeviceWithPendingAuthByUserIdQuery
|
||||||
|
{
|
||||||
|
public IQueryable<DeviceAuthDetails> GetQuery(
|
||||||
|
DatabaseContext dbContext,
|
||||||
|
Guid userId,
|
||||||
|
int expirationMinutes)
|
||||||
|
{
|
||||||
|
var devicesWithAuthQuery = (
|
||||||
|
from device in dbContext.Devices
|
||||||
|
where device.UserId == userId && device.Active
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
device,
|
||||||
|
authRequest =
|
||||||
|
(
|
||||||
|
from authRequest in dbContext.AuthRequests
|
||||||
|
where authRequest.RequestDeviceIdentifier == device.Identifier
|
||||||
|
where authRequest.Type == AuthRequestType.AuthenticateAndUnlock || authRequest.Type == AuthRequestType.Unlock
|
||||||
|
where authRequest.Approved == null
|
||||||
|
where authRequest.UserId == userId
|
||||||
|
where authRequest.CreationDate.AddMinutes(expirationMinutes) > DateTime.UtcNow
|
||||||
|
orderby authRequest.CreationDate descending
|
||||||
|
select authRequest
|
||||||
|
).First()
|
||||||
|
}).Select(deviceWithAuthRequest => new DeviceAuthDetails(
|
||||||
|
deviceWithAuthRequest.device,
|
||||||
|
deviceWithAuthRequest.authRequest.Id,
|
||||||
|
deviceWithAuthRequest.authRequest.CreationDate));
|
||||||
|
|
||||||
|
return devicesWithAuthQuery;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.Models;
|
using Bit.Infrastructure.EntityFramework.Platform;
|
||||||
|
|
||||||
namespace Bit.Infrastructure.EntityFramework.Billing.Models;
|
namespace Bit.Infrastructure.EntityFramework.Billing.Models;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ using Bit.Core.Billing.Repositories;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.KeyManagement.Repositories;
|
using Bit.Core.KeyManagement.Repositories;
|
||||||
using Bit.Core.NotificationCenter.Repositories;
|
using Bit.Core.NotificationCenter.Repositories;
|
||||||
|
using Bit.Core.Platform.Installations;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Tools.Repositories;
|
using Bit.Core.Tools.Repositories;
|
||||||
@ -13,6 +14,7 @@ using Bit.Infrastructure.EntityFramework.Auth.Repositories;
|
|||||||
using Bit.Infrastructure.EntityFramework.Billing.Repositories;
|
using Bit.Infrastructure.EntityFramework.Billing.Repositories;
|
||||||
using Bit.Infrastructure.EntityFramework.KeyManagement.Repositories;
|
using Bit.Infrastructure.EntityFramework.KeyManagement.Repositories;
|
||||||
using Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories;
|
using Bit.Infrastructure.EntityFramework.NotificationCenter.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Platform;
|
||||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Repositories;
|
using Bit.Infrastructure.EntityFramework.SecretsManager.Repositories;
|
||||||
using Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
using Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user