mirror of
https://github.com/bitwarden/server.git
synced 2025-06-25 13:18:48 -05:00
Merge branch 'main' into dirt/pm-20112_member_access_report_503error
This commit is contained in:
commit
a0ccc98109
220
.github/workflows/build.yml
vendored
220
.github/workflows/build.yml
vendored
@ -11,6 +11,9 @@ on:
|
|||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs: {}
|
inputs: {}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
||||||
@ -19,7 +22,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@ -33,115 +36,15 @@ jobs:
|
|||||||
run: dotnet format --verify-no-changes
|
run: dotnet format --verify-no-changes
|
||||||
|
|
||||||
build-artifacts:
|
build-artifacts:
|
||||||
name: Build artifacts
|
name: Build Docker images
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- lint
|
- lint
|
||||||
outputs:
|
outputs:
|
||||||
has_secrets: ${{ steps.check-secrets.outputs.has_secrets }}
|
has_secrets: ${{ steps.check-secrets.outputs.has_secrets }}
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- project_name: Admin
|
|
||||||
base_path: ./src
|
|
||||||
node: true
|
|
||||||
- project_name: Api
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: Billing
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: Events
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: EventsProcessor
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: Icons
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: Identity
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: MsSqlMigratorUtility
|
|
||||||
base_path: ./util
|
|
||||||
dotnet: true
|
|
||||||
- project_name: Notifications
|
|
||||||
base_path: ./src
|
|
||||||
- project_name: Scim
|
|
||||||
base_path: ./bitwarden_license/src
|
|
||||||
dotnet: true
|
|
||||||
- project_name: Server
|
|
||||||
base_path: ./util
|
|
||||||
- project_name: Setup
|
|
||||||
base_path: ./util
|
|
||||||
- project_name: Sso
|
|
||||||
base_path: ./bitwarden_license/src
|
|
||||||
node: true
|
|
||||||
steps:
|
|
||||||
- name: Check secrets
|
|
||||||
id: check-secrets
|
|
||||||
env:
|
|
||||||
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
|
||||||
run: |
|
|
||||||
has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }}
|
|
||||||
echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Check out repo
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
|
|
||||||
- name: Set up .NET
|
|
||||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
|
||||||
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
|
||||||
with:
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: "**/package-lock.json"
|
|
||||||
node-version: "16"
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
whoami
|
|
||||||
dotnet --info
|
|
||||||
node --version
|
|
||||||
npm --version
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Build node
|
|
||||||
if: ${{ matrix.node }}
|
|
||||||
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Publish project
|
|
||||||
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
|
||||||
run: |
|
|
||||||
echo "Publish"
|
|
||||||
dotnet publish -c "Release" -o obj/build-output/publish
|
|
||||||
|
|
||||||
cd obj/build-output/publish
|
|
||||||
zip -r ${{ matrix.project_name }}.zip .
|
|
||||||
mv ${{ matrix.project_name }}.zip ../../../
|
|
||||||
|
|
||||||
pwd
|
|
||||||
ls -atlh ../../../
|
|
||||||
|
|
||||||
- name: Upload project artifact
|
|
||||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.project_name }}.zip
|
|
||||||
path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
build-docker:
|
|
||||||
name: Build Docker images
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write
|
security-events: write
|
||||||
id-token: write
|
id-token: write
|
||||||
needs:
|
|
||||||
- build-artifacts
|
|
||||||
if: ${{ needs.build-artifacts.outputs.has_secrets == 'true' }}
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -149,6 +52,7 @@ jobs:
|
|||||||
- project_name: Admin
|
- project_name: Admin
|
||||||
base_path: ./src
|
base_path: ./src
|
||||||
dotnet: true
|
dotnet: true
|
||||||
|
node: true
|
||||||
- project_name: Api
|
- project_name: Api
|
||||||
base_path: ./src
|
base_path: ./src
|
||||||
dotnet: true
|
dotnet: true
|
||||||
@ -182,9 +86,6 @@ jobs:
|
|||||||
- project_name: Scim
|
- project_name: Scim
|
||||||
base_path: ./bitwarden_license/src
|
base_path: ./bitwarden_license/src
|
||||||
dotnet: true
|
dotnet: true
|
||||||
- project_name: Server
|
|
||||||
base_path: ./util
|
|
||||||
dotnet: true
|
|
||||||
- project_name: Setup
|
- project_name: Setup
|
||||||
base_path: ./util
|
base_path: ./util
|
||||||
dotnet: true
|
dotnet: true
|
||||||
@ -192,6 +93,14 @@ jobs:
|
|||||||
base_path: ./bitwarden_license/src
|
base_path: ./bitwarden_license/src
|
||||||
dotnet: true
|
dotnet: true
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check secrets
|
||||||
|
id: check-secrets
|
||||||
|
env:
|
||||||
|
AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||||
|
run: |
|
||||||
|
has_secrets=${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL != '' }}
|
||||||
|
echo "has_secrets=$has_secrets" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
@ -203,13 +112,67 @@ jobs:
|
|||||||
id: publish-branch-check
|
id: publish-branch-check
|
||||||
run: |
|
run: |
|
||||||
IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES
|
IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES
|
||||||
|
|
||||||
if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then
|
if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then
|
||||||
echo "is_publish_branch=true" >> $GITHUB_ENV
|
echo "is_publish_branch=true" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "is_publish_branch=false" >> $GITHUB_ENV
|
echo "is_publish_branch=false" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Set up .NET
|
||||||
|
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
|
with:
|
||||||
|
cache: "npm"
|
||||||
|
cache-dependency-path: "**/package-lock.json"
|
||||||
|
node-version: "16"
|
||||||
|
|
||||||
|
- name: Print environment
|
||||||
|
run: |
|
||||||
|
whoami
|
||||||
|
dotnet --info
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
echo "GitHub ref: $GITHUB_REF"
|
||||||
|
echo "GitHub event: $GITHUB_EVENT"
|
||||||
|
|
||||||
|
- name: Build node
|
||||||
|
if: ${{ matrix.node }}
|
||||||
|
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
- name: Publish project
|
||||||
|
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
||||||
|
if: ${{ matrix.dotnet }}
|
||||||
|
run: |
|
||||||
|
echo "Publish"
|
||||||
|
dotnet publish -c "Release" -o obj/build-output/publish
|
||||||
|
|
||||||
|
cd obj/build-output/publish
|
||||||
|
zip -r ${{ matrix.project_name }}.zip .
|
||||||
|
mv ${{ matrix.project_name }}.zip ../../../
|
||||||
|
|
||||||
|
pwd
|
||||||
|
ls -atlh ../../../
|
||||||
|
|
||||||
|
- name: Upload project artifact
|
||||||
|
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||||
|
if: ${{ matrix.dotnet }}
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.project_name }}.zip
|
||||||
|
path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
########## Set up Docker ##########
|
||||||
|
- name: Set up QEMU emulators
|
||||||
|
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||||
|
|
||||||
########## ACRs ##########
|
########## ACRs ##########
|
||||||
- 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
|
||||||
@ -277,26 +240,16 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "tags=$TAGS" >> $GITHUB_OUTPUT
|
echo "tags=$TAGS" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Get build artifact
|
|
||||||
if: ${{ matrix.dotnet }}
|
|
||||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.project_name }}.zip
|
|
||||||
|
|
||||||
- name: Set up build artifact
|
|
||||||
if: ${{ matrix.dotnet }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ${{ matrix.base_path}}/${{ matrix.project_name }}/obj/build-output/publish
|
|
||||||
unzip ${{ matrix.project_name }}.zip \
|
|
||||||
-d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish
|
|
||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
id: build-docker
|
id: build-artifacts
|
||||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
||||||
with:
|
with:
|
||||||
context: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
context: .
|
||||||
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
|
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
|
||||||
platforms: linux/amd64
|
platforms: |
|
||||||
|
linux/amd64,
|
||||||
|
linux/arm/v7,
|
||||||
|
linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.image-tags.outputs.tags }}
|
tags: ${{ steps.image-tags.outputs.tags }}
|
||||||
secrets: |
|
secrets: |
|
||||||
@ -309,7 +262,7 @@ jobs:
|
|||||||
- name: Sign image with Cosign
|
- name: Sign image with Cosign
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||||
env:
|
env:
|
||||||
DIGEST: ${{ steps.build-docker.outputs.digest }}
|
DIGEST: ${{ steps.build-artifacts.outputs.digest }}
|
||||||
TAGS: ${{ steps.image-tags.outputs.tags }}
|
TAGS: ${{ steps.image-tags.outputs.tags }}
|
||||||
run: |
|
run: |
|
||||||
IFS="," read -a tags <<< "${TAGS}"
|
IFS="," read -a tags <<< "${TAGS}"
|
||||||
@ -336,8 +289,8 @@ jobs:
|
|||||||
|
|
||||||
upload:
|
upload:
|
||||||
name: Upload
|
name: Upload
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
needs: build-docker
|
needs: build-artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
@ -377,9 +330,9 @@ jobs:
|
|||||||
|
|
||||||
# Run setup
|
# Run setup
|
||||||
docker run -i --rm --name setup -v $STUB_OUTPUT/US:/bitwarden $SETUP_IMAGE \
|
docker run -i --rm --name setup -v $STUB_OUTPUT/US:/bitwarden $SETUP_IMAGE \
|
||||||
dotnet Setup.dll -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region US
|
/app/Setup -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region US
|
||||||
docker run -i --rm --name setup -v $STUB_OUTPUT/EU:/bitwarden $SETUP_IMAGE \
|
docker run -i --rm --name setup -v $STUB_OUTPUT/EU:/bitwarden $SETUP_IMAGE \
|
||||||
dotnet Setup.dll -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region EU
|
/app/Setup -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region EU
|
||||||
|
|
||||||
sudo chown -R $(whoami):$(whoami) $STUB_OUTPUT
|
sudo chown -R $(whoami):$(whoami) $STUB_OUTPUT
|
||||||
|
|
||||||
@ -512,7 +465,7 @@ jobs:
|
|||||||
|
|
||||||
build-mssqlmigratorutility:
|
build-mssqlmigratorutility:
|
||||||
name: Build MSSQL migrator utility
|
name: Build MSSQL migrator utility
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- lint
|
- lint
|
||||||
defaults:
|
defaults:
|
||||||
@ -568,9 +521,9 @@ jobs:
|
|||||||
if: |
|
if: |
|
||||||
github.event_name != 'pull_request'
|
github.event_name != 'pull_request'
|
||||||
&& (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')
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- build-docker
|
- build-artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Log in to Azure - CI subscription
|
- name: Log in to Azure - CI subscription
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
@ -604,7 +557,7 @@ jobs:
|
|||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- build-docker
|
- build-artifacts
|
||||||
steps:
|
steps:
|
||||||
- name: Log in to Azure - CI subscription
|
- name: Log in to Azure - CI subscription
|
||||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||||
@ -638,7 +591,6 @@ jobs:
|
|||||||
name: Setup Ephemeral Environment
|
name: Setup Ephemeral Environment
|
||||||
needs:
|
needs:
|
||||||
- build-artifacts
|
- build-artifacts
|
||||||
- build-docker
|
|
||||||
if: |
|
if: |
|
||||||
needs.build-artifacts.outputs.has_secrets == 'true'
|
needs.build-artifacts.outputs.has_secrets == 'true'
|
||||||
&& github.event_name == 'pull_request'
|
&& github.event_name == 'pull_request'
|
||||||
@ -648,6 +600,7 @@ jobs:
|
|||||||
project: server
|
project: server
|
||||||
pull_request_number: ${{ github.event.number }}
|
pull_request_number: ${{ github.event.number }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
check-failures:
|
check-failures:
|
||||||
name: Check for failures
|
name: Check for failures
|
||||||
@ -656,7 +609,6 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- lint
|
- lint
|
||||||
- build-artifacts
|
- build-artifacts
|
||||||
- build-docker
|
|
||||||
- upload
|
- upload
|
||||||
- build-mssqlmigratorutility
|
- build-mssqlmigratorutility
|
||||||
- self-host-build
|
- self-host-build
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
|
||||||
<Version>2025.5.2</Version>
|
<Version>2025.6.0</Version>
|
||||||
|
|
||||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@ -69,5 +69,4 @@
|
|||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
@ -287,11 +287,10 @@ public class ProviderService : IProviderService
|
|||||||
|
|
||||||
foreach (var user in users)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
if (!keyedFilteredUsers.ContainsKey(user.Id))
|
if (!keyedFilteredUsers.TryGetValue(user.Id, out var providerUser))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var providerUser = keyedFilteredUsers[user.Id];
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (providerUser.Status != ProviderUserStatusType.Accepted || providerUser.ProviderId != providerId)
|
if (providerUser.Status != ProviderUserStatusType.Accepted || providerUser.ProviderId != providerId)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
@ -27,7 +26,6 @@ using Stripe;
|
|||||||
|
|
||||||
namespace Bit.Commercial.Core.Billing.Providers.Services;
|
namespace Bit.Commercial.Core.Billing.Providers.Services;
|
||||||
|
|
||||||
[RequireFeature(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion)]
|
|
||||||
public class BusinessUnitConverter(
|
public class BusinessUnitConverter(
|
||||||
IDataProtectionProvider dataProtectionProvider,
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
*
|
|
||||||
!obj/build-output/publish/*
|
|
||||||
!obj/Docker/empty/
|
|
||||||
!entrypoint.sh
|
|
@ -1,6 +1,50 @@
|
|||||||
|
###############################################
|
||||||
|
# Build stage #
|
||||||
|
###############################################
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
|
||||||
|
# Docker buildx supplies the value for this arg
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# Determine proper runtime value for .NET
|
||||||
|
# We put the value in a file to be read by later layers.
|
||||||
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
|
RID=linux-x64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
|
RID=linux-arm64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
RID=linux-arm ; \
|
||||||
|
fi \
|
||||||
|
&& echo "RID=$RID" > /tmp/rid.txt
|
||||||
|
|
||||||
|
# Copy required project files
|
||||||
|
WORKDIR /source
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Restore project dependencies and tools
|
||||||
|
WORKDIR /source/bitwarden_license/src/Scim
|
||||||
|
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
RUN . /tmp/rid.txt && dotnet publish \
|
||||||
|
-c release \
|
||||||
|
--no-restore \
|
||||||
|
--self-contained \
|
||||||
|
/p:PublishSingleFile=true \
|
||||||
|
-r $RID \
|
||||||
|
-o out
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# App stage #
|
||||||
|
###############################################
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ENV ASPNETCORE_URLS=http://+:5000
|
||||||
|
ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@ -9,11 +53,10 @@ RUN apt-get update \
|
|||||||
krb5-user \
|
krb5-user \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS http://+:5000
|
# Copy app from the build stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 5000
|
COPY --from=build /source/bitwarden_license/src/Scim/out /app
|
||||||
COPY obj/build-output/publish .
|
COPY ./bitwarden_license/src/Scim/entrypoint.sh /entrypoint.sh
|
||||||
COPY entrypoint.sh /
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
||||||
|
@ -16,8 +16,8 @@ public class Program
|
|||||||
{
|
{
|
||||||
var context = e.Properties["SourceContext"].ToString();
|
var context = e.Properties["SourceContext"].ToString();
|
||||||
|
|
||||||
if (e.Properties.ContainsKey("RequestPath") &&
|
if (e.Properties.TryGetValue("RequestPath", out var requestPath) &&
|
||||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
!string.IsNullOrWhiteSpace(requestPath?.ToString()) &&
|
||||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
@ -19,31 +19,42 @@ then
|
|||||||
LGID=65534
|
LGID=65534
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create user and group
|
if [ "$(id -u)" = "0" ]
|
||||||
|
then
|
||||||
|
# Create user and group
|
||||||
|
|
||||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||||
mkhomedir_helper $USERNAME
|
mkhomedir_helper $USERNAME
|
||||||
|
|
||||||
# The rest...
|
# The rest...
|
||||||
|
|
||||||
chown -R $USERNAME:$GROUPNAME /app
|
chown -R $USERNAME:$GROUPNAME /app
|
||||||
mkdir -p /etc/bitwarden/core
|
mkdir -p /etc/bitwarden/core
|
||||||
mkdir -p /etc/bitwarden/logs
|
mkdir -p /etc/bitwarden/logs
|
||||||
mkdir -p /etc/bitwarden/ca-certificates
|
mkdir -p /etc/bitwarden/ca-certificates
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||||
|
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
cp /etc/bitwarden/ca-certificates/*.crt /usr/local/share/ca-certificates/ >/dev/null 2>&1 \
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
||||||
&& update-ca-certificates
|
fi
|
||||||
|
|
||||||
|
gosu_cmd="gosu $USERNAME:$GROUPNAME"
|
||||||
|
else
|
||||||
|
gosu_cmd=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
||||||
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
$gosu_cmd kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
||||||
gosu $USERNAME:$GROUPNAME kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gosu $USERNAME:$GROUPNAME dotnet /app/Scim.dll
|
if [[ $globalSettings__selfHosted == "true" ]]; then
|
||||||
|
if [[ -z $globalSettings__identityServer__certificateLocation ]]; then
|
||||||
|
export globalSettings__identityServer__certificateLocation=/etc/bitwarden/identity/identity.pfx
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec $gosu_cmd /app/Scim
|
||||||
|
@ -370,8 +370,8 @@ public class AccountController : Controller
|
|||||||
// for the user identifier.
|
// for the user identifier.
|
||||||
static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier
|
static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier
|
||||||
&& (c.Properties == null
|
&& (c.Properties == null
|
||||||
|| !c.Properties.ContainsKey(SamlPropertyKeys.ClaimFormat)
|
|| !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat, out var claimFormat)
|
||||||
|| c.Properties[SamlPropertyKeys.ClaimFormat] != SamlNameIdFormats.Transient);
|
|| claimFormat != SamlNameIdFormats.Transient);
|
||||||
|
|
||||||
// Try to determine the unique id of the external user (issued by the provider)
|
// Try to determine the unique id of the external user (issued by the provider)
|
||||||
// the most common claim type for that are the sub claim and the NameIdentifier
|
// the most common claim type for that are the sub claim and the NameIdentifier
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
|
###############################################
|
||||||
|
# Build stage #
|
||||||
|
###############################################
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
|
||||||
|
# Docker buildx supplies the value for this arg
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# Determine proper runtime value for .NET
|
||||||
|
# We put the value in a file to be read by later layers.
|
||||||
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
|
RID=linux-x64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
|
RID=linux-arm64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
RID=linux-arm ; \
|
||||||
|
fi \
|
||||||
|
&& echo "RID=$RID" > /tmp/rid.txt
|
||||||
|
|
||||||
|
# Copy required project files
|
||||||
|
WORKDIR /source
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Restore project dependencies and tools
|
||||||
|
WORKDIR /source/bitwarden_license/src/Sso
|
||||||
|
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
RUN . /tmp/rid.txt && dotnet publish \
|
||||||
|
-c release \
|
||||||
|
--no-restore \
|
||||||
|
--self-contained \
|
||||||
|
/p:PublishSingleFile=true \
|
||||||
|
-r $RID \
|
||||||
|
-o out
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# App stage #
|
||||||
|
###############################################
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ENV ASPNETCORE_URLS=http://+:5000
|
||||||
|
ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@ -9,11 +53,10 @@ RUN apt-get update \
|
|||||||
krb5-user \
|
krb5-user \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS http://+:5000
|
# Copy app from the build stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 5000
|
COPY --from=build /source/bitwarden_license/src/Sso/out /app
|
||||||
COPY obj/build-output/publish .
|
COPY ./bitwarden_license/src/Sso/entrypoint.sh /entrypoint.sh
|
||||||
COPY entrypoint.sh /
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
||||||
|
@ -17,8 +17,8 @@ public class Program
|
|||||||
logging.AddSerilog(hostingContext, (e, globalSettings) =>
|
logging.AddSerilog(hostingContext, (e, globalSettings) =>
|
||||||
{
|
{
|
||||||
var context = e.Properties["SourceContext"].ToString();
|
var context = e.Properties["SourceContext"].ToString();
|
||||||
if (e.Properties.ContainsKey("RequestPath") &&
|
if (e.Properties.TryGetValue("RequestPath", out var requestPath) &&
|
||||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
!string.IsNullOrWhiteSpace(requestPath?.ToString()) &&
|
||||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -46,9 +46,9 @@ public static class OpenIdConnectOptionsExtensions
|
|||||||
|
|
||||||
// Handle State if we've gotten that back
|
// Handle State if we've gotten that back
|
||||||
var decodedState = options.StateDataFormat.Unprotect(state);
|
var decodedState = options.StateDataFormat.Unprotect(state);
|
||||||
if (decodedState != null && decodedState.Items.ContainsKey("scheme"))
|
if (decodedState != null && decodedState.Items.TryGetValue("scheme", out var stateScheme))
|
||||||
{
|
{
|
||||||
return decodedState.Items["scheme"] == scheme;
|
return stateScheme == scheme;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
@ -19,37 +19,42 @@ then
|
|||||||
LGID=65534
|
LGID=65534
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create user and group
|
if [ "$(id -u)" = "0" ]
|
||||||
|
then
|
||||||
|
# Create user and group
|
||||||
|
|
||||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||||
mkhomedir_helper $USERNAME
|
mkhomedir_helper $USERNAME
|
||||||
|
|
||||||
# The rest...
|
# The rest...
|
||||||
|
|
||||||
mkdir -p /etc/bitwarden/identity
|
chown -R $USERNAME:$GROUPNAME /app
|
||||||
mkdir -p /etc/bitwarden/core
|
mkdir -p /etc/bitwarden/core
|
||||||
mkdir -p /etc/bitwarden/logs
|
mkdir -p /etc/bitwarden/logs
|
||||||
mkdir -p /etc/bitwarden/ca-certificates
|
mkdir -p /etc/bitwarden/ca-certificates
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||||
|
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
cp /etc/bitwarden/identity/identity.pfx /app/identity.pfx
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chown -R $USERNAME:$GROUPNAME /app
|
gosu_cmd="gosu $USERNAME:$GROUPNAME"
|
||||||
|
else
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
gosu_cmd=""
|
||||||
cp /etc/bitwarden/ca-certificates/*.crt /usr/local/share/ca-certificates/ >/dev/null 2>&1 \
|
|
||||||
&& update-ca-certificates
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
||||||
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
$gosu_cmd kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
||||||
gosu $USERNAME:$GROUPNAME kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gosu $USERNAME:$GROUPNAME dotnet /app/Sso.dll
|
if [[ $globalSettings__selfHosted == "true" ]]; then
|
||||||
|
if [[ -z $globalSettings__identityServer__certificateLocation ]]; then
|
||||||
|
export globalSettings__identityServer__certificateLocation=/etc/bitwarden/identity/identity.pfx
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec $gosu_cmd /app/Sso
|
||||||
|
@ -99,7 +99,7 @@ services:
|
|||||||
- idp
|
- idp
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: rabbitmq:management
|
image: rabbitmq:4.1.0-management
|
||||||
container_name: rabbitmq
|
container_name: rabbitmq
|
||||||
ports:
|
ports:
|
||||||
- "5672:5672"
|
- "5672:5672"
|
||||||
@ -108,7 +108,7 @@ services:
|
|||||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||||
volumes:
|
volumes:
|
||||||
- rabbitmq_data:/var/lib/rabbitmq_data
|
- rabbitmq_data:/var/lib/rabbitmq
|
||||||
profiles:
|
profiles:
|
||||||
- rabbitmq
|
- rabbitmq
|
||||||
|
|
||||||
|
@ -5,6 +5,6 @@
|
|||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "4.1.0",
|
"Microsoft.Build.Traversal": "4.1.0",
|
||||||
"Microsoft.Build.Sql": "0.1.9-preview"
|
"Microsoft.Build.Sql": "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
*
|
|
||||||
!obj/build-output/publish/*
|
|
||||||
!obj/Docker/empty/
|
|
||||||
!entrypoint.sh
|
|
@ -12,7 +12,6 @@ using Bit.Core.Billing.Enums;
|
|||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Providers.Services;
|
using Bit.Core.Billing.Providers.Services;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||||
@ -20,9 +19,6 @@ using Bit.Core.Repositories;
|
|||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Bit.Core.Vault.Repositories;
|
using Bit.Core.Vault.Repositories;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
@ -45,12 +41,9 @@ public class OrganizationsController : Controller
|
|||||||
private readonly IPaymentService _paymentService;
|
private readonly IPaymentService _paymentService;
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly IUserService _userService;
|
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly ILogger<OrganizationsController> _logger;
|
private readonly ILogger<OrganizationsController> _logger;
|
||||||
private readonly IAccessControlService _accessControlService;
|
private readonly IAccessControlService _accessControlService;
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
private readonly ISecretRepository _secretRepository;
|
private readonly ISecretRepository _secretRepository;
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||||
@ -73,12 +66,9 @@ public class OrganizationsController : Controller
|
|||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IUserService userService,
|
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
ILogger<OrganizationsController> logger,
|
ILogger<OrganizationsController> logger,
|
||||||
IAccessControlService accessControlService,
|
IAccessControlService accessControlService,
|
||||||
ICurrentContext currentContext,
|
|
||||||
ISecretRepository secretRepository,
|
ISecretRepository secretRepository,
|
||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
IServiceAccountRepository serviceAccountRepository,
|
IServiceAccountRepository serviceAccountRepository,
|
||||||
@ -100,12 +90,9 @@ public class OrganizationsController : Controller
|
|||||||
_paymentService = paymentService;
|
_paymentService = paymentService;
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_userService = userService;
|
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_accessControlService = accessControlService;
|
_accessControlService = accessControlService;
|
||||||
_currentContext = currentContext;
|
|
||||||
_secretRepository = secretRepository;
|
_secretRepository = secretRepository;
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
_serviceAccountRepository = serviceAccountRepository;
|
_serviceAccountRepository = serviceAccountRepository;
|
||||||
@ -272,11 +259,6 @@ public class OrganizationsController : Controller
|
|||||||
await _organizationRepository.ReplaceAsync(organization);
|
await _organizationRepository.ReplaceAsync(organization);
|
||||||
|
|
||||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization, _currentContext)
|
|
||||||
{
|
|
||||||
EventRaisedByUser = _userService.GetUserName(User),
|
|
||||||
SalesAssistedTrialStarted = model.SalesAssistedTrialStarted,
|
|
||||||
});
|
|
||||||
|
|
||||||
return RedirectToAction("Edit", new { id });
|
return RedirectToAction("Edit", new { id });
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
@using Bit.Admin.Enums;
|
@using Bit.Admin.Enums;
|
||||||
@using Bit.Admin.Models
|
@using Bit.Admin.Models
|
||||||
@using Bit.Core
|
|
||||||
@using Bit.Core.AdminConsole.Enums.Provider
|
@using Bit.Core.AdminConsole.Enums.Provider
|
||||||
@using Bit.Core.Billing.Enums
|
@using Bit.Core.Billing.Enums
|
||||||
@using Bit.Core.Billing.Extensions
|
@using Bit.Core.Billing.Extensions
|
||||||
@using Bit.Core.Services
|
|
||||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@inject Bit.Admin.Services.IAccessControlService AccessControlService
|
@inject Bit.Admin.Services.IAccessControlService AccessControlService
|
||||||
@inject IFeatureService FeatureService
|
|
||||||
@model OrganizationEditModel
|
@model OrganizationEditModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Name;
|
ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Name;
|
||||||
@ -19,12 +15,10 @@
|
|||||||
var canDelete = AccessControlService.UserHasPermission(Permission.Org_Delete);
|
var canDelete = AccessControlService.UserHasPermission(Permission.Org_Delete);
|
||||||
var canUnlinkFromProvider = AccessControlService.UserHasPermission(Permission.Provider_Edit);
|
var canUnlinkFromProvider = AccessControlService.UserHasPermission(Permission.Provider_Edit);
|
||||||
|
|
||||||
var canConvertToBusinessUnit =
|
var canConvertToBusinessUnit = AccessControlService.UserHasPermission(Permission.Org_Billing_ConvertToBusinessUnit) &&
|
||||||
FeatureService.IsEnabled(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion) &&
|
Model.Organization.PlanType.GetProductTier() == ProductTierType.Enterprise &&
|
||||||
AccessControlService.UserHasPermission(Permission.Org_Billing_ConvertToBusinessUnit) &&
|
!string.IsNullOrEmpty(Model.Organization.GatewaySubscriptionId) &&
|
||||||
Model.Organization.PlanType.GetProductTier() == ProductTierType.Enterprise &&
|
Model.Provider is null or { Type: ProviderType.BusinessUnit, Status: ProviderStatusType.Pending };
|
||||||
!string.IsNullOrEmpty(Model.Organization.GatewaySubscriptionId) &&
|
|
||||||
Model.Provider is null or { Type: ProviderType.BusinessUnit, Status: ProviderStatusType.Pending };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using Bit.Admin.Billing.Models;
|
using Bit.Admin.Billing.Models;
|
||||||
using Bit.Admin.Enums;
|
using Bit.Admin.Enums;
|
||||||
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.Entities.Provider;
|
using Bit.Core.AdminConsole.Entities.Provider;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
@ -18,7 +17,6 @@ namespace Bit.Admin.Billing.Controllers;
|
|||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("organizations/billing/{organizationId:guid}/business-unit")]
|
[Route("organizations/billing/{organizationId:guid}/business-unit")]
|
||||||
[RequireFeature(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion)]
|
|
||||||
public class BusinessUnitConversionController(
|
public class BusinessUnitConversionController(
|
||||||
IBusinessUnitConverter businessUnitConverter,
|
IBusinessUnitConverter businessUnitConverter,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
|
@ -1,21 +1,71 @@
|
|||||||
|
###############################################
|
||||||
|
# Build stage #
|
||||||
|
###############################################
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
|
||||||
|
# Docker buildx supplies the value for this arg
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# Determine proper runtime value for .NET
|
||||||
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
|
RID=linux-x64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
|
RID=linux-arm64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
RID=linux-arm ; \
|
||||||
|
fi \
|
||||||
|
&& echo "RID=$RID" > /tmp/rid.txt
|
||||||
|
|
||||||
|
# Set up Node
|
||||||
|
ARG NODE_VERSION=20
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y nodejs \
|
||||||
|
&& npm install -g npm@latest && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy required project files
|
||||||
|
WORKDIR /source
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Restore project dependencies and tools
|
||||||
|
WORKDIR /source/src/Admin
|
||||||
|
RUN npm ci
|
||||||
|
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
RUN npm run build
|
||||||
|
RUN . /tmp/rid.txt && dotnet publish \
|
||||||
|
-c release \
|
||||||
|
--no-restore \
|
||||||
|
--self-contained \
|
||||||
|
/p:PublishSingleFile=true \
|
||||||
|
-r $RID \
|
||||||
|
-o out
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# App stage #
|
||||||
|
###############################################
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ENV ASPNETCORE_URLS=http://+:5000
|
||||||
|
ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
gosu \
|
gosu \
|
||||||
curl \
|
curl \
|
||||||
krb5-user \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS http://+:5000
|
# Copy app from the build stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 5000
|
COPY --from=build /source/src/Admin/out /app
|
||||||
COPY obj/build-output/publish .
|
COPY ./src/Admin/entrypoint.sh /entrypoint.sh
|
||||||
COPY entrypoint.sh /
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
||||||
HEALTHCHECK CMD curl -f http://localhost:5000 || exit 1
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
@ -39,7 +39,7 @@ public class ReadOnlyEnvIdentityUserStore : ReadOnlyIdentityUserStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var userStamp = usersDict.ContainsKey(normalizedEmail) ? usersDict[normalizedEmail] : null;
|
var userStamp = usersDict.GetValueOrDefault(normalizedEmail);
|
||||||
if (userStamp == null)
|
if (userStamp == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult<IdentityUser>(null);
|
return Task.FromResult<IdentityUser>(null);
|
||||||
|
@ -20,8 +20,8 @@ public class Program
|
|||||||
logging.AddSerilog(hostingContext, (e, globalSettings) =>
|
logging.AddSerilog(hostingContext, (e, globalSettings) =>
|
||||||
{
|
{
|
||||||
var context = e.Properties["SourceContext"].ToString();
|
var context = e.Properties["SourceContext"].ToString();
|
||||||
if (e.Properties.ContainsKey("RequestPath") &&
|
if (e.Properties.TryGetValue("RequestPath", out var requestPath) &&
|
||||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
!string.IsNullOrWhiteSpace(requestPath?.ToString()) &&
|
||||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -29,12 +29,12 @@ public class AccessControlService : IAccessControlService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userRole = GetUserRoleFromClaim();
|
var userRole = GetUserRoleFromClaim();
|
||||||
if (string.IsNullOrEmpty(userRole) || !RolePermissionMapping.RolePermissions.ContainsKey(userRole))
|
if (string.IsNullOrEmpty(userRole) || !RolePermissionMapping.RolePermissions.TryGetValue(userRole, out var rolePermissions))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return RolePermissionMapping.RolePermissions[userRole].Contains(permission);
|
return rolePermissions.Contains(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetUserRole(string userEmail)
|
public string GetUserRole(string userEmail)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
@ -19,31 +19,36 @@ then
|
|||||||
LGID=65534
|
LGID=65534
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create user and group
|
if [ "$(id -u)" = "0" ]
|
||||||
|
then
|
||||||
|
# Create user and group
|
||||||
|
|
||||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||||
mkhomedir_helper $USERNAME
|
mkhomedir_helper $USERNAME
|
||||||
|
|
||||||
# The rest...
|
# The rest...
|
||||||
|
|
||||||
chown -R $USERNAME:$GROUPNAME /app
|
chown -R $USERNAME:$GROUPNAME /app
|
||||||
mkdir -p /etc/bitwarden/core
|
mkdir -p /etc/bitwarden/core
|
||||||
mkdir -p /etc/bitwarden/logs
|
mkdir -p /etc/bitwarden/logs
|
||||||
mkdir -p /etc/bitwarden/ca-certificates
|
mkdir -p /etc/bitwarden/ca-certificates
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||||
|
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
cp /etc/bitwarden/ca-certificates/*.crt /usr/local/share/ca-certificates/ >/dev/null 2>&1 \
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
||||||
&& update-ca-certificates
|
fi
|
||||||
|
|
||||||
|
gosu_cmd="gosu $USERNAME:$GROUPNAME"
|
||||||
|
else
|
||||||
|
gosu_cmd=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
||||||
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
$gosu_cmd kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
||||||
gosu $USERNAME:$GROUPNAME kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gosu $USERNAME:$GROUPNAME dotnet /app/Admin.dll
|
exec $gosu_cmd /app/Admin
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
*
|
|
||||||
!obj/build-output/publish/*
|
|
||||||
!obj/Docker/empty/
|
|
||||||
!entrypoint.sh
|
|
@ -25,7 +25,7 @@ public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationReques
|
|||||||
{
|
{
|
||||||
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
}
|
}
|
||||||
else if (providers.ContainsKey(TwoFactorProviderType.Authenticator))
|
else
|
||||||
{
|
{
|
||||||
providers.Remove(TwoFactorProviderType.Authenticator);
|
providers.Remove(TwoFactorProviderType.Authenticator);
|
||||||
}
|
}
|
||||||
@ -62,7 +62,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
|
|||||||
{
|
{
|
||||||
providers = [];
|
providers = [];
|
||||||
}
|
}
|
||||||
else if (providers.ContainsKey(TwoFactorProviderType.Duo))
|
else
|
||||||
{
|
{
|
||||||
providers.Remove(TwoFactorProviderType.Duo);
|
providers.Remove(TwoFactorProviderType.Duo);
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
|
|||||||
{
|
{
|
||||||
providers = [];
|
providers = [];
|
||||||
}
|
}
|
||||||
else if (providers.ContainsKey(TwoFactorProviderType.OrganizationDuo))
|
else
|
||||||
{
|
{
|
||||||
providers.Remove(TwoFactorProviderType.OrganizationDuo);
|
providers.Remove(TwoFactorProviderType.OrganizationDuo);
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ public class UpdateTwoFactorYubicoOtpRequestModel : SecretVerificationRequestMod
|
|||||||
{
|
{
|
||||||
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
}
|
}
|
||||||
else if (providers.ContainsKey(TwoFactorProviderType.YubiKey))
|
else
|
||||||
{
|
{
|
||||||
providers.Remove(TwoFactorProviderType.YubiKey);
|
providers.Remove(TwoFactorProviderType.YubiKey);
|
||||||
}
|
}
|
||||||
@ -228,7 +228,7 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
|
|||||||
{
|
{
|
||||||
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
}
|
}
|
||||||
else if (providers.ContainsKey(TwoFactorProviderType.Email))
|
else
|
||||||
{
|
{
|
||||||
providers.Remove(TwoFactorProviderType.Email);
|
providers.Remove(TwoFactorProviderType.Email);
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ public class TwoFactorAuthenticatorResponseModel : ResponseModel
|
|||||||
ArgumentNullException.ThrowIfNull(user);
|
ArgumentNullException.ThrowIfNull(user);
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
||||||
if (provider?.MetaData?.ContainsKey("Key") ?? false)
|
if (provider?.MetaData?.TryGetValue("Key", out var keyValue) ?? false)
|
||||||
{
|
{
|
||||||
Key = (string)provider.MetaData["Key"];
|
Key = (string)keyValue;
|
||||||
Enabled = provider.Enabled;
|
Enabled = provider.Enabled;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -15,9 +15,9 @@ public class TwoFactorEmailResponseModel : ResponseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
||||||
if (provider?.MetaData?.ContainsKey("Email") ?? false)
|
if (provider?.MetaData?.TryGetValue("Email", out var email) ?? false)
|
||||||
{
|
{
|
||||||
Email = (string)provider.MetaData["Email"];
|
Email = (string)email;
|
||||||
Enabled = provider.Enabled;
|
Enabled = provider.Enabled;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -19,29 +19,29 @@ public class TwoFactorYubiKeyResponseModel : ResponseModel
|
|||||||
{
|
{
|
||||||
Enabled = provider.Enabled;
|
Enabled = provider.Enabled;
|
||||||
|
|
||||||
if (provider.MetaData.ContainsKey("Key1"))
|
if (provider.MetaData.TryGetValue("Key1", out var key1))
|
||||||
{
|
{
|
||||||
Key1 = (string)provider.MetaData["Key1"];
|
Key1 = (string)key1;
|
||||||
}
|
}
|
||||||
if (provider.MetaData.ContainsKey("Key2"))
|
if (provider.MetaData.TryGetValue("Key2", out var key2))
|
||||||
{
|
{
|
||||||
Key2 = (string)provider.MetaData["Key2"];
|
Key2 = (string)key2;
|
||||||
}
|
}
|
||||||
if (provider.MetaData.ContainsKey("Key3"))
|
if (provider.MetaData.TryGetValue("Key3", out var key3))
|
||||||
{
|
{
|
||||||
Key3 = (string)provider.MetaData["Key3"];
|
Key3 = (string)key3;
|
||||||
}
|
}
|
||||||
if (provider.MetaData.ContainsKey("Key4"))
|
if (provider.MetaData.TryGetValue("Key4", out var key4))
|
||||||
{
|
{
|
||||||
Key4 = (string)provider.MetaData["Key4"];
|
Key4 = (string)key4;
|
||||||
}
|
}
|
||||||
if (provider.MetaData.ContainsKey("Key5"))
|
if (provider.MetaData.TryGetValue("Key5", out var key5))
|
||||||
{
|
{
|
||||||
Key5 = (string)provider.MetaData["Key5"];
|
Key5 = (string)key5;
|
||||||
}
|
}
|
||||||
if (provider.MetaData.ContainsKey("Nfc"))
|
if (provider.MetaData.TryGetValue("Nfc", out var nfc))
|
||||||
{
|
{
|
||||||
Nfc = (bool)provider.MetaData["Nfc"];
|
Nfc = (bool)nfc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -6,14 +6,10 @@ using Bit.Api.Utilities;
|
|||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -161,8 +157,6 @@ public class AccountsController(
|
|||||||
[HttpPost("cancel")]
|
[HttpPost("cancel")]
|
||||||
public async Task PostCancelAsync(
|
public async Task PostCancelAsync(
|
||||||
[FromBody] SubscriptionCancellationRequestModel request,
|
[FromBody] SubscriptionCancellationRequestModel request,
|
||||||
[FromServices] ICurrentContext currentContext,
|
|
||||||
[FromServices] IReferenceEventService referenceEventService,
|
|
||||||
[FromServices] ISubscriberService subscriberService)
|
[FromServices] ISubscriberService subscriberService)
|
||||||
{
|
{
|
||||||
var user = await userService.GetUserByPrincipalAsync(User);
|
var user = await userService.GetUserByPrincipalAsync(User);
|
||||||
@ -175,12 +169,6 @@ public class AccountsController(
|
|||||||
await subscriberService.CancelSubscription(user,
|
await subscriberService.CancelSubscription(user,
|
||||||
new OffboardingSurveyResponse { UserId = user.Id, Reason = request.Reason, Feedback = request.Feedback },
|
new OffboardingSurveyResponse { UserId = user.Id, Reason = request.Reason, Feedback = request.Feedback },
|
||||||
user.IsExpired());
|
user.IsExpired());
|
||||||
|
|
||||||
await referenceEventService.RaiseEventAsync(new ReferenceEvent(
|
|
||||||
ReferenceEventType.CancelSubscription,
|
|
||||||
user,
|
|
||||||
currentContext)
|
|
||||||
{ EndOfPeriod = user.IsExpired() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("reinstate-premium")]
|
[HttpPost("reinstate-premium")]
|
||||||
|
@ -4,7 +4,6 @@ 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.Api.Billing.Queries.Organizations;
|
using Bit.Api.Billing.Queries.Organizations;
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Models.Sales;
|
using Bit.Core.Billing.Models.Sales;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
@ -25,7 +24,6 @@ namespace Bit.Api.Billing.Controllers;
|
|||||||
public class OrganizationBillingController(
|
public class OrganizationBillingController(
|
||||||
IBusinessUnitConverter businessUnitConverter,
|
IBusinessUnitConverter businessUnitConverter,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IFeatureService featureService,
|
|
||||||
IOrganizationBillingService organizationBillingService,
|
IOrganizationBillingService organizationBillingService,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationWarningsQuery organizationWarningsQuery,
|
IOrganizationWarningsQuery organizationWarningsQuery,
|
||||||
@ -318,14 +316,6 @@ public class OrganizationBillingController(
|
|||||||
[FromRoute] Guid organizationId,
|
[FromRoute] Guid organizationId,
|
||||||
[FromBody] SetupBusinessUnitRequestBody requestBody)
|
[FromBody] SetupBusinessUnitRequestBody requestBody)
|
||||||
{
|
{
|
||||||
var enableOrganizationBusinessUnitConversion =
|
|
||||||
featureService.IsEnabled(FeatureFlagKeys.PM18770_EnableOrganizationBusinessUnitConversion);
|
|
||||||
|
|
||||||
if (!enableOrganizationBusinessUnitConversion)
|
|
||||||
{
|
|
||||||
return Error.NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var organization = await organizationRepository.GetByIdAsync(organizationId);
|
var organization = await organizationRepository.GetByIdAsync(organizationId);
|
||||||
|
|
||||||
if (organization == null)
|
if (organization == null)
|
||||||
|
@ -20,9 +20,6 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
|||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -44,7 +41,6 @@ public class OrganizationsController(
|
|||||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||||
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand,
|
IUpgradeOrganizationPlanCommand upgradeOrganizationPlanCommand,
|
||||||
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
|
IAddSecretsManagerSubscriptionCommand addSecretsManagerSubscriptionCommand,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ISubscriberService subscriberService,
|
ISubscriberService subscriberService,
|
||||||
IOrganizationInstallationRepository organizationInstallationRepository,
|
IOrganizationInstallationRepository organizationInstallationRepository,
|
||||||
IPricingClient pricingClient)
|
IPricingClient pricingClient)
|
||||||
@ -246,14 +242,6 @@ public class OrganizationsController(
|
|||||||
Feedback = request.Feedback
|
Feedback = request.Feedback
|
||||||
},
|
},
|
||||||
organization.IsExpired());
|
organization.IsExpired());
|
||||||
|
|
||||||
await referenceEventService.RaiseEventAsync(new ReferenceEvent(
|
|
||||||
ReferenceEventType.CancelSubscription,
|
|
||||||
organization,
|
|
||||||
currentContext)
|
|
||||||
{
|
|
||||||
EndOfPeriod = organization.IsExpired()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id:guid}/reinstate")]
|
[HttpPost("{id:guid}/reinstate")]
|
||||||
|
@ -81,13 +81,6 @@ public class ProviderBillingController(
|
|||||||
[FromRoute] Guid providerId,
|
[FromRoute] Guid providerId,
|
||||||
[FromBody] UpdatePaymentMethodRequestBody requestBody)
|
[FromBody] UpdatePaymentMethodRequestBody requestBody)
|
||||||
{
|
{
|
||||||
var allowProviderPaymentMethod = featureService.IsEnabled(FeatureFlagKeys.PM18794_ProviderPaymentMethod);
|
|
||||||
|
|
||||||
if (!allowProviderPaymentMethod)
|
|
||||||
{
|
|
||||||
return TypedResults.NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId);
|
var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId);
|
||||||
|
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
@ -111,13 +104,6 @@ public class ProviderBillingController(
|
|||||||
[FromRoute] Guid providerId,
|
[FromRoute] Guid providerId,
|
||||||
[FromBody] VerifyBankAccountRequestBody requestBody)
|
[FromBody] VerifyBankAccountRequestBody requestBody)
|
||||||
{
|
{
|
||||||
var allowProviderPaymentMethod = featureService.IsEnabled(FeatureFlagKeys.PM18794_ProviderPaymentMethod);
|
|
||||||
|
|
||||||
if (!allowProviderPaymentMethod)
|
|
||||||
{
|
|
||||||
return TypedResults.NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId);
|
var (provider, result) = await TryGetBillableProviderForAdminOperation(providerId);
|
||||||
|
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
|
###############################################
|
||||||
|
# Build stage #
|
||||||
|
###############################################
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
|
||||||
|
# Docker buildx supplies the value for this arg
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# Determine proper runtime value for .NET
|
||||||
|
# We put the value in a file to be read by later layers.
|
||||||
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
|
RID=linux-x64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
|
RID=linux-arm64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
RID=linux-arm ; \
|
||||||
|
fi \
|
||||||
|
&& echo "RID=$RID" > /tmp/rid.txt
|
||||||
|
|
||||||
|
# Copy required project files
|
||||||
|
WORKDIR /source
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Restore project dependencies and tools
|
||||||
|
WORKDIR /source/src/Api
|
||||||
|
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
RUN . /tmp/rid.txt && dotnet publish \
|
||||||
|
-c release \
|
||||||
|
--no-restore \
|
||||||
|
--self-contained \
|
||||||
|
/p:PublishSingleFile=true \
|
||||||
|
-r $RID \
|
||||||
|
-o out
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# App stage #
|
||||||
|
###############################################
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ENV ASPNETCORE_URLS=http://+:5000
|
||||||
|
ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@ -9,13 +53,11 @@ RUN apt-get update \
|
|||||||
krb5-user \
|
krb5-user \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS http://+:5000
|
# Copy app from the build stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 5000
|
COPY --from=build /source/src/Api/out /app
|
||||||
COPY obj/build-output/publish .
|
COPY ./src/Api/entrypoint.sh /entrypoint.sh
|
||||||
COPY entrypoint.sh /
|
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
@ -5,7 +5,6 @@ using Bit.Core.Context;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Identity;
|
using Bit.Core.Identity;
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
using Bit.Core.SecretsManager.AuthorizationRequirements;
|
||||||
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Entities;
|
using Bit.Core.SecretsManager.Entities;
|
||||||
@ -16,9 +15,6 @@ using Bit.Core.SecretsManager.Queries.Interfaces;
|
|||||||
using Bit.Core.SecretsManager.Queries.Secrets.Interfaces;
|
using Bit.Core.SecretsManager.Queries.Secrets.Interfaces;
|
||||||
using Bit.Core.SecretsManager.Repositories;
|
using Bit.Core.SecretsManager.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -30,7 +26,6 @@ public class SecretsController : Controller
|
|||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IProjectRepository _projectRepository;
|
private readonly IProjectRepository _projectRepository;
|
||||||
private readonly ISecretRepository _secretRepository;
|
private readonly ISecretRepository _secretRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
|
||||||
private readonly ICreateSecretCommand _createSecretCommand;
|
private readonly ICreateSecretCommand _createSecretCommand;
|
||||||
private readonly IUpdateSecretCommand _updateSecretCommand;
|
private readonly IUpdateSecretCommand _updateSecretCommand;
|
||||||
private readonly IDeleteSecretCommand _deleteSecretCommand;
|
private readonly IDeleteSecretCommand _deleteSecretCommand;
|
||||||
@ -39,14 +34,12 @@ public class SecretsController : Controller
|
|||||||
private readonly ISecretAccessPoliciesUpdatesQuery _secretAccessPoliciesUpdatesQuery;
|
private readonly ISecretAccessPoliciesUpdatesQuery _secretAccessPoliciesUpdatesQuery;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
public SecretsController(
|
public SecretsController(
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IProjectRepository projectRepository,
|
IProjectRepository projectRepository,
|
||||||
ISecretRepository secretRepository,
|
ISecretRepository secretRepository,
|
||||||
IOrganizationRepository organizationRepository,
|
|
||||||
ICreateSecretCommand createSecretCommand,
|
ICreateSecretCommand createSecretCommand,
|
||||||
IUpdateSecretCommand updateSecretCommand,
|
IUpdateSecretCommand updateSecretCommand,
|
||||||
IDeleteSecretCommand deleteSecretCommand,
|
IDeleteSecretCommand deleteSecretCommand,
|
||||||
@ -55,13 +48,11 @@ public class SecretsController : Controller
|
|||||||
ISecretAccessPoliciesUpdatesQuery secretAccessPoliciesUpdatesQuery,
|
ISecretAccessPoliciesUpdatesQuery secretAccessPoliciesUpdatesQuery,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IAuthorizationService authorizationService)
|
IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_projectRepository = projectRepository;
|
_projectRepository = projectRepository;
|
||||||
_secretRepository = secretRepository;
|
_secretRepository = secretRepository;
|
||||||
_organizationRepository = organizationRepository;
|
|
||||||
_createSecretCommand = createSecretCommand;
|
_createSecretCommand = createSecretCommand;
|
||||||
_updateSecretCommand = updateSecretCommand;
|
_updateSecretCommand = updateSecretCommand;
|
||||||
_deleteSecretCommand = deleteSecretCommand;
|
_deleteSecretCommand = deleteSecretCommand;
|
||||||
@ -70,7 +61,6 @@ public class SecretsController : Controller
|
|||||||
_secretAccessPoliciesUpdatesQuery = secretAccessPoliciesUpdatesQuery;
|
_secretAccessPoliciesUpdatesQuery = secretAccessPoliciesUpdatesQuery;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -148,9 +138,6 @@ public class SecretsController : Controller
|
|||||||
if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount)
|
if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount)
|
||||||
{
|
{
|
||||||
await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved);
|
await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved);
|
||||||
|
|
||||||
var org = await _organizationRepository.GetByIdAsync(secret.OrganizationId);
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.SmServiceAccountAccessedSecret, org, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SecretResponseModel(secret, access.Read, access.Write);
|
return new SecretResponseModel(secret, access.Read, access.Write);
|
||||||
@ -266,7 +253,7 @@ public class SecretsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
await LogSecretsRetrievalAsync(secrets.First().OrganizationId, secrets);
|
await LogSecretsRetrievalAsync(secrets);
|
||||||
|
|
||||||
var responses = secrets.Select(s => new BaseSecretResponseModel(s));
|
var responses = secrets.Select(s => new BaseSecretResponseModel(s));
|
||||||
return new ListResponseModel<BaseSecretResponseModel>(responses);
|
return new ListResponseModel<BaseSecretResponseModel>(responses);
|
||||||
@ -303,21 +290,18 @@ public class SecretsController : Controller
|
|||||||
|
|
||||||
if (syncResult.HasChanges)
|
if (syncResult.HasChanges)
|
||||||
{
|
{
|
||||||
await LogSecretsRetrievalAsync(organizationId, syncResult.Secrets);
|
await LogSecretsRetrievalAsync(syncResult.Secrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SecretsSyncResponseModel(syncResult.HasChanges, syncResult.Secrets);
|
return new SecretsSyncResponseModel(syncResult.HasChanges, syncResult.Secrets);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogSecretsRetrievalAsync(Guid organizationId, IEnumerable<Secret> secrets)
|
private async Task LogSecretsRetrievalAsync(IEnumerable<Secret> secrets)
|
||||||
{
|
{
|
||||||
if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount)
|
if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User)!.Value;
|
var userId = _userService.GetProperUserId(User)!.Value;
|
||||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
|
||||||
await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, EventType.Secret_Retrieved);
|
await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, EventType.Secret_Retrieved);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.SmServiceAccountAccessedSecret, org, _currentContext));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using Bit.Api.Tools.Models.Request;
|
|||||||
using Bit.Api.Tools.Models.Response;
|
using Bit.Api.Tools.Models.Response;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
@ -33,7 +32,6 @@ public class SendsController : Controller
|
|||||||
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
|
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
|
||||||
private readonly ILogger<SendsController> _logger;
|
private readonly ILogger<SendsController> _logger;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
public SendsController(
|
public SendsController(
|
||||||
ISendRepository sendRepository,
|
ISendRepository sendRepository,
|
||||||
@ -43,8 +41,7 @@ public class SendsController : Controller
|
|||||||
INonAnonymousSendCommand nonAnonymousSendCommand,
|
INonAnonymousSendCommand nonAnonymousSendCommand,
|
||||||
ISendFileStorageService sendFileStorageService,
|
ISendFileStorageService sendFileStorageService,
|
||||||
ILogger<SendsController> logger,
|
ILogger<SendsController> logger,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings)
|
||||||
ICurrentContext currentContext)
|
|
||||||
{
|
{
|
||||||
_sendRepository = sendRepository;
|
_sendRepository = sendRepository;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
@ -54,7 +51,6 @@ public class SendsController : Controller
|
|||||||
_sendFileStorageService = sendFileStorageService;
|
_sendFileStorageService = sendFileStorageService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_currentContext = currentContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Anonymous endpoints
|
#region Anonymous endpoints
|
||||||
|
@ -62,9 +62,9 @@ public static class ApiHelpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventTypeHandlers.ContainsKey(eventGridEvent.EventType))
|
if (eventTypeHandlers.TryGetValue(eventGridEvent.EventType, out var eventTypeHandler))
|
||||||
{
|
{
|
||||||
await eventTypeHandlers[eventGridEvent.EventType](eventGridEvent);
|
await eventTypeHandler(eventGridEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1064,7 +1064,7 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpPut("share")]
|
[HttpPut("share")]
|
||||||
[HttpPost("share")]
|
[HttpPost("share")]
|
||||||
public async Task PutShareMany([FromBody] CipherBulkShareRequestModel model)
|
public async Task<ListResponseModel<CipherMiniResponseModel>> PutShareMany([FromBody] CipherBulkShareRequestModel model)
|
||||||
{
|
{
|
||||||
var organizationId = new Guid(model.Ciphers.First().OrganizationId);
|
var organizationId = new Guid(model.Ciphers.First().OrganizationId);
|
||||||
if (!await _currentContext.OrganizationUser(organizationId))
|
if (!await _currentContext.OrganizationUser(organizationId))
|
||||||
@ -1073,38 +1073,41 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
|
|
||||||
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: false);
|
var ciphers = await _cipherRepository.GetManyByUserIdAsync(userId, withOrganizations: false);
|
||||||
var ciphersDict = ciphers.ToDictionary(c => c.Id);
|
var ciphersDict = ciphers.ToDictionary(c => c.Id);
|
||||||
|
|
||||||
// Validate the model was encrypted for the posting user
|
// Validate the model was encrypted for the posting user
|
||||||
foreach (var cipher in model.Ciphers)
|
foreach (var cipher in model.Ciphers)
|
||||||
{
|
{
|
||||||
if (cipher.EncryptedFor != null)
|
if (cipher.EncryptedFor.HasValue && cipher.EncryptedFor.Value != userId)
|
||||||
{
|
{
|
||||||
if (cipher.EncryptedFor != userId)
|
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
||||||
{
|
|
||||||
throw new BadRequestException("Cipher was not encrypted for the current user. Please try again.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var shareCiphers = new List<(Cipher, DateTime?)>();
|
var shareCiphers = new List<(CipherDetails, DateTime?)>();
|
||||||
foreach (var cipher in model.Ciphers)
|
foreach (var cipher in model.Ciphers)
|
||||||
{
|
{
|
||||||
if (!ciphersDict.ContainsKey(cipher.Id.Value))
|
if (!ciphersDict.TryGetValue(cipher.Id.Value, out var existingCipher))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Trying to move ciphers that you do not own.");
|
throw new BadRequestException("Trying to share ciphers that you do not own.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var existingCipher = ciphersDict[cipher.Id.Value];
|
|
||||||
|
|
||||||
ValidateClientVersionForFido2CredentialSupport(existingCipher);
|
ValidateClientVersionForFido2CredentialSupport(existingCipher);
|
||||||
|
|
||||||
shareCiphers.Add((cipher.ToCipher(existingCipher), cipher.LastKnownRevisionDate));
|
shareCiphers.Add((cipher.ToCipherDetails(existingCipher), cipher.LastKnownRevisionDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cipherService.ShareManyAsync(shareCiphers, organizationId,
|
var updated = await _cipherService.ShareManyAsync(
|
||||||
model.CollectionIds.Select(c => new Guid(c)), userId);
|
shareCiphers,
|
||||||
|
organizationId,
|
||||||
|
model.CollectionIds.Select(Guid.Parse),
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
|
||||||
|
var response = updated.Select(c => new CipherMiniResponseModel(c, _globalSettings, c.OrganizationUseTotp));
|
||||||
|
return new ListResponseModel<CipherMiniResponseModel>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("purge")]
|
[HttpPost("purge")]
|
||||||
@ -1186,14 +1189,14 @@ public class CiphersController : Controller
|
|||||||
var cipher = await GetByIdAsync(id, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
var attachments = cipher?.GetAttachments();
|
var attachments = cipher?.GetAttachments();
|
||||||
|
|
||||||
if (attachments == null || !attachments.ContainsKey(attachmentId) || attachments[attachmentId].Validated)
|
if (attachments == null || !attachments.TryGetValue(attachmentId, out var attachment) || attachment.Validated)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AttachmentUploadDataResponseModel
|
return new AttachmentUploadDataResponseModel
|
||||||
{
|
{
|
||||||
Url = await _attachmentStorageService.GetAttachmentUploadUrlAsync(cipher, attachments[attachmentId]),
|
Url = await _attachmentStorageService.GetAttachmentUploadUrlAsync(cipher, attachment),
|
||||||
FileUploadType = _attachmentStorageService.FileUploadType,
|
FileUploadType = _attachmentStorageService.FileUploadType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1212,11 +1215,10 @@ public class CiphersController : Controller
|
|||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await GetByIdAsync(id, userId);
|
var cipher = await GetByIdAsync(id, userId);
|
||||||
var attachments = cipher?.GetAttachments();
|
var attachments = cipher?.GetAttachments();
|
||||||
if (attachments == null || !attachments.ContainsKey(attachmentId))
|
if (attachments == null || !attachments.TryGetValue(attachmentId, out var attachmentData))
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
var attachmentData = attachments[attachmentId];
|
|
||||||
|
|
||||||
await Request.GetFileAsync(async (stream) =>
|
await Request.GetFileAsync(async (stream) =>
|
||||||
{
|
{
|
||||||
@ -1366,7 +1368,7 @@ public class CiphersController : Controller
|
|||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(cipherId));
|
var cipher = await _cipherRepository.GetByIdAsync(new Guid(cipherId));
|
||||||
var attachments = cipher?.GetAttachments() ?? new Dictionary<string, CipherAttachment.MetaData>();
|
var attachments = cipher?.GetAttachments() ?? new Dictionary<string, CipherAttachment.MetaData>();
|
||||||
|
|
||||||
if (cipher == null || !attachments.ContainsKey(attachmentId) || attachments[attachmentId].Validated)
|
if (cipher == null || !attachments.TryGetValue(attachmentId, out var attachment) || attachment.Validated)
|
||||||
{
|
{
|
||||||
if (_attachmentStorageService is AzureSendFileStorageService azureFileStorageService)
|
if (_attachmentStorageService is AzureSendFileStorageService azureFileStorageService)
|
||||||
{
|
{
|
||||||
@ -1376,7 +1378,7 @@ public class CiphersController : Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _cipherService.ValidateCipherAttachmentFile(cipher, attachments[attachmentId]);
|
await _cipherService.ValidateCipherAttachmentFile(cipher, attachment);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -113,18 +113,25 @@ public class CipherRequestModel
|
|||||||
|
|
||||||
if (hasAttachments2)
|
if (hasAttachments2)
|
||||||
{
|
{
|
||||||
foreach (var attachment in attachments.Where(a => Attachments2.ContainsKey(a.Key)))
|
foreach (var attachment in attachments)
|
||||||
{
|
{
|
||||||
var attachment2 = Attachments2[attachment.Key];
|
if (!Attachments2.TryGetValue(attachment.Key, out var attachment2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
attachment.Value.FileName = attachment2.FileName;
|
attachment.Value.FileName = attachment2.FileName;
|
||||||
attachment.Value.Key = attachment2.Key;
|
attachment.Value.Key = attachment2.Key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (hasAttachments)
|
else if (hasAttachments)
|
||||||
{
|
{
|
||||||
foreach (var attachment in attachments.Where(a => Attachments.ContainsKey(a.Key)))
|
foreach (var attachment in attachments)
|
||||||
{
|
{
|
||||||
attachment.Value.FileName = Attachments[attachment.Key];
|
if (!Attachments.TryGetValue(attachment.Key, out var attachmentForKey))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attachment.Value.FileName = attachmentForKey;
|
||||||
attachment.Value.Key = null;
|
attachment.Value.Key = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,13 +129,13 @@ public class CipherDetailsResponseModel : CipherResponseModel
|
|||||||
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphers, string obj = "cipherDetails")
|
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphers, string obj = "cipherDetails")
|
||||||
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
||||||
{
|
{
|
||||||
if (collectionCiphers?.ContainsKey(cipher.Id) ?? false)
|
if (collectionCiphers?.TryGetValue(cipher.Id, out var collectionCipher) ?? false)
|
||||||
{
|
{
|
||||||
CollectionIds = collectionCiphers[cipher.Id].Select(c => c.CollectionId);
|
CollectionIds = collectionCipher.Select(c => c.CollectionId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CollectionIds = new Guid[] { };
|
CollectionIds = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ public class CipherDetailsResponseModel : CipherResponseModel
|
|||||||
IEnumerable<CollectionCipher> collectionCiphers, string obj = "cipherDetails")
|
IEnumerable<CollectionCipher> collectionCiphers, string obj = "cipherDetails")
|
||||||
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
||||||
{
|
{
|
||||||
CollectionIds = collectionCiphers?.Select(c => c.CollectionId) ?? new List<Guid>();
|
CollectionIds = collectionCiphers?.Select(c => c.CollectionId) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherDetailsResponseModel(
|
public CipherDetailsResponseModel(
|
||||||
@ -158,7 +158,7 @@ public class CipherDetailsResponseModel : CipherResponseModel
|
|||||||
string obj = "cipherDetails")
|
string obj = "cipherDetails")
|
||||||
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
: base(cipher, user, organizationAbilities, globalSettings, obj)
|
||||||
{
|
{
|
||||||
CollectionIds = cipher.CollectionIds ?? new List<Guid>();
|
CollectionIds = cipher.CollectionIds ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Guid> CollectionIds { get; set; }
|
public IEnumerable<Guid> CollectionIds { get; set; }
|
||||||
@ -170,13 +170,13 @@ public class CipherMiniDetailsResponseModel : CipherMiniResponseModel
|
|||||||
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphers, bool orgUseTotp, string obj = "cipherMiniDetails")
|
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphers, bool orgUseTotp, string obj = "cipherMiniDetails")
|
||||||
: base(cipher, globalSettings, orgUseTotp, obj)
|
: base(cipher, globalSettings, orgUseTotp, obj)
|
||||||
{
|
{
|
||||||
if (collectionCiphers?.ContainsKey(cipher.Id) ?? false)
|
if (collectionCiphers?.TryGetValue(cipher.Id, out var collectionCipher) ?? false)
|
||||||
{
|
{
|
||||||
CollectionIds = collectionCiphers[cipher.Id].Select(c => c.CollectionId);
|
CollectionIds = collectionCipher.Select(c => c.CollectionId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CollectionIds = new Guid[] { };
|
CollectionIds = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ public class CipherMiniDetailsResponseModel : CipherMiniResponseModel
|
|||||||
GlobalSettings globalSettings, bool orgUseTotp, string obj = "cipherMiniDetails")
|
GlobalSettings globalSettings, bool orgUseTotp, string obj = "cipherMiniDetails")
|
||||||
: base(cipher, globalSettings, orgUseTotp, obj)
|
: base(cipher, globalSettings, orgUseTotp, obj)
|
||||||
{
|
{
|
||||||
CollectionIds = cipher.CollectionIds ?? new List<Guid>();
|
CollectionIds = cipher.CollectionIds ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherMiniDetailsResponseModel(CipherOrganizationDetailsWithCollections cipher,
|
public CipherMiniDetailsResponseModel(CipherOrganizationDetailsWithCollections cipher,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
@ -19,31 +19,36 @@ then
|
|||||||
LGID=65534
|
LGID=65534
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create user and group
|
if [ "$(id -u)" = "0" ]
|
||||||
|
then
|
||||||
|
# Create user and group
|
||||||
|
|
||||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||||
mkhomedir_helper $USERNAME
|
mkhomedir_helper $USERNAME
|
||||||
|
|
||||||
# The rest...
|
# The rest...
|
||||||
|
|
||||||
chown -R $USERNAME:$GROUPNAME /app
|
chown -R $USERNAME:$GROUPNAME /app
|
||||||
mkdir -p /etc/bitwarden/core
|
mkdir -p /etc/bitwarden/core
|
||||||
mkdir -p /etc/bitwarden/logs
|
mkdir -p /etc/bitwarden/logs
|
||||||
mkdir -p /etc/bitwarden/ca-certificates
|
mkdir -p /etc/bitwarden/ca-certificates
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||||
|
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
cp /etc/bitwarden/ca-certificates/*.crt /usr/local/share/ca-certificates/ >/dev/null 2>&1 \
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
||||||
&& update-ca-certificates
|
fi
|
||||||
|
|
||||||
|
gosu_cmd="gosu $USERNAME:$GROUPNAME"
|
||||||
|
else
|
||||||
|
gosu_cmd=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
if [[ -f "/etc/bitwarden/kerberos/bitwarden.keytab" && -f "/etc/bitwarden/kerberos/krb5.conf" ]]; then
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden/kerberos
|
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
||||||
cp -f /etc/bitwarden/kerberos/krb5.conf /etc/krb5.conf
|
$gosu_cmd kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
||||||
gosu $USERNAME:$GROUPNAME kinit $globalSettings__kerberosUser -k -t /etc/bitwarden/kerberos/bitwarden.keytab
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gosu $USERNAME:$GROUPNAME dotnet /app/Api.dll
|
exec $gosu_cmd /app/Api
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
*
|
|
||||||
!obj/build-output/publish/*
|
|
||||||
!obj/Docker/empty/
|
|
||||||
!entrypoint.sh
|
|
@ -28,8 +28,8 @@ public class AppleController : Controller
|
|||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = HttpContext.Request.Query.ContainsKey("key") ?
|
var key = HttpContext.Request.Query.TryGetValue("key", out var keyValue) ?
|
||||||
HttpContext.Request.Query["key"].ToString() : null;
|
keyValue.ToString() : null;
|
||||||
if (!CoreHelpers.FixedTimeEquals(key, _billingSettings.AppleWebhookKey))
|
if (!CoreHelpers.FixedTimeEquals(key, _billingSettings.AppleWebhookKey))
|
||||||
{
|
{
|
||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
|
@ -51,8 +51,8 @@ public class PayPalController : Controller
|
|||||||
[HttpPost("ipn")]
|
[HttpPost("ipn")]
|
||||||
public async Task<IActionResult> PostIpn()
|
public async Task<IActionResult> PostIpn()
|
||||||
{
|
{
|
||||||
var key = HttpContext.Request.Query.ContainsKey("key")
|
var key = HttpContext.Request.Query.TryGetValue("key", out var keyValue)
|
||||||
? HttpContext.Request.Query["key"].ToString()
|
? keyValue.ToString()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
|
@ -1,6 +1,50 @@
|
|||||||
|
###############################################
|
||||||
|
# Build stage #
|
||||||
|
###############################################
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
|
||||||
|
# Docker buildx supplies the value for this arg
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# Determine proper runtime value for .NET
|
||||||
|
# We put the value in a file to be read by later layers.
|
||||||
|
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
|
RID=linux-x64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
|
||||||
|
RID=linux-arm64 ; \
|
||||||
|
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
|
||||||
|
RID=linux-arm ; \
|
||||||
|
fi \
|
||||||
|
&& echo "RID=$RID" > /tmp/rid.txt
|
||||||
|
|
||||||
|
# Copy required project files
|
||||||
|
WORKDIR /source
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
# Restore project dependencies and tools
|
||||||
|
WORKDIR /source/src/Billing
|
||||||
|
RUN . /tmp/rid.txt && dotnet restore -r $RID
|
||||||
|
|
||||||
|
# Build project
|
||||||
|
RUN . /tmp/rid.txt && dotnet publish \
|
||||||
|
-c release \
|
||||||
|
--no-restore \
|
||||||
|
--self-contained \
|
||||||
|
/p:PublishSingleFile=true \
|
||||||
|
-r $RID \
|
||||||
|
-o out
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# App stage #
|
||||||
|
###############################################
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
LABEL com.bitwarden.product="bitwarden"
|
LABEL com.bitwarden.product="bitwarden"
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production
|
||||||
|
ENV ASPNETCORE_URLS=http://+:5000
|
||||||
|
ENV SSL_CERT_DIR=/etc/bitwarden/ca-certificates
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@ -8,14 +52,11 @@ RUN apt-get update \
|
|||||||
curl \
|
curl \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS http://+:5000
|
# Copy app from the build stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
EXPOSE 5000
|
COPY --from=build /source/src/Billing/out /app
|
||||||
COPY entrypoint.sh /
|
COPY ./src/Billing/entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
COPY obj/build-output/publish .
|
|
||||||
|
|
||||||
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
HEALTHCHECK CMD curl -f http://localhost:5000/alive || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
@ -20,8 +20,8 @@ public class Program
|
|||||||
return e.Level >= globalSettings.MinLogLevel.BillingSettings.Jobs;
|
return e.Level >= globalSettings.MinLogLevel.BillingSettings.Jobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Properties.ContainsKey("RequestPath") &&
|
if (e.Properties.TryGetValue("RequestPath", out var requestPath) &&
|
||||||
!string.IsNullOrWhiteSpace(e.Properties["RequestPath"]?.ToString()) &&
|
!string.IsNullOrWhiteSpace(requestPath?.ToString()) &&
|
||||||
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
(context.Contains(".Server.Kestrel") || context.Contains(".Core.IISHttpServer")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Event = Stripe.Event;
|
using Event = Stripe.Event;
|
||||||
|
|
||||||
namespace Bit.Billing.Services.Implementations;
|
namespace Bit.Billing.Services.Implementations;
|
||||||
@ -10,23 +6,17 @@ namespace Bit.Billing.Services.Implementations;
|
|||||||
public class CustomerUpdatedHandler : ICustomerUpdatedHandler
|
public class CustomerUpdatedHandler : ICustomerUpdatedHandler
|
||||||
{
|
{
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
private readonly IStripeEventService _stripeEventService;
|
private readonly IStripeEventService _stripeEventService;
|
||||||
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
||||||
private readonly ILogger<CustomerUpdatedHandler> _logger;
|
private readonly ILogger<CustomerUpdatedHandler> _logger;
|
||||||
|
|
||||||
public CustomerUpdatedHandler(
|
public CustomerUpdatedHandler(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ICurrentContext currentContext,
|
|
||||||
IStripeEventService stripeEventService,
|
IStripeEventService stripeEventService,
|
||||||
IStripeEventUtilityService stripeEventUtilityService,
|
IStripeEventUtilityService stripeEventUtilityService,
|
||||||
ILogger<CustomerUpdatedHandler> logger)
|
ILogger<CustomerUpdatedHandler> logger)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository ?? throw new ArgumentNullException(nameof(organizationRepository));
|
_organizationRepository = organizationRepository ?? throw new ArgumentNullException(nameof(organizationRepository));
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_currentContext = currentContext;
|
|
||||||
_stripeEventService = stripeEventService;
|
_stripeEventService = stripeEventService;
|
||||||
_stripeEventUtilityService = stripeEventUtilityService;
|
_stripeEventUtilityService = stripeEventUtilityService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -95,20 +85,5 @@ public class CustomerUpdatedHandler : ICustomerUpdatedHandler
|
|||||||
|
|
||||||
organization.BillingEmail = customer.Email;
|
organization.BillingEmail = customer.Email;
|
||||||
await _organizationRepository.ReplaceAsync(organization);
|
await _organizationRepository.ReplaceAsync(organization);
|
||||||
|
|
||||||
if (_referenceEventService == null)
|
|
||||||
{
|
|
||||||
_logger.LogError("ReferenceEventService was not initialized in CustomerUpdatedHandler");
|
|
||||||
throw new InvalidOperationException($"{nameof(_referenceEventService)} is not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentContext == null)
|
|
||||||
{
|
|
||||||
_logger.LogError("CurrentContext was not initialized in CustomerUpdatedHandler");
|
|
||||||
throw new InvalidOperationException($"{nameof(_currentContext)} is not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.OrganizationEditedInStripe, organization, _currentContext));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,9 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Platform.Push;
|
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Event = Stripe.Event;
|
using Event = Stripe.Event;
|
||||||
|
|
||||||
namespace Bit.Billing.Services.Implementations;
|
namespace Bit.Billing.Services.Implementations;
|
||||||
@ -22,9 +18,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
private readonly IStripeFacade _stripeFacade;
|
private readonly IStripeFacade _stripeFacade;
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
private readonly IUserRepository _userRepository;
|
|
||||||
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
private readonly IOrganizationEnableCommand _organizationEnableCommand;
|
||||||
@ -36,9 +29,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
IStripeFacade stripeFacade,
|
IStripeFacade stripeFacade,
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ICurrentContext currentContext,
|
|
||||||
IUserRepository userRepository,
|
|
||||||
IStripeEventUtilityService stripeEventUtilityService,
|
IStripeEventUtilityService stripeEventUtilityService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IPushNotificationService pushNotificationService,
|
IPushNotificationService pushNotificationService,
|
||||||
@ -50,9 +40,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
_stripeFacade = stripeFacade;
|
_stripeFacade = stripeFacade;
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_currentContext = currentContext;
|
|
||||||
_userRepository = userRepository;
|
|
||||||
_stripeEventUtilityService = stripeEventUtilityService;
|
_stripeEventUtilityService = stripeEventUtilityService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_pushNotificationService = pushNotificationService;
|
_pushNotificationService = pushNotificationService;
|
||||||
@ -116,27 +103,7 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
_logger.LogError("invoice.payment_succeeded webhook ({EventID}) for Provider ({ProviderID}) indicates missing subscription line items",
|
_logger.LogError("invoice.payment_succeeded webhook ({EventID}) for Provider ({ProviderID}) indicates missing subscription line items",
|
||||||
parsedEvent.Id,
|
parsedEvent.Id,
|
||||||
provider.Id);
|
provider.Id);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent
|
|
||||||
{
|
|
||||||
Type = ReferenceEventType.Rebilled,
|
|
||||||
Source = ReferenceEventSource.Provider,
|
|
||||||
Id = provider.Id,
|
|
||||||
PlanType = PlanType.TeamsMonthly,
|
|
||||||
Seats = (int)teamsMonthlyLineItem.Quantity
|
|
||||||
});
|
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent
|
|
||||||
{
|
|
||||||
Type = ReferenceEventType.Rebilled,
|
|
||||||
Source = ReferenceEventSource.Provider,
|
|
||||||
Id = provider.Id,
|
|
||||||
PlanType = PlanType.EnterpriseMonthly,
|
|
||||||
Seats = (int)enterpriseMonthlyLineItem.Quantity
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else if (organizationId.HasValue)
|
else if (organizationId.HasValue)
|
||||||
{
|
{
|
||||||
@ -156,15 +123,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
|
|
||||||
await _organizationEnableCommand.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
await _organizationEnableCommand.EnableAsync(organizationId.Value, subscription.CurrentPeriodEnd);
|
||||||
await _pushNotificationService.PushSyncOrganizationStatusAsync(organization);
|
await _pushNotificationService.PushSyncOrganizationStatusAsync(organization);
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Rebilled, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = organization?.Plan,
|
|
||||||
PlanType = organization?.PlanType,
|
|
||||||
Seats = organization?.Seats,
|
|
||||||
Storage = organization?.MaxStorageGb,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else if (userId.HasValue)
|
else if (userId.HasValue)
|
||||||
{
|
{
|
||||||
@ -174,14 +132,6 @@ public class PaymentSucceededHandler : IPaymentSucceededHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _userService.EnablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd);
|
await _userService.EnablePremiumAsync(userId.Value, subscription.CurrentPeriodEnd);
|
||||||
|
|
||||||
var user = await _userRepository.GetByIdAsync(userId.Value);
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Rebilled, user, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = IStripeEventUtilityService.PremiumPlanId,
|
|
||||||
Storage = user?.MaxStorageGb,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
@ -19,25 +19,27 @@ then
|
|||||||
LGID=65534
|
LGID=65534
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create user and group
|
if [ "$(id -u)" = "0" ]
|
||||||
|
then
|
||||||
|
# Create user and group
|
||||||
|
|
||||||
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 ||
|
||||||
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1
|
||||||
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 ||
|
||||||
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1
|
||||||
mkhomedir_helper $USERNAME
|
mkhomedir_helper $USERNAME
|
||||||
|
|
||||||
# The rest...
|
# The rest...
|
||||||
|
|
||||||
chown -R $USERNAME:$GROUPNAME /app
|
chown -R $USERNAME:$GROUPNAME /app
|
||||||
mkdir -p /etc/bitwarden/core
|
mkdir -p /etc/bitwarden/core
|
||||||
mkdir -p /etc/bitwarden/logs
|
mkdir -p /etc/bitwarden/logs
|
||||||
mkdir -p /etc/bitwarden/ca-certificates
|
mkdir -p /etc/bitwarden/ca-certificates
|
||||||
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
chown -R $USERNAME:$GROUPNAME /etc/bitwarden
|
||||||
|
|
||||||
if [[ $globalSettings__selfHosted == "true" ]]; then
|
gosu_cmd="gosu $USERNAME:$GROUPNAME"
|
||||||
cp /etc/bitwarden/ca-certificates/*.crt /usr/local/share/ca-certificates/ >/dev/null 2>&1 \
|
else
|
||||||
&& update-ca-certificates
|
gosu_cmd=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec gosu $USERNAME:$GROUPNAME dotnet /app/Billing.dll
|
exec $gosu_cmd /app/Billing
|
||||||
|
@ -8,14 +8,13 @@ using Bit.Core.Entities;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Tools.Entities;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Entities;
|
namespace Bit.Core.AdminConsole.Entities;
|
||||||
|
|
||||||
public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable, IReferenceable
|
public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable
|
||||||
{
|
{
|
||||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider>? _twoFactorProviders;
|
private Dictionary<TwoFactorProviderType, TwoFactorProvider>? _twoFactorProviders;
|
||||||
|
|
||||||
@ -258,12 +257,12 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
|||||||
public bool TwoFactorProviderIsEnabled(TwoFactorProviderType provider)
|
public bool TwoFactorProviderIsEnabled(TwoFactorProviderType provider)
|
||||||
{
|
{
|
||||||
var providers = GetTwoFactorProviders();
|
var providers = GetTwoFactorProviders();
|
||||||
if (providers == null || !providers.ContainsKey(provider))
|
if (providers == null || !providers.TryGetValue(provider, out var twoFactorProvider))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return providers[provider].Enabled && Use2fa;
|
return twoFactorProvider.Enabled && Use2fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TwoFactorIsEnabled()
|
public bool TwoFactorIsEnabled()
|
||||||
@ -280,12 +279,7 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
|||||||
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
||||||
{
|
{
|
||||||
var providers = GetTwoFactorProviders();
|
var providers = GetTwoFactorProviders();
|
||||||
if (providers == null || !providers.ContainsKey(provider))
|
return providers?.GetValueOrDefault(provider);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return providers[provider];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFromLicense(OrganizationLicense license, IFeatureService featureService)
|
public void UpdateFromLicense(OrganizationLicense license, IFeatureService featureService)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using Bit.Core.Enums;
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public interface IIntegrationMessage
|
public interface IIntegrationMessage
|
||||||
{
|
{
|
||||||
IntegrationType IntegrationType { get; }
|
IntegrationType IntegrationType { get; }
|
||||||
int RetryCount { get; set; }
|
string MessageId { get; set; }
|
||||||
DateTime? DelayUntilDate { get; set; }
|
int RetryCount { get; }
|
||||||
|
DateTime? DelayUntilDate { get; }
|
||||||
void ApplyRetry(DateTime? handlerDelayUntilDate);
|
void ApplyRetry(DateTime? handlerDelayUntilDate);
|
||||||
string ToJson();
|
string ToJson();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public class IntegrationHandlerResult
|
public class IntegrationHandlerResult
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
using System.Text.Json;
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public class IntegrationMessage<T> : IIntegrationMessage
|
public class IntegrationMessage : IIntegrationMessage
|
||||||
{
|
{
|
||||||
public IntegrationType IntegrationType { get; set; }
|
public IntegrationType IntegrationType { get; set; }
|
||||||
public T Configuration { get; set; }
|
public required string MessageId { get; set; }
|
||||||
public string RenderedTemplate { get; set; }
|
public required string RenderedTemplate { get; set; }
|
||||||
public int RetryCount { get; set; } = 0;
|
public int RetryCount { get; set; } = 0;
|
||||||
public DateTime? DelayUntilDate { get; set; }
|
public DateTime? DelayUntilDate { get; set; }
|
||||||
|
|
||||||
@ -22,12 +24,22 @@ public class IntegrationMessage<T> : IIntegrationMessage
|
|||||||
DelayUntilDate = baseTime.AddSeconds(backoffSeconds + jitterSeconds);
|
DelayUntilDate = baseTime.AddSeconds(backoffSeconds + jitterSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToJson()
|
public virtual string ToJson()
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IntegrationMessage<T> : IntegrationMessage
|
||||||
|
{
|
||||||
|
public required T Configuration { get; set; }
|
||||||
|
|
||||||
|
public override string ToJson()
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(this);
|
return JsonSerializer.Serialize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntegrationMessage<T> FromJson(string json)
|
public static IntegrationMessage<T>? FromJson(string json)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Deserialize<IntegrationMessage<T>>(json);
|
return JsonSerializer.Deserialize<IntegrationMessage<T>>(json);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public record SlackIntegration(string token);
|
public record SlackIntegration(string token);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public record SlackIntegrationConfiguration(string channelId);
|
public record SlackIntegrationConfiguration(string channelId);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public record SlackIntegrationConfigurationDetails(string channelId, string token);
|
public record SlackIntegrationConfigurationDetails(string channelId, string token);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public record WebhookIntegrationConfiguration(string url);
|
public record WebhookIntegrationConfiguration(string url);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
public record WebhookIntegrationConfigurationDetails(string url);
|
public record WebhookIntegrationConfigurationDetails(string url);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Slack;
|
namespace Bit.Core.Models.Slack;
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups;
|
||||||
|
|
||||||
@ -18,21 +14,16 @@ public class CreateGroupCommand : ICreateGroupCommand
|
|||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
private readonly IGroupRepository _groupRepository;
|
private readonly IGroupRepository _groupRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
public CreateGroupCommand(
|
public CreateGroupCommand(
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IGroupRepository groupRepository,
|
IGroupRepository groupRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository
|
||||||
IReferenceEventService referenceEventService,
|
)
|
||||||
ICurrentContext currentContext)
|
|
||||||
{
|
{
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_groupRepository = groupRepository;
|
_groupRepository = groupRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_currentContext = currentContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateGroupAsync(Group group, Organization organization,
|
public async Task CreateGroupAsync(Group group, Organization organization,
|
||||||
@ -77,8 +68,6 @@ public class CreateGroupCommand : ICreateGroupCommand
|
|||||||
{
|
{
|
||||||
await _groupRepository.CreateAsync(group, collections);
|
await _groupRepository.CreateAsync(group, collections);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.GroupCreated, organization, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GroupRepositoryUpdateUsersAsync(Group group, IEnumerable<Guid> userIds,
|
private async Task GroupRepositoryUpdateUsersAsync(Group group, IEnumerable<Guid> userIds,
|
||||||
|
@ -7,9 +7,6 @@ using Bit.Core.Exceptions;
|
|||||||
using Bit.Core.Platform.Push;
|
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -24,7 +21,6 @@ public class DeleteClaimedOrganizationUserAccountCommand : IDeleteClaimedOrganiz
|
|||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly IPushNotificationService _pushService;
|
private readonly IPushNotificationService _pushService;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IProviderUserRepository _providerUserRepository;
|
private readonly IProviderUserRepository _providerUserRepository;
|
||||||
@ -36,7 +32,6 @@ public class DeleteClaimedOrganizationUserAccountCommand : IDeleteClaimedOrganiz
|
|||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IPushNotificationService pushService,
|
IPushNotificationService pushService,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IProviderUserRepository providerUserRepository)
|
IProviderUserRepository providerUserRepository)
|
||||||
@ -48,7 +43,6 @@ public class DeleteClaimedOrganizationUserAccountCommand : IDeleteClaimedOrganiz
|
|||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_pushService = pushService;
|
_pushService = pushService;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_providerUserRepository = providerUserRepository;
|
_providerUserRepository = providerUserRepository;
|
||||||
@ -195,8 +189,6 @@ public class DeleteClaimedOrganizationUserAccountCommand : IDeleteClaimedOrganiz
|
|||||||
await _userRepository.DeleteManyAsync(users);
|
await _userRepository.DeleteManyAsync(users);
|
||||||
foreach (var user in users)
|
foreach (var user in users)
|
||||||
{
|
{
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, user, _currentContext));
|
|
||||||
await _pushService.PushLogOutAsync(user.Id);
|
await _pushService.PushLogOutAsync(user.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,15 +9,11 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.AdminConsole.Utilities.Commands;
|
using Bit.Core.AdminConsole.Utilities.Commands;
|
||||||
using Bit.Core.AdminConsole.Utilities.Errors;
|
using Bit.Core.AdminConsole.Utilities.Errors;
|
||||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||||
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
|
using OrganizationUserInvite = Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models.OrganizationUserInvite;
|
||||||
|
|
||||||
@ -28,8 +24,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
IInviteUsersValidator inviteUsersValidator,
|
IInviteUsersValidator inviteUsersValidator,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ICurrentContext currentContext,
|
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
ILogger<InviteOrganizationUsersCommand> logger,
|
ILogger<InviteOrganizationUsersCommand> logger,
|
||||||
@ -121,8 +115,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
await SendAdditionalEmailsAsync(validatedRequest, organization);
|
await SendAdditionalEmailsAsync(validatedRequest, organization);
|
||||||
|
|
||||||
await SendInvitesAsync(organizationUserToInviteEntities, organization);
|
await SendInvitesAsync(organizationUserToInviteEntities, organization);
|
||||||
|
|
||||||
await PublishReferenceEventAsync(validatedRequest, organization);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -190,14 +182,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PublishReferenceEventAsync(Valid<InviteOrganizationUsersValidationRequest> validatedResult,
|
|
||||||
Organization organization) =>
|
|
||||||
await referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, currentContext)
|
|
||||||
{
|
|
||||||
Users = validatedResult.Value.Invites.Length
|
|
||||||
});
|
|
||||||
|
|
||||||
private async Task SendInvitesAsync(IEnumerable<CreateOrganizationUser> users, Organization organization) =>
|
private async Task SendInvitesAsync(IEnumerable<CreateOrganizationUser> users, Organization organization) =>
|
||||||
await sendOrganizationInvitesCommand.SendInvitesAsync(
|
await sendOrganizationInvitesCommand.SendInvitesAsync(
|
||||||
new SendInvitesRequest(
|
new SendInvitesRequest(
|
||||||
@ -284,15 +268,6 @@ public class InviteOrganizationUsersCommand(IEventService eventService,
|
|||||||
|
|
||||||
await organizationRepository.ReplaceAsync(organization); // could optimize this with only a property update
|
await organizationRepository.ReplaceAsync(organization); // could optimize this with only a property update
|
||||||
await applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
await applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||||
|
|
||||||
await referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization, currentContext)
|
|
||||||
{
|
|
||||||
PlanName = validatedResult.Value.InviteOrganization.Plan.Name,
|
|
||||||
PlanType = validatedResult.Value.InviteOrganization.Plan.Type,
|
|
||||||
Seats = validatedResult.Value.PasswordManagerSubscriptionUpdate.UpdatedSeatTotal,
|
|
||||||
PreviousSeats = validatedResult.Value.PasswordManagerSubscriptionUpdate.Seats
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using Bit.Core.Billing.Enums;
|
|||||||
using Bit.Core.Billing.Models.Sales;
|
using Bit.Core.Billing.Models.Sales;
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
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;
|
||||||
@ -15,9 +14,6 @@ using Bit.Core.Models.StaticStore;
|
|||||||
using Bit.Core.Platform.Push;
|
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
@ -36,8 +32,6 @@ public class CloudOrganizationSignUpCommand(
|
|||||||
IOrganizationBillingService organizationBillingService,
|
IOrganizationBillingService organizationBillingService,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ICurrentContext currentContext,
|
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
@ -132,17 +126,6 @@ public class CloudOrganizationSignUpCommand(
|
|||||||
|
|
||||||
var ownerId = signup.IsFromProvider ? default : signup.Owner.Id;
|
var ownerId = signup.IsFromProvider ? default : signup.Owner.Id;
|
||||||
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
|
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
|
||||||
await referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Signup, organization, currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Seats = returnValue.Item1.Seats,
|
|
||||||
SignupInitiationPath = signup.InitiationPath,
|
|
||||||
Storage = returnValue.Item1.MaxStorageGb,
|
|
||||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
|
||||||
});
|
|
||||||
|
|
||||||
return new SignUpOrganizationResponse(returnValue.organization, returnValue.organizationUser);
|
return new SignUpOrganizationResponse(returnValue.organization, returnValue.organizationUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,38 +2,28 @@
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
public class OrganizationDeleteCommand : IOrganizationDeleteCommand
|
public class OrganizationDeleteCommand : IOrganizationDeleteCommand
|
||||||
{
|
{
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IPaymentService _paymentService;
|
private readonly IPaymentService _paymentService;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
|
|
||||||
public OrganizationDeleteCommand(
|
public OrganizationDeleteCommand(
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
ICurrentContext currentContext,
|
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IPaymentService paymentService,
|
IPaymentService paymentService,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
ISsoConfigRepository ssoConfigRepository)
|
ISsoConfigRepository ssoConfigRepository)
|
||||||
{
|
{
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_currentContext = currentContext;
|
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_paymentService = paymentService;
|
_paymentService = paymentService;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_ssoConfigRepository = ssoConfigRepository;
|
_ssoConfigRepository = ssoConfigRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,8 +38,6 @@ public class OrganizationDeleteCommand : IOrganizationDeleteCommand
|
|||||||
var eop = !organization.ExpirationDate.HasValue ||
|
var eop = !organization.ExpirationDate.HasValue ||
|
||||||
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
organization.ExpirationDate.Value >= DateTime.UtcNow;
|
||||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.DeleteAccount, organization, _currentContext));
|
|
||||||
}
|
}
|
||||||
catch (GatewayException) { }
|
catch (GatewayException) { }
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,6 @@ using Bit.Core.Models.Business;
|
|||||||
using Bit.Core.Models.StaticStore;
|
using Bit.Core.Models.StaticStore;
|
||||||
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.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
@ -37,7 +34,6 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
|||||||
|
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
@ -46,7 +42,6 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
|||||||
public ProviderClientOrganizationSignUpCommand(
|
public ProviderClientOrganizationSignUpCommand(
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IPricingClient pricingClient,
|
IPricingClient pricingClient,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
@ -54,7 +49,6 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
|||||||
{
|
{
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
@ -108,16 +102,6 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
|||||||
|
|
||||||
var returnValue = await SignUpAsync(organization, signup.CollectionName);
|
var returnValue = await SignUpAsync(organization, signup.CollectionName);
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Seats = returnValue.Organization.Seats,
|
|
||||||
SignupInitiationPath = signup.InitiationPath,
|
|
||||||
Storage = returnValue.Organization.MaxStorageGb,
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +104,8 @@ public class SavePolicyCommand : ISavePolicyCommand
|
|||||||
var dependentPolicyTypes = _policyValidators.Values
|
var dependentPolicyTypes = _policyValidators.Values
|
||||||
.Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyUpdate.Type))
|
.Where(otherValidator => otherValidator.RequiredPolicies.Contains(policyUpdate.Type))
|
||||||
.Select(otherValidator => otherValidator.Type)
|
.Select(otherValidator => otherValidator.Type)
|
||||||
.Where(otherPolicyType => savedPoliciesDict.ContainsKey(otherPolicyType) &&
|
.Where(otherPolicyType => savedPoliciesDict.TryGetValue(otherPolicyType, out var savedPolicy) &&
|
||||||
savedPoliciesDict[otherPolicyType].Enabled)
|
savedPolicy.Enabled)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
switch (dependentPolicyTypes)
|
switch (dependentPolicyTypes)
|
||||||
|
@ -1,13 +1,87 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
public abstract class EventLoggingListenerService : BackgroundService
|
public abstract class EventLoggingListenerService : BackgroundService
|
||||||
{
|
{
|
||||||
protected readonly IEventMessageHandler _handler;
|
protected readonly IEventMessageHandler _handler;
|
||||||
|
protected ILogger<EventLoggingListenerService> _logger;
|
||||||
|
|
||||||
protected EventLoggingListenerService(IEventMessageHandler handler)
|
protected EventLoggingListenerService(IEventMessageHandler handler, ILogger<EventLoggingListenerService> logger)
|
||||||
{
|
{
|
||||||
_handler = handler ?? throw new ArgumentNullException(nameof(handler));
|
_handler = handler;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task ProcessReceivedMessageAsync(string body, string? messageId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var jsonDocument = JsonDocument.Parse(body);
|
||||||
|
var root = jsonDocument.RootElement;
|
||||||
|
|
||||||
|
if (root.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var eventMessages = root.Deserialize<IEnumerable<EventMessage>>();
|
||||||
|
await _handler.HandleManyEventsAsync(eventMessages);
|
||||||
|
}
|
||||||
|
else if (root.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
var eventMessage = root.Deserialize<EventMessage>();
|
||||||
|
await _handler.HandleEventAsync(eventMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(messageId))
|
||||||
|
{
|
||||||
|
_logger.LogError("An error occurred while processing message: {MessageId} - Invalid JSON", messageId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError("An Invalid JSON error occurred while processing a message with an empty message id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (JsonException exception)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(messageId))
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
exception,
|
||||||
|
"An error occurred while processing message: {MessageId} - Invalid JSON",
|
||||||
|
messageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
exception,
|
||||||
|
"An Invalid JSON error occurred while processing a message with an empty message id"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(messageId))
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
exception,
|
||||||
|
"An error occurred while processing message: {MessageId}",
|
||||||
|
messageId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
exception,
|
||||||
|
"An error occurred while processing a message with an empty message id"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/Core/AdminConsole/Services/IAzureServiceBusService.cs
Normal file
10
src/Core/AdminConsole/Services/IAzureServiceBusService.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Azure.Messaging.ServiceBus;
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public interface IAzureServiceBusService : IEventIntegrationPublisher, IAsyncDisposable
|
||||||
|
{
|
||||||
|
ServiceBusProcessor CreateProcessor(string topicName, string subscriptionName, ServiceBusProcessorOptions options);
|
||||||
|
Task PublishToRetryAsync(IIntegrationMessage message);
|
||||||
|
}
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
public interface IIntegrationPublisher
|
public interface IEventIntegrationPublisher : IAsyncDisposable
|
||||||
{
|
{
|
||||||
Task PublishAsync(IIntegrationMessage message);
|
Task PublishAsync(IIntegrationMessage message);
|
||||||
|
Task PublishEventAsync(string body);
|
||||||
}
|
}
|
19
src/Core/AdminConsole/Services/IRabbitMqService.cs
Normal file
19
src/Core/AdminConsole/Services/IRabbitMqService.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using RabbitMQ.Client.Events;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public interface IRabbitMqService : IEventIntegrationPublisher
|
||||||
|
{
|
||||||
|
Task<IChannel> CreateChannelAsync(CancellationToken cancellationToken = default);
|
||||||
|
Task CreateEventQueueAsync(string queueName, CancellationToken cancellationToken = default);
|
||||||
|
Task CreateIntegrationQueuesAsync(
|
||||||
|
string queueName,
|
||||||
|
string retryQueueName,
|
||||||
|
string routingKey,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
Task PublishToRetryAsync(IChannel channel, IIntegrationMessage message, CancellationToken cancellationToken);
|
||||||
|
Task PublishToDeadLetterAsync(IChannel channel, IIntegrationMessage message, CancellationToken cancellationToken);
|
||||||
|
Task RepublishToRetryQueueAsync(IChannel channel, BasicDeliverEventArgs eventArgs);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System.Text;
|
#nullable enable
|
||||||
using System.Text.Json;
|
|
||||||
|
using System.Text;
|
||||||
using Azure.Messaging.ServiceBus;
|
using Azure.Messaging.ServiceBus;
|
||||||
using Bit.Core.Models.Data;
|
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -9,67 +9,47 @@ namespace Bit.Core.Services;
|
|||||||
|
|
||||||
public class AzureServiceBusEventListenerService : EventLoggingListenerService
|
public class AzureServiceBusEventListenerService : EventLoggingListenerService
|
||||||
{
|
{
|
||||||
private readonly ILogger<AzureServiceBusEventListenerService> _logger;
|
|
||||||
private readonly ServiceBusClient _client;
|
|
||||||
private readonly ServiceBusProcessor _processor;
|
private readonly ServiceBusProcessor _processor;
|
||||||
|
|
||||||
public AzureServiceBusEventListenerService(
|
public AzureServiceBusEventListenerService(
|
||||||
IEventMessageHandler handler,
|
IEventMessageHandler handler,
|
||||||
ILogger<AzureServiceBusEventListenerService> logger,
|
IAzureServiceBusService serviceBusService,
|
||||||
|
string subscriptionName,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
string subscriptionName) : base(handler)
|
ILogger<AzureServiceBusEventListenerService> logger) : base(handler, logger)
|
||||||
{
|
{
|
||||||
_client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString);
|
_processor = serviceBusService.CreateProcessor(
|
||||||
_processor = _client.CreateProcessor(globalSettings.EventLogging.AzureServiceBus.EventTopicName, subscriptionName, new ServiceBusProcessorOptions());
|
globalSettings.EventLogging.AzureServiceBus.EventTopicName,
|
||||||
|
subscriptionName,
|
||||||
|
new ServiceBusProcessorOptions());
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_processor.ProcessMessageAsync += async args =>
|
_processor.ProcessMessageAsync += ProcessReceivedMessageAsync;
|
||||||
{
|
_processor.ProcessErrorAsync += ProcessErrorAsync;
|
||||||
try
|
|
||||||
{
|
|
||||||
using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(args.Message.Body));
|
|
||||||
var root = jsonDocument.RootElement;
|
|
||||||
|
|
||||||
if (root.ValueKind == JsonValueKind.Array)
|
|
||||||
{
|
|
||||||
var eventMessages = root.Deserialize<IEnumerable<EventMessage>>();
|
|
||||||
await _handler.HandleManyEventsAsync(eventMessages);
|
|
||||||
}
|
|
||||||
else if (root.ValueKind == JsonValueKind.Object)
|
|
||||||
{
|
|
||||||
var eventMessage = root.Deserialize<EventMessage>();
|
|
||||||
await _handler.HandleEventAsync(eventMessage);
|
|
||||||
|
|
||||||
}
|
|
||||||
await args.CompleteMessageAsync(args.Message);
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.LogError(
|
|
||||||
exception,
|
|
||||||
"An error occured while processing message: {MessageId}",
|
|
||||||
args.Message.MessageId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_processor.ProcessErrorAsync += args =>
|
|
||||||
{
|
|
||||||
_logger.LogError(
|
|
||||||
args.Exception,
|
|
||||||
"An error occurred. Entity Path: {EntityPath}, Error Source: {ErrorSource}",
|
|
||||||
args.EntityPath,
|
|
||||||
args.ErrorSource
|
|
||||||
);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await _processor.StartProcessingAsync(cancellationToken);
|
await _processor.StartProcessingAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Task ProcessErrorAsync(ProcessErrorEventArgs args)
|
||||||
|
{
|
||||||
|
_logger.LogError(
|
||||||
|
args.Exception,
|
||||||
|
"An error occurred. Entity Path: {EntityPath}, Error Source: {ErrorSource}",
|
||||||
|
args.EntityPath,
|
||||||
|
args.ErrorSource
|
||||||
|
);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessReceivedMessageAsync(ProcessMessageEventArgs args)
|
||||||
|
{
|
||||||
|
await ProcessReceivedMessageAsync(Encoding.UTF8.GetString(args.Message.Body), args.Message.MessageId);
|
||||||
|
await args.CompleteMessageAsync(args.Message);
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _processor.StopProcessingAsync(cancellationToken);
|
await _processor.StopProcessingAsync(cancellationToken);
|
||||||
@ -79,7 +59,6 @@ public class AzureServiceBusEventListenerService : EventLoggingListenerService
|
|||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
_processor.DisposeAsync().GetAwaiter().GetResult();
|
_processor.DisposeAsync().GetAwaiter().GetResult();
|
||||||
_client.DisposeAsync().GetAwaiter().GetResult();
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Azure.Messaging.ServiceBus;
|
|
||||||
using Bit.Core.Models.Data;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.Services.Implementations;
|
|
||||||
|
|
||||||
public class AzureServiceBusEventWriteService : IEventWriteService, IAsyncDisposable
|
|
||||||
{
|
|
||||||
private readonly ServiceBusClient _client;
|
|
||||||
private readonly ServiceBusSender _sender;
|
|
||||||
|
|
||||||
public AzureServiceBusEventWriteService(GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
_client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString);
|
|
||||||
_sender = _client.CreateSender(globalSettings.EventLogging.AzureServiceBus.EventTopicName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateAsync(IEvent e)
|
|
||||||
{
|
|
||||||
var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(e))
|
|
||||||
{
|
|
||||||
ContentType = "application/json"
|
|
||||||
};
|
|
||||||
|
|
||||||
await _sender.SendMessageAsync(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateManyAsync(IEnumerable<IEvent> events)
|
|
||||||
{
|
|
||||||
var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(events))
|
|
||||||
{
|
|
||||||
ContentType = "application/json"
|
|
||||||
};
|
|
||||||
|
|
||||||
await _sender.SendMessageAsync(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
await _sender.DisposeAsync();
|
|
||||||
await _client.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using Azure.Messaging.ServiceBus;
|
using Azure.Messaging.ServiceBus;
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -10,39 +9,30 @@ namespace Bit.Core.Services;
|
|||||||
public class AzureServiceBusIntegrationListenerService : BackgroundService
|
public class AzureServiceBusIntegrationListenerService : BackgroundService
|
||||||
{
|
{
|
||||||
private readonly int _maxRetries;
|
private readonly int _maxRetries;
|
||||||
private readonly string _subscriptionName;
|
private readonly IAzureServiceBusService _serviceBusService;
|
||||||
private readonly string _topicName;
|
|
||||||
private readonly IIntegrationHandler _handler;
|
private readonly IIntegrationHandler _handler;
|
||||||
private readonly ServiceBusClient _client;
|
|
||||||
private readonly ServiceBusProcessor _processor;
|
private readonly ServiceBusProcessor _processor;
|
||||||
private readonly ServiceBusSender _sender;
|
|
||||||
private readonly ILogger<AzureServiceBusIntegrationListenerService> _logger;
|
private readonly ILogger<AzureServiceBusIntegrationListenerService> _logger;
|
||||||
|
|
||||||
public AzureServiceBusIntegrationListenerService(
|
public AzureServiceBusIntegrationListenerService(IIntegrationHandler handler,
|
||||||
IIntegrationHandler handler,
|
string topicName,
|
||||||
string subscriptionName,
|
string subscriptionName,
|
||||||
GlobalSettings globalSettings,
|
int maxRetries,
|
||||||
|
IAzureServiceBusService serviceBusService,
|
||||||
ILogger<AzureServiceBusIntegrationListenerService> logger)
|
ILogger<AzureServiceBusIntegrationListenerService> logger)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_maxRetries = globalSettings.EventLogging.AzureServiceBus.MaxRetries;
|
_maxRetries = maxRetries;
|
||||||
_topicName = globalSettings.EventLogging.AzureServiceBus.IntegrationTopicName;
|
_serviceBusService = serviceBusService;
|
||||||
_subscriptionName = subscriptionName;
|
|
||||||
|
|
||||||
_client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString);
|
_processor = _serviceBusService.CreateProcessor(topicName, subscriptionName, new ServiceBusProcessorOptions());
|
||||||
_processor = _client.CreateProcessor(_topicName, _subscriptionName, new ServiceBusProcessorOptions());
|
|
||||||
_sender = _client.CreateSender(_topicName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_processor.ProcessMessageAsync += HandleMessageAsync;
|
_processor.ProcessMessageAsync += HandleMessageAsync;
|
||||||
_processor.ProcessErrorAsync += args =>
|
_processor.ProcessErrorAsync += ProcessErrorAsync;
|
||||||
{
|
|
||||||
_logger.LogError(args.Exception, "Azure Service Bus error");
|
|
||||||
return Task.CompletedTask;
|
|
||||||
};
|
|
||||||
|
|
||||||
await _processor.StartProcessingAsync(cancellationToken);
|
await _processor.StartProcessingAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
@ -51,51 +41,67 @@ public class AzureServiceBusIntegrationListenerService : BackgroundService
|
|||||||
{
|
{
|
||||||
await _processor.StopProcessingAsync(cancellationToken);
|
await _processor.StopProcessingAsync(cancellationToken);
|
||||||
await _processor.DisposeAsync();
|
await _processor.DisposeAsync();
|
||||||
await _sender.DisposeAsync();
|
|
||||||
await _client.DisposeAsync();
|
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleMessageAsync(ProcessMessageEventArgs args)
|
internal Task ProcessErrorAsync(ProcessErrorEventArgs args)
|
||||||
{
|
{
|
||||||
var json = args.Message.Body.ToString();
|
_logger.LogError(
|
||||||
|
args.Exception,
|
||||||
|
"An error occurred. Entity Path: {EntityPath}, Error Source: {ErrorSource}",
|
||||||
|
args.EntityPath,
|
||||||
|
args.ErrorSource
|
||||||
|
);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<bool> HandleMessageAsync(string body)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _handler.HandleAsync(json);
|
var result = await _handler.HandleAsync(body);
|
||||||
var message = result.Message;
|
var message = result.Message;
|
||||||
|
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
await args.CompleteMessageAsync(args.Message);
|
// Successful integration. Return true to indicate the message has been handled
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
message.ApplyRetry(result.DelayUntilDate);
|
message.ApplyRetry(result.DelayUntilDate);
|
||||||
|
|
||||||
if (result.Retryable && message.RetryCount < _maxRetries)
|
if (result.Retryable && message.RetryCount < _maxRetries)
|
||||||
{
|
{
|
||||||
var scheduledTime = (DateTime)message.DelayUntilDate!;
|
// Publish message to the retry queue. It will be re-published for retry after a delay
|
||||||
var retryMsg = new ServiceBusMessage(message.ToJson())
|
// Return true to indicate the message has been handled
|
||||||
{
|
await _serviceBusService.PublishToRetryAsync(message);
|
||||||
Subject = args.Message.Subject,
|
return true;
|
||||||
ScheduledEnqueueTime = scheduledTime
|
|
||||||
};
|
|
||||||
|
|
||||||
await _sender.SendMessageAsync(retryMsg);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await args.DeadLetterMessageAsync(args.Message, "Retry limit exceeded or non-retryable");
|
// Non-recoverable failure or exceeded the max number of retries
|
||||||
return;
|
// Return false to indicate this message should be dead-lettered
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await args.CompleteMessageAsync(args.Message);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
// Unknown exception - log error, return true so the message will be acknowledged and not resent
|
||||||
_logger.LogError(ex, "Unhandled error processing ASB message");
|
_logger.LogError(ex, "Unhandled error processing ASB message");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleMessageAsync(ProcessMessageEventArgs args)
|
||||||
|
{
|
||||||
|
var json = args.Message.Body.ToString();
|
||||||
|
if (await HandleMessageAsync(json))
|
||||||
|
{
|
||||||
await args.CompleteMessageAsync(args.Message);
|
await args.CompleteMessageAsync(args.Message);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await args.DeadLetterMessageAsync(args.Message, "Retry limit exceeded or non-retryable");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
using Azure.Messaging.ServiceBus;
|
|
||||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
|
||||||
|
|
||||||
public class AzureServiceBusIntegrationPublisher : IIntegrationPublisher, IAsyncDisposable
|
|
||||||
{
|
|
||||||
private readonly ServiceBusClient _client;
|
|
||||||
private readonly ServiceBusSender _sender;
|
|
||||||
|
|
||||||
public AzureServiceBusIntegrationPublisher(GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
_client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString);
|
|
||||||
_sender = _client.CreateSender(globalSettings.EventLogging.AzureServiceBus.IntegrationTopicName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PublishAsync(IIntegrationMessage message)
|
|
||||||
{
|
|
||||||
var json = message.ToJson();
|
|
||||||
|
|
||||||
var serviceBusMessage = new ServiceBusMessage(json)
|
|
||||||
{
|
|
||||||
Subject = message.IntegrationType.ToRoutingKey(),
|
|
||||||
};
|
|
||||||
|
|
||||||
await _sender.SendMessageAsync(serviceBusMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
await _sender.DisposeAsync();
|
|
||||||
await _client.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,70 @@
|
|||||||
|
using Azure.Messaging.ServiceBus;
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public class AzureServiceBusService : IAzureServiceBusService
|
||||||
|
{
|
||||||
|
private readonly ServiceBusClient _client;
|
||||||
|
private readonly ServiceBusSender _eventSender;
|
||||||
|
private readonly ServiceBusSender _integrationSender;
|
||||||
|
|
||||||
|
public AzureServiceBusService(GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
_client = new ServiceBusClient(globalSettings.EventLogging.AzureServiceBus.ConnectionString);
|
||||||
|
_eventSender = _client.CreateSender(globalSettings.EventLogging.AzureServiceBus.EventTopicName);
|
||||||
|
_integrationSender = _client.CreateSender(globalSettings.EventLogging.AzureServiceBus.IntegrationTopicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceBusProcessor CreateProcessor(string topicName, string subscriptionName, ServiceBusProcessorOptions options)
|
||||||
|
{
|
||||||
|
return _client.CreateProcessor(topicName, subscriptionName, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishAsync(IIntegrationMessage message)
|
||||||
|
{
|
||||||
|
var json = message.ToJson();
|
||||||
|
|
||||||
|
var serviceBusMessage = new ServiceBusMessage(json)
|
||||||
|
{
|
||||||
|
Subject = message.IntegrationType.ToRoutingKey(),
|
||||||
|
MessageId = message.MessageId
|
||||||
|
};
|
||||||
|
|
||||||
|
await _integrationSender.SendMessageAsync(serviceBusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishToRetryAsync(IIntegrationMessage message)
|
||||||
|
{
|
||||||
|
var json = message.ToJson();
|
||||||
|
|
||||||
|
var serviceBusMessage = new ServiceBusMessage(json)
|
||||||
|
{
|
||||||
|
Subject = message.IntegrationType.ToRoutingKey(),
|
||||||
|
ScheduledEnqueueTime = message.DelayUntilDate ?? DateTime.UtcNow,
|
||||||
|
MessageId = message.MessageId
|
||||||
|
};
|
||||||
|
|
||||||
|
await _integrationSender.SendMessageAsync(serviceBusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishEventAsync(string body)
|
||||||
|
{
|
||||||
|
var message = new ServiceBusMessage(body)
|
||||||
|
{
|
||||||
|
ContentType = "application/json",
|
||||||
|
MessageId = Guid.NewGuid().ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
await _eventSender.SendMessageAsync(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _eventSender.DisposeAsync();
|
||||||
|
await _integrationSender.DisposeAsync();
|
||||||
|
await _client.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Models.Data;
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
public class EventIntegrationEventWriteService : IEventWriteService, IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly IEventIntegrationPublisher _eventIntegrationPublisher;
|
||||||
|
|
||||||
|
public EventIntegrationEventWriteService(IEventIntegrationPublisher eventIntegrationPublisher)
|
||||||
|
{
|
||||||
|
_eventIntegrationPublisher = eventIntegrationPublisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateAsync(IEvent e)
|
||||||
|
{
|
||||||
|
var body = JsonSerializer.Serialize(e);
|
||||||
|
await _eventIntegrationPublisher.PublishEventAsync(body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateManyAsync(IEnumerable<IEvent> events)
|
||||||
|
{
|
||||||
|
var body = JsonSerializer.Serialize(events);
|
||||||
|
await _eventIntegrationPublisher.PublishEventAsync(body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _eventIntegrationPublisher.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using System.Text.Json;
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text.Json;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
using Bit.Core.AdminConsole.Utilities;
|
using Bit.Core.AdminConsole.Utilities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -7,11 +9,9 @@ using Bit.Core.Repositories;
|
|||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class EventIntegrationHandler<T>(
|
public class EventIntegrationHandler<T>(
|
||||||
IntegrationType integrationType,
|
IntegrationType integrationType,
|
||||||
IIntegrationPublisher integrationPublisher,
|
IEventIntegrationPublisher eventIntegrationPublisher,
|
||||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
IOrganizationRepository organizationRepository)
|
IOrganizationRepository organizationRepository)
|
||||||
@ -34,6 +34,7 @@ public class EventIntegrationHandler<T>(
|
|||||||
var template = configuration.Template ?? string.Empty;
|
var template = configuration.Template ?? string.Empty;
|
||||||
var context = await BuildContextAsync(eventMessage, template);
|
var context = await BuildContextAsync(eventMessage, template);
|
||||||
var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(template, context);
|
var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(template, context);
|
||||||
|
var messageId = eventMessage.IdempotencyId ?? Guid.NewGuid();
|
||||||
|
|
||||||
var config = configuration.MergedConfiguration.Deserialize<T>()
|
var config = configuration.MergedConfiguration.Deserialize<T>()
|
||||||
?? throw new InvalidOperationException($"Failed to deserialize to {typeof(T).Name}");
|
?? throw new InvalidOperationException($"Failed to deserialize to {typeof(T).Name}");
|
||||||
@ -41,13 +42,14 @@ public class EventIntegrationHandler<T>(
|
|||||||
var message = new IntegrationMessage<T>
|
var message = new IntegrationMessage<T>
|
||||||
{
|
{
|
||||||
IntegrationType = integrationType,
|
IntegrationType = integrationType,
|
||||||
|
MessageId = messageId.ToString(),
|
||||||
Configuration = config,
|
Configuration = config,
|
||||||
RenderedTemplate = renderedTemplate,
|
RenderedTemplate = renderedTemplate,
|
||||||
RetryCount = 0,
|
RetryCount = 0,
|
||||||
DelayUntilDate = null
|
DelayUntilDate = null
|
||||||
};
|
};
|
||||||
|
|
||||||
await integrationPublisher.PublishAsync(message);
|
await eventIntegrationPublisher.PublishAsync(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Models.Data;
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.Models.Data;
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
@ -462,13 +462,13 @@ public class EventService : IEventService
|
|||||||
|
|
||||||
private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
|
private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
|
||||||
{
|
{
|
||||||
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
|
return orgAbilities != null && orgAbilities.TryGetValue(orgId, out var orgAbility) &&
|
||||||
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents;
|
orgAbility.Enabled && orgAbility.UseEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanUseProviderEvents(IDictionary<Guid, ProviderAbility> providerAbilities, Guid providerId)
|
private bool CanUseProviderEvents(IDictionary<Guid, ProviderAbility> providerAbilities, Guid providerId)
|
||||||
{
|
{
|
||||||
return providerAbilities != null && providerAbilities.ContainsKey(providerId) &&
|
return providerAbilities != null && providerAbilities.TryGetValue(providerId, out var providerAbility) &&
|
||||||
providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents;
|
providerAbility.Enabled && providerAbility.UseEvents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,6 @@ using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
|||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Stripe;
|
using Stripe;
|
||||||
@ -56,7 +53,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
private readonly IPolicyRepository _policyRepository;
|
private readonly IPolicyRepository _policyRepository;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ISsoUserRepository _ssoUserRepository;
|
private readonly ISsoUserRepository _ssoUserRepository;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
private readonly IGlobalSettings _globalSettings;
|
private readonly IGlobalSettings _globalSettings;
|
||||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
@ -88,7 +84,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
ISsoUserRepository ssoUserRepository,
|
ISsoUserRepository ssoUserRepository,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
@ -120,7 +115,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
_policyRepository = policyRepository;
|
_policyRepository = policyRepository;
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_ssoUserRepository = ssoUserRepository;
|
_ssoUserRepository = ssoUserRepository;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
@ -153,11 +147,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
await _paymentService.CancelSubscriptionAsync(organization, eop);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.CancelSubscription, organization, _currentContext)
|
|
||||||
{
|
|
||||||
EndOfPeriod = endOfPeriod,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReinstateSubscriptionAsync(Guid organizationId)
|
public async Task ReinstateSubscriptionAsync(Guid organizationId)
|
||||||
@ -169,8 +158,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _paymentService.ReinstateSubscriptionAsync(organization);
|
await _paymentService.ReinstateSubscriptionAsync(organization);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.ReinstateSubscription, organization, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb)
|
public async Task<string> AdjustStorageAsync(Guid organizationId, short storageAdjustmentGb)
|
||||||
@ -190,13 +177,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
||||||
plan.PasswordManager.StripeStoragePlanId);
|
plan.PasswordManager.StripeStoragePlanId);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Storage = storageAdjustmentGb,
|
|
||||||
});
|
|
||||||
await ReplaceAndUpdateCacheAsync(organization);
|
await ReplaceAndUpdateCacheAsync(organization);
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
@ -328,14 +308,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats);
|
var paymentIntentClientSecret = await _paymentService.AdjustSeatsAsync(organization, plan, additionalSeats);
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.AdjustSeats, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Seats = newSeatTotal,
|
|
||||||
PreviousSeats = organization.Seats
|
|
||||||
});
|
|
||||||
organization.Seats = (short?)newSeatTotal;
|
organization.Seats = (short?)newSeatTotal;
|
||||||
await ReplaceAndUpdateCacheAsync(organization);
|
await ReplaceAndUpdateCacheAsync(organization);
|
||||||
|
|
||||||
@ -640,12 +612,12 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var providers = organization.GetTwoFactorProviders();
|
var providers = organization.GetTwoFactorProviders();
|
||||||
if (!providers?.ContainsKey(type) ?? true)
|
if (providers is null || !providers.TryGetValue(type, out var provider))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
providers[type].Enabled = true;
|
provider.Enabled = true;
|
||||||
organization.SetTwoFactorProviders(providers);
|
organization.SetTwoFactorProviders(providers);
|
||||||
await UpdateAsync(organization);
|
await UpdateAsync(organization);
|
||||||
}
|
}
|
||||||
@ -886,12 +858,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await SendInvitesAsync(allOrgUsers, organization);
|
await SendInvitesAsync(allOrgUsers, organization);
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.InvitedUsers, organization, _currentContext)
|
|
||||||
{
|
|
||||||
Users = orgUserInvitedCount
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -1149,7 +1115,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId);
|
var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId);
|
||||||
var removeUsersSet = new HashSet<string>(removeUserExternalIds)
|
var removeUsersSet = new HashSet<string>(removeUserExternalIds)
|
||||||
.Except(newUsersSet)
|
.Except(newUsersSet)
|
||||||
.Where(u => existingUsersDict.ContainsKey(u) && existingUsersDict[u].Type != OrganizationUserType.Owner)
|
.Where(u => existingUsersDict.TryGetValue(u, out var existingUser) && existingUser.Type != OrganizationUserType.Owner)
|
||||||
.Select(u => existingUsersDict[u]);
|
.Select(u => existingUsersDict[u]);
|
||||||
|
|
||||||
await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id));
|
await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id));
|
||||||
@ -1317,8 +1283,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.ou, e.e, eventSystemUser, e.d)));
|
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.ou, e.e, eventSystemUser, e.d)));
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.DirectorySynced, organization, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
|
public async Task DeleteSsoUserAsync(Guid userId, Guid? organizationId)
|
||||||
@ -1754,11 +1718,5 @@ public class OrganizationService : IOrganizationService
|
|||||||
|
|
||||||
await SendInviteAsync(ownerOrganizationUser, organization, true);
|
await SendInviteAsync(ownerOrganizationUser, organization, true);
|
||||||
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
|
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationCreatedByAdmin, organization, _currentContext)
|
|
||||||
{
|
|
||||||
EventRaisedByUser = userService.GetUserName(user),
|
|
||||||
SalesAssistedTrialStarted = salesAssistedTrialStarted,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ public class PolicyService : IPolicyService
|
|||||||
var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType);
|
var excludedUserTypes = GetUserTypesExcludedFromPolicy(policyType);
|
||||||
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
|
||||||
return organizationUserPolicyDetails.Where(o =>
|
return organizationUserPolicyDetails.Where(o =>
|
||||||
(!orgAbilities.ContainsKey(o.OrganizationId) || orgAbilities[o.OrganizationId].UsePolicies) &&
|
(!orgAbilities.TryGetValue(o.OrganizationId, out var orgAbility) || orgAbility.UsePolicies) &&
|
||||||
o.PolicyEnabled &&
|
o.PolicyEnabled &&
|
||||||
!excludedUserTypes.Contains(o.OrganizationUserType) &&
|
!excludedUserTypes.Contains(o.OrganizationUserType) &&
|
||||||
o.OrganizationUserStatus >= minStatus &&
|
o.OrganizationUserStatus >= minStatus &&
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using System.Text;
|
#nullable enable
|
||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.Models.Data;
|
using System.Text;
|
||||||
using Bit.Core.Settings;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
using RabbitMQ.Client.Events;
|
using RabbitMQ.Client.Events;
|
||||||
@ -10,94 +9,60 @@ namespace Bit.Core.Services;
|
|||||||
|
|
||||||
public class RabbitMqEventListenerService : EventLoggingListenerService
|
public class RabbitMqEventListenerService : EventLoggingListenerService
|
||||||
{
|
{
|
||||||
private IChannel _channel;
|
private readonly Lazy<Task<IChannel>> _lazyChannel;
|
||||||
private IConnection _connection;
|
|
||||||
private readonly string _exchangeName;
|
|
||||||
private readonly ConnectionFactory _factory;
|
|
||||||
private readonly ILogger<RabbitMqEventListenerService> _logger;
|
|
||||||
private readonly string _queueName;
|
private readonly string _queueName;
|
||||||
|
private readonly IRabbitMqService _rabbitMqService;
|
||||||
|
|
||||||
public RabbitMqEventListenerService(
|
public RabbitMqEventListenerService(
|
||||||
IEventMessageHandler handler,
|
IEventMessageHandler handler,
|
||||||
ILogger<RabbitMqEventListenerService> logger,
|
string queueName,
|
||||||
GlobalSettings globalSettings,
|
IRabbitMqService rabbitMqService,
|
||||||
string queueName) : base(handler)
|
ILogger<RabbitMqEventListenerService> logger) : base(handler, logger)
|
||||||
{
|
{
|
||||||
_factory = new ConnectionFactory
|
|
||||||
{
|
|
||||||
HostName = globalSettings.EventLogging.RabbitMq.HostName,
|
|
||||||
UserName = globalSettings.EventLogging.RabbitMq.Username,
|
|
||||||
Password = globalSettings.EventLogging.RabbitMq.Password
|
|
||||||
};
|
|
||||||
_exchangeName = globalSettings.EventLogging.RabbitMq.EventExchangeName;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_queueName = queueName;
|
_queueName = queueName;
|
||||||
|
_rabbitMqService = rabbitMqService;
|
||||||
|
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_connection = await _factory.CreateConnectionAsync(cancellationToken);
|
await _rabbitMqService.CreateEventQueueAsync(_queueName, cancellationToken);
|
||||||
_channel = await _connection.CreateChannelAsync(cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
await _channel.ExchangeDeclareAsync(exchange: _exchangeName,
|
|
||||||
type: ExchangeType.Fanout,
|
|
||||||
durable: true,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await _channel.QueueDeclareAsync(queue: _queueName,
|
|
||||||
durable: true,
|
|
||||||
exclusive: false,
|
|
||||||
autoDelete: false,
|
|
||||||
arguments: null,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await _channel.QueueBindAsync(queue: _queueName,
|
|
||||||
exchange: _exchangeName,
|
|
||||||
routingKey: string.Empty,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await base.StartAsync(cancellationToken);
|
await base.StartAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var consumer = new AsyncEventingBasicConsumer(_channel);
|
var channel = await _lazyChannel.Value;
|
||||||
consumer.ReceivedAsync += async (_, eventArgs) =>
|
var consumer = new AsyncEventingBasicConsumer(channel);
|
||||||
{
|
consumer.ReceivedAsync += async (_, eventArgs) => { await ProcessReceivedMessageAsync(eventArgs); };
|
||||||
try
|
|
||||||
{
|
|
||||||
using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(eventArgs.Body.Span));
|
|
||||||
var root = jsonDocument.RootElement;
|
|
||||||
|
|
||||||
if (root.ValueKind == JsonValueKind.Array)
|
await channel.BasicConsumeAsync(_queueName, autoAck: true, consumer: consumer, cancellationToken: cancellationToken);
|
||||||
{
|
}
|
||||||
var eventMessages = root.Deserialize<IEnumerable<EventMessage>>();
|
|
||||||
await _handler.HandleManyEventsAsync(eventMessages);
|
|
||||||
}
|
|
||||||
else if (root.ValueKind == JsonValueKind.Object)
|
|
||||||
{
|
|
||||||
var eventMessage = root.Deserialize<EventMessage>();
|
|
||||||
await _handler.HandleEventAsync(eventMessage);
|
|
||||||
|
|
||||||
}
|
internal async Task ProcessReceivedMessageAsync(BasicDeliverEventArgs eventArgs)
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
await ProcessReceivedMessageAsync(
|
||||||
{
|
Encoding.UTF8.GetString(eventArgs.Body.Span),
|
||||||
_logger.LogError(ex, "An error occurred while processing the message");
|
eventArgs.BasicProperties.MessageId);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await _channel.BasicConsumeAsync(_queueName, autoAck: true, consumer: consumer, cancellationToken: cancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _channel.CloseAsync(cancellationToken);
|
if (_lazyChannel.IsValueCreated)
|
||||||
await _connection.CloseAsync(cancellationToken);
|
{
|
||||||
|
var channel = await _lazyChannel.Value;
|
||||||
|
await channel.CloseAsync(cancellationToken);
|
||||||
|
}
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
_channel.Dispose();
|
if (_lazyChannel.IsValueCreated && _lazyChannel.Value.IsCompletedSuccessfully)
|
||||||
_connection.Dispose();
|
{
|
||||||
|
_lazyChannel.Value.Result.Dispose();
|
||||||
|
}
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using Bit.Core.Models.Data;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
|
||||||
public class RabbitMqEventWriteService : IEventWriteService, IAsyncDisposable
|
|
||||||
{
|
|
||||||
private readonly ConnectionFactory _factory;
|
|
||||||
private readonly Lazy<Task<IConnection>> _lazyConnection;
|
|
||||||
private readonly string _exchangeName;
|
|
||||||
|
|
||||||
public RabbitMqEventWriteService(GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
_factory = new ConnectionFactory
|
|
||||||
{
|
|
||||||
HostName = globalSettings.EventLogging.RabbitMq.HostName,
|
|
||||||
UserName = globalSettings.EventLogging.RabbitMq.Username,
|
|
||||||
Password = globalSettings.EventLogging.RabbitMq.Password
|
|
||||||
};
|
|
||||||
_exchangeName = globalSettings.EventLogging.RabbitMq.EventExchangeName;
|
|
||||||
|
|
||||||
_lazyConnection = new Lazy<Task<IConnection>>(CreateConnectionAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateAsync(IEvent e)
|
|
||||||
{
|
|
||||||
var connection = await _lazyConnection.Value;
|
|
||||||
using var channel = await connection.CreateChannelAsync();
|
|
||||||
|
|
||||||
await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Fanout, durable: true);
|
|
||||||
|
|
||||||
var body = JsonSerializer.SerializeToUtf8Bytes(e);
|
|
||||||
|
|
||||||
await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateManyAsync(IEnumerable<IEvent> events)
|
|
||||||
{
|
|
||||||
var connection = await _lazyConnection.Value;
|
|
||||||
using var channel = await connection.CreateChannelAsync();
|
|
||||||
await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Fanout, durable: true);
|
|
||||||
|
|
||||||
var body = JsonSerializer.SerializeToUtf8Bytes(events);
|
|
||||||
|
|
||||||
await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
if (_lazyConnection.IsValueCreated)
|
|
||||||
{
|
|
||||||
var connection = await _lazyConnection.Value;
|
|
||||||
await connection.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IConnection> CreateConnectionAsync()
|
|
||||||
{
|
|
||||||
return await _factory.CreateConnectionAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,8 @@
|
|||||||
using System.Text;
|
#nullable enable
|
||||||
using Bit.Core.Settings;
|
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using RabbitMQ.Client;
|
using RabbitMQ.Client;
|
||||||
@ -9,183 +12,137 @@ namespace Bit.Core.Services;
|
|||||||
|
|
||||||
public class RabbitMqIntegrationListenerService : BackgroundService
|
public class RabbitMqIntegrationListenerService : BackgroundService
|
||||||
{
|
{
|
||||||
private const string _deadLetterRoutingKey = "dead-letter";
|
|
||||||
private IChannel _channel;
|
|
||||||
private IConnection _connection;
|
|
||||||
private readonly string _exchangeName;
|
|
||||||
private readonly string _queueName;
|
|
||||||
private readonly string _retryQueueName;
|
|
||||||
private readonly string _deadLetterQueueName;
|
|
||||||
private readonly string _routingKey;
|
|
||||||
private readonly string _retryRoutingKey;
|
|
||||||
private readonly int _maxRetries;
|
private readonly int _maxRetries;
|
||||||
|
private readonly string _queueName;
|
||||||
|
private readonly string _routingKey;
|
||||||
|
private readonly string _retryQueueName;
|
||||||
private readonly IIntegrationHandler _handler;
|
private readonly IIntegrationHandler _handler;
|
||||||
private readonly ConnectionFactory _factory;
|
private readonly Lazy<Task<IChannel>> _lazyChannel;
|
||||||
|
private readonly IRabbitMqService _rabbitMqService;
|
||||||
private readonly ILogger<RabbitMqIntegrationListenerService> _logger;
|
private readonly ILogger<RabbitMqIntegrationListenerService> _logger;
|
||||||
private readonly int _retryTiming;
|
|
||||||
|
|
||||||
public RabbitMqIntegrationListenerService(IIntegrationHandler handler,
|
public RabbitMqIntegrationListenerService(IIntegrationHandler handler,
|
||||||
string routingKey,
|
string routingKey,
|
||||||
string queueName,
|
string queueName,
|
||||||
string retryQueueName,
|
string retryQueueName,
|
||||||
string deadLetterQueueName,
|
int maxRetries,
|
||||||
GlobalSettings globalSettings,
|
IRabbitMqService rabbitMqService,
|
||||||
ILogger<RabbitMqIntegrationListenerService> logger)
|
ILogger<RabbitMqIntegrationListenerService> logger)
|
||||||
{
|
{
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
_routingKey = routingKey;
|
_routingKey = routingKey;
|
||||||
_retryRoutingKey = $"{_routingKey}-retry";
|
|
||||||
_queueName = queueName;
|
|
||||||
_retryQueueName = retryQueueName;
|
_retryQueueName = retryQueueName;
|
||||||
_deadLetterQueueName = deadLetterQueueName;
|
_queueName = queueName;
|
||||||
|
_rabbitMqService = rabbitMqService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_exchangeName = globalSettings.EventLogging.RabbitMq.IntegrationExchangeName;
|
_maxRetries = maxRetries;
|
||||||
_maxRetries = globalSettings.EventLogging.RabbitMq.MaxRetries;
|
_lazyChannel = new Lazy<Task<IChannel>>(() => _rabbitMqService.CreateChannelAsync());
|
||||||
_retryTiming = globalSettings.EventLogging.RabbitMq.RetryTiming;
|
|
||||||
|
|
||||||
_factory = new ConnectionFactory
|
|
||||||
{
|
|
||||||
HostName = globalSettings.EventLogging.RabbitMq.HostName,
|
|
||||||
UserName = globalSettings.EventLogging.RabbitMq.Username,
|
|
||||||
Password = globalSettings.EventLogging.RabbitMq.Password
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StartAsync(CancellationToken cancellationToken)
|
public override async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_connection = await _factory.CreateConnectionAsync(cancellationToken);
|
await _rabbitMqService.CreateIntegrationQueuesAsync(
|
||||||
_channel = await _connection.CreateChannelAsync(cancellationToken: cancellationToken);
|
_queueName,
|
||||||
|
_retryQueueName,
|
||||||
await _channel.ExchangeDeclareAsync(exchange: _exchangeName,
|
_routingKey,
|
||||||
type: ExchangeType.Direct,
|
cancellationToken: cancellationToken);
|
||||||
durable: true,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
// Declare main queue
|
|
||||||
await _channel.QueueDeclareAsync(queue: _queueName,
|
|
||||||
durable: true,
|
|
||||||
exclusive: false,
|
|
||||||
autoDelete: false,
|
|
||||||
arguments: null,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await _channel.QueueBindAsync(queue: _queueName,
|
|
||||||
exchange: _exchangeName,
|
|
||||||
routingKey: _routingKey,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
// Declare retry queue (Configurable TTL, dead-letters back to main queue)
|
|
||||||
await _channel.QueueDeclareAsync(queue: _retryQueueName,
|
|
||||||
durable: true,
|
|
||||||
exclusive: false,
|
|
||||||
autoDelete: false,
|
|
||||||
arguments: new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "x-dead-letter-exchange", _exchangeName },
|
|
||||||
{ "x-dead-letter-routing-key", _routingKey },
|
|
||||||
{ "x-message-ttl", _retryTiming }
|
|
||||||
},
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await _channel.QueueBindAsync(queue: _retryQueueName,
|
|
||||||
exchange: _exchangeName,
|
|
||||||
routingKey: _retryRoutingKey,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
// Declare dead letter queue
|
|
||||||
await _channel.QueueDeclareAsync(queue: _deadLetterQueueName,
|
|
||||||
durable: true,
|
|
||||||
exclusive: false,
|
|
||||||
autoDelete: false,
|
|
||||||
arguments: null,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
await _channel.QueueBindAsync(queue: _deadLetterQueueName,
|
|
||||||
exchange: _exchangeName,
|
|
||||||
routingKey: _deadLetterRoutingKey,
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
|
|
||||||
await base.StartAsync(cancellationToken);
|
await base.StartAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var consumer = new AsyncEventingBasicConsumer(_channel);
|
var channel = await _lazyChannel.Value;
|
||||||
|
var consumer = new AsyncEventingBasicConsumer(channel);
|
||||||
consumer.ReceivedAsync += async (_, ea) =>
|
consumer.ReceivedAsync += async (_, ea) =>
|
||||||
|
{
|
||||||
|
await ProcessReceivedMessageAsync(ea, cancellationToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.BasicConsumeAsync(queue: _queueName, autoAck: false, consumer: consumer, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task ProcessReceivedMessageAsync(BasicDeliverEventArgs ea, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var channel = await _lazyChannel.Value;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var json = Encoding.UTF8.GetString(ea.Body.Span);
|
var json = Encoding.UTF8.GetString(ea.Body.Span);
|
||||||
|
|
||||||
try
|
// Determine if the message came off of the retry queue too soon
|
||||||
|
// If so, place it back on the retry queue
|
||||||
|
var integrationMessage = JsonSerializer.Deserialize<IntegrationMessage>(json);
|
||||||
|
if (integrationMessage is not null &&
|
||||||
|
integrationMessage.DelayUntilDate.HasValue &&
|
||||||
|
integrationMessage.DelayUntilDate.Value > DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
var result = await _handler.HandleAsync(json);
|
await _rabbitMqService.RepublishToRetryQueueAsync(channel, ea);
|
||||||
var message = result.Message;
|
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.Success)
|
var result = await _handler.HandleAsync(json);
|
||||||
|
var message = result.Message;
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
// Successful integration send. Acknowledge message delivery and return
|
||||||
|
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Retryable)
|
||||||
|
{
|
||||||
|
// Integration failed, but is retryable - apply delay and check max retries
|
||||||
|
message.ApplyRetry(result.DelayUntilDate);
|
||||||
|
|
||||||
|
if (message.RetryCount < _maxRetries)
|
||||||
{
|
{
|
||||||
// Successful integration send. Acknowledge message delivery and return
|
// Publish message to the retry queue. It will be re-published for retry after a delay
|
||||||
await _channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
await _rabbitMqService.PublishToRetryAsync(channel, message, cancellationToken);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Retryable)
|
|
||||||
{
|
|
||||||
// Integration failed, but is retryable - apply delay and check max retries
|
|
||||||
message.ApplyRetry(result.DelayUntilDate);
|
|
||||||
|
|
||||||
if (message.RetryCount < _maxRetries)
|
|
||||||
{
|
|
||||||
// Publish message to the retry queue. It will be re-published for retry after a delay
|
|
||||||
await _channel.BasicPublishAsync(
|
|
||||||
exchange: _exchangeName,
|
|
||||||
routingKey: _retryRoutingKey,
|
|
||||||
body: Encoding.UTF8.GetBytes(message.ToJson()),
|
|
||||||
cancellationToken: cancellationToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Exceeded the max number of retries; fail and send to dead letter queue
|
|
||||||
await PublishToDeadLetterAsync(message.ToJson());
|
|
||||||
_logger.LogWarning("Max retry attempts reached. Sent to DLQ.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Fatal error (i.e. not retryable) occurred. Send message to dead letter queue without any retries
|
// Exceeded the max number of retries; fail and send to dead letter queue
|
||||||
await PublishToDeadLetterAsync(message.ToJson());
|
await _rabbitMqService.PublishToDeadLetterAsync(channel, message, cancellationToken);
|
||||||
_logger.LogWarning("Non-retryable failure. Sent to DLQ.");
|
_logger.LogWarning("Max retry attempts reached. Sent to DLQ.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message has been sent to retry or dead letter queues.
|
|
||||||
// Acknowledge receipt so Rabbit knows it's been processed
|
|
||||||
await _channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
// Unknown error occurred. Acknowledge so Rabbit doesn't keep attempting. Log the error
|
// Fatal error (i.e. not retryable) occurred. Send message to dead letter queue without any retries
|
||||||
_logger.LogError(ex, "Unhandled error processing integration message.");
|
await _rabbitMqService.PublishToDeadLetterAsync(channel, message, cancellationToken);
|
||||||
await _channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
_logger.LogWarning("Non-retryable failure. Sent to DLQ.");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
await _channel.BasicConsumeAsync(queue: _queueName, autoAck: false, consumer: consumer, cancellationToken: cancellationToken);
|
// Message has been sent to retry or dead letter queues.
|
||||||
}
|
// Acknowledge receipt so Rabbit knows it's been processed
|
||||||
|
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
||||||
private async Task PublishToDeadLetterAsync(string json)
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
await _channel.BasicPublishAsync(
|
{
|
||||||
exchange: _exchangeName,
|
// Unknown error occurred. Acknowledge so Rabbit doesn't keep attempting. Log the error
|
||||||
routingKey: _deadLetterRoutingKey,
|
_logger.LogError(ex, "Unhandled error processing integration message.");
|
||||||
body: Encoding.UTF8.GetBytes(json));
|
await channel.BasicAckAsync(ea.DeliveryTag, false, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _channel.CloseAsync(cancellationToken);
|
if (_lazyChannel.IsValueCreated)
|
||||||
await _connection.CloseAsync(cancellationToken);
|
{
|
||||||
|
var channel = await _lazyChannel.Value;
|
||||||
|
await channel.CloseAsync(cancellationToken);
|
||||||
|
}
|
||||||
await base.StopAsync(cancellationToken);
|
await base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
_channel.Dispose();
|
if (_lazyChannel.IsValueCreated && _lazyChannel.Value.IsCompletedSuccessfully)
|
||||||
_connection.Dispose();
|
{
|
||||||
|
_lazyChannel.Value.Result.Dispose();
|
||||||
|
}
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Settings;
|
|
||||||
using RabbitMQ.Client;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
|
||||||
|
|
||||||
public class RabbitMqIntegrationPublisher : IIntegrationPublisher, IAsyncDisposable
|
|
||||||
{
|
|
||||||
private readonly ConnectionFactory _factory;
|
|
||||||
private readonly Lazy<Task<IConnection>> _lazyConnection;
|
|
||||||
private readonly string _exchangeName;
|
|
||||||
|
|
||||||
public RabbitMqIntegrationPublisher(GlobalSettings globalSettings)
|
|
||||||
{
|
|
||||||
_factory = new ConnectionFactory
|
|
||||||
{
|
|
||||||
HostName = globalSettings.EventLogging.RabbitMq.HostName,
|
|
||||||
UserName = globalSettings.EventLogging.RabbitMq.Username,
|
|
||||||
Password = globalSettings.EventLogging.RabbitMq.Password
|
|
||||||
};
|
|
||||||
_exchangeName = globalSettings.EventLogging.RabbitMq.IntegrationExchangeName;
|
|
||||||
|
|
||||||
_lazyConnection = new Lazy<Task<IConnection>>(CreateConnectionAsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PublishAsync(IIntegrationMessage message)
|
|
||||||
{
|
|
||||||
var routingKey = message.IntegrationType.ToRoutingKey();
|
|
||||||
var connection = await _lazyConnection.Value;
|
|
||||||
await using var channel = await connection.CreateChannelAsync();
|
|
||||||
|
|
||||||
await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Direct, durable: true);
|
|
||||||
|
|
||||||
var body = Encoding.UTF8.GetBytes(message.ToJson());
|
|
||||||
|
|
||||||
await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: routingKey, body: body);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
if (_lazyConnection.IsValueCreated)
|
|
||||||
{
|
|
||||||
var connection = await _lazyConnection.Value;
|
|
||||||
await connection.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IConnection> CreateConnectionAsync()
|
|
||||||
{
|
|
||||||
return await _factory.CreateConnectionAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,244 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using RabbitMQ.Client;
|
||||||
|
using RabbitMQ.Client.Events;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public class RabbitMqService : IRabbitMqService
|
||||||
|
{
|
||||||
|
private const string _deadLetterRoutingKey = "dead-letter";
|
||||||
|
|
||||||
|
private readonly ConnectionFactory _factory;
|
||||||
|
private readonly Lazy<Task<IConnection>> _lazyConnection;
|
||||||
|
private readonly string _deadLetterQueueName;
|
||||||
|
private readonly string _eventExchangeName;
|
||||||
|
private readonly string _integrationExchangeName;
|
||||||
|
private readonly int _retryTiming;
|
||||||
|
private readonly bool _useDelayPlugin;
|
||||||
|
|
||||||
|
public RabbitMqService(GlobalSettings globalSettings)
|
||||||
|
{
|
||||||
|
_factory = new ConnectionFactory
|
||||||
|
{
|
||||||
|
HostName = globalSettings.EventLogging.RabbitMq.HostName,
|
||||||
|
UserName = globalSettings.EventLogging.RabbitMq.Username,
|
||||||
|
Password = globalSettings.EventLogging.RabbitMq.Password
|
||||||
|
};
|
||||||
|
_deadLetterQueueName = globalSettings.EventLogging.RabbitMq.IntegrationDeadLetterQueueName;
|
||||||
|
_eventExchangeName = globalSettings.EventLogging.RabbitMq.EventExchangeName;
|
||||||
|
_integrationExchangeName = globalSettings.EventLogging.RabbitMq.IntegrationExchangeName;
|
||||||
|
_retryTiming = globalSettings.EventLogging.RabbitMq.RetryTiming;
|
||||||
|
_useDelayPlugin = globalSettings.EventLogging.RabbitMq.UseDelayPlugin;
|
||||||
|
|
||||||
|
_lazyConnection = new Lazy<Task<IConnection>>(CreateConnectionAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IChannel> CreateChannelAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var connection = await _lazyConnection.Value;
|
||||||
|
return await connection.CreateChannelAsync(cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateEventQueueAsync(string queueName, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var channel = await CreateChannelAsync(cancellationToken);
|
||||||
|
await channel.QueueDeclareAsync(queue: queueName,
|
||||||
|
durable: true,
|
||||||
|
exclusive: false,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: null,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
await channel.QueueBindAsync(queue: queueName,
|
||||||
|
exchange: _eventExchangeName,
|
||||||
|
routingKey: string.Empty,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateIntegrationQueuesAsync(
|
||||||
|
string queueName,
|
||||||
|
string retryQueueName,
|
||||||
|
string routingKey,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var channel = await CreateChannelAsync(cancellationToken);
|
||||||
|
var retryRoutingKey = $"{routingKey}-retry";
|
||||||
|
|
||||||
|
// Declare main integration queue
|
||||||
|
await channel.QueueDeclareAsync(
|
||||||
|
queue: queueName,
|
||||||
|
durable: true,
|
||||||
|
exclusive: false,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: null,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
await channel.QueueBindAsync(
|
||||||
|
queue: queueName,
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
routingKey: routingKey,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
if (!_useDelayPlugin)
|
||||||
|
{
|
||||||
|
// Declare retry queue (Configurable TTL, dead-letters back to main queue)
|
||||||
|
// Only needed if NOT using delay plugin
|
||||||
|
await channel.QueueDeclareAsync(queue: retryQueueName,
|
||||||
|
durable: true,
|
||||||
|
exclusive: false,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
{ "x-dead-letter-exchange", _integrationExchangeName },
|
||||||
|
{ "x-dead-letter-routing-key", routingKey },
|
||||||
|
{ "x-message-ttl", _retryTiming }
|
||||||
|
},
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
await channel.QueueBindAsync(queue: retryQueueName,
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
routingKey: retryRoutingKey,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishAsync(IIntegrationMessage message)
|
||||||
|
{
|
||||||
|
var routingKey = message.IntegrationType.ToRoutingKey();
|
||||||
|
await using var channel = await CreateChannelAsync();
|
||||||
|
|
||||||
|
var body = Encoding.UTF8.GetBytes(message.ToJson());
|
||||||
|
var properties = new BasicProperties
|
||||||
|
{
|
||||||
|
MessageId = message.MessageId,
|
||||||
|
Persistent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: properties,
|
||||||
|
routingKey: routingKey,
|
||||||
|
body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishEventAsync(string body)
|
||||||
|
{
|
||||||
|
await using var channel = await CreateChannelAsync();
|
||||||
|
var properties = new BasicProperties
|
||||||
|
{
|
||||||
|
MessageId = Guid.NewGuid().ToString(),
|
||||||
|
Persistent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: _eventExchangeName,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: properties,
|
||||||
|
routingKey: string.Empty,
|
||||||
|
body: Encoding.UTF8.GetBytes(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishToRetryAsync(IChannel channel, IIntegrationMessage message, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var routingKey = message.IntegrationType.ToRoutingKey();
|
||||||
|
var retryRoutingKey = $"{routingKey}-retry";
|
||||||
|
var properties = new BasicProperties
|
||||||
|
{
|
||||||
|
Persistent = true,
|
||||||
|
MessageId = message.MessageId,
|
||||||
|
Headers = _useDelayPlugin && message.DelayUntilDate.HasValue ?
|
||||||
|
new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["x-delay"] = Math.Max((int)(message.DelayUntilDate.Value - DateTime.UtcNow).TotalMilliseconds, 0)
|
||||||
|
} :
|
||||||
|
null
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
routingKey: _useDelayPlugin ? routingKey : retryRoutingKey,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: properties,
|
||||||
|
body: Encoding.UTF8.GetBytes(message.ToJson()),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PublishToDeadLetterAsync(
|
||||||
|
IChannel channel,
|
||||||
|
IIntegrationMessage message,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var properties = new BasicProperties
|
||||||
|
{
|
||||||
|
MessageId = message.MessageId,
|
||||||
|
Persistent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: properties,
|
||||||
|
routingKey: _deadLetterRoutingKey,
|
||||||
|
body: Encoding.UTF8.GetBytes(message.ToJson()),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RepublishToRetryQueueAsync(IChannel channel, BasicDeliverEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
await channel.BasicPublishAsync(
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
routingKey: eventArgs.RoutingKey,
|
||||||
|
mandatory: true,
|
||||||
|
basicProperties: new BasicProperties(eventArgs.BasicProperties),
|
||||||
|
body: eventArgs.Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (_lazyConnection.IsValueCreated)
|
||||||
|
{
|
||||||
|
var connection = await _lazyConnection.Value;
|
||||||
|
await connection.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IConnection> CreateConnectionAsync()
|
||||||
|
{
|
||||||
|
var connection = await _factory.CreateConnectionAsync();
|
||||||
|
using var channel = await connection.CreateChannelAsync();
|
||||||
|
|
||||||
|
// Declare Exchanges
|
||||||
|
await channel.ExchangeDeclareAsync(exchange: _eventExchangeName, type: ExchangeType.Fanout, durable: true);
|
||||||
|
if (_useDelayPlugin)
|
||||||
|
{
|
||||||
|
await channel.ExchangeDeclareAsync(
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
type: "x-delayed-message",
|
||||||
|
durable: true,
|
||||||
|
arguments: new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
{ "x-delayed-type", "direct" }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await channel.ExchangeDeclareAsync(exchange: _integrationExchangeName, type: ExchangeType.Direct, durable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare dead letter queue for Integration exchange
|
||||||
|
await channel.QueueDeclareAsync(queue: _deadLetterQueueName,
|
||||||
|
durable: true,
|
||||||
|
exclusive: false,
|
||||||
|
autoDelete: false,
|
||||||
|
arguments: null);
|
||||||
|
await channel.QueueBindAsync(queue: _deadLetterQueueName,
|
||||||
|
exchange: _integrationExchangeName,
|
||||||
|
routingKey: _deadLetterRoutingKey);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
#nullable enable
|
||||||
|
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Net.Http.Headers;
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Bit.Core.Models.Slack;
|
using Bit.Core.Models.Slack;
|
||||||
@ -22,7 +24,7 @@ public class SlackService(
|
|||||||
|
|
||||||
public async Task<string> GetChannelIdAsync(string token, string channelName)
|
public async Task<string> GetChannelIdAsync(string token, string channelName)
|
||||||
{
|
{
|
||||||
return (await GetChannelIdsAsync(token, [channelName])).FirstOrDefault();
|
return (await GetChannelIdsAsync(token, [channelName])).FirstOrDefault() ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<string>> GetChannelIdsAsync(string token, List<string> channelNames)
|
public async Task<List<string>> GetChannelIdsAsync(string token, List<string> channelNames)
|
||||||
@ -58,7 +60,7 @@ public class SlackService(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogError("Error getting Channel Ids: {Error}", result.Error);
|
logger.LogError("Error getting Channel Ids: {Error}", result?.Error ?? "Unknown Error");
|
||||||
nextCursor = string.Empty;
|
nextCursor = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +91,7 @@ public class SlackService(
|
|||||||
new KeyValuePair<string, string>("redirect_uri", redirectUrl)
|
new KeyValuePair<string, string>("redirect_uri", redirectUrl)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
SlackOAuthResponse result;
|
SlackOAuthResponse? result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = await tokenResponse.Content.ReadFromJsonAsync<SlackOAuthResponse>();
|
result = await tokenResponse.Content.ReadFromJsonAsync<SlackOAuthResponse>();
|
||||||
@ -99,7 +101,7 @@ public class SlackService(
|
|||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null)
|
if (result is null)
|
||||||
{
|
{
|
||||||
logger.LogError("Error obtaining token via OAuth: Unknown error");
|
logger.LogError("Error obtaining token via OAuth: Unknown error");
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
@ -130,6 +132,11 @@ public class SlackService(
|
|||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
var result = await response.Content.ReadFromJsonAsync<SlackUserResponse>();
|
var result = await response.Content.ReadFromJsonAsync<SlackUserResponse>();
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
{
|
||||||
|
logger.LogError("Error retrieving Slack user ID: Unknown error");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
if (!result.Ok)
|
if (!result.Ok)
|
||||||
{
|
{
|
||||||
logger.LogError("Error retrieving Slack user ID: {Error}", result.Error);
|
logger.LogError("Error retrieving Slack user ID: {Error}", result.Error);
|
||||||
@ -151,6 +158,11 @@ public class SlackService(
|
|||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
var result = await response.Content.ReadFromJsonAsync<SlackDmResponse>();
|
var result = await response.Content.ReadFromJsonAsync<SlackDmResponse>();
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
{
|
||||||
|
logger.LogError("Error opening DM channel: Unknown error");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
if (!result.Ok)
|
if (!result.Ok)
|
||||||
{
|
{
|
||||||
logger.LogError("Error opening DM channel: {Error}", result.Error);
|
logger.LogError("Error opening DM channel: {Error}", result.Error);
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Globalization;
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||||
@ -29,7 +31,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory)
|
|||||||
case HttpStatusCode.ServiceUnavailable:
|
case HttpStatusCode.ServiceUnavailable:
|
||||||
case HttpStatusCode.GatewayTimeout:
|
case HttpStatusCode.GatewayTimeout:
|
||||||
result.Retryable = true;
|
result.Retryable = true;
|
||||||
result.FailureReason = response.ReasonPhrase;
|
result.FailureReason = response.ReasonPhrase ?? $"Failure with status code: {(int)response.StatusCode}";
|
||||||
|
|
||||||
if (response.Headers.TryGetValues("Retry-After", out var values))
|
if (response.Headers.TryGetValues("Retry-After", out var values))
|
||||||
{
|
{
|
||||||
@ -52,7 +54,7 @@ public class WebhookIntegrationHandler(IHttpClientFactory httpClientFactory)
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
result.Retryable = false;
|
result.Retryable = false;
|
||||||
result.FailureReason = response.ReasonPhrase;
|
result.FailureReason = response.ReasonPhrase ?? $"Failure with status code {(int)response.StatusCode}";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class EmailTwoFactorTokenProvider : EmailTokenProvider
|
|||||||
|
|
||||||
private static bool HasProperMetaData(TwoFactorProvider provider)
|
private static bool HasProperMetaData(TwoFactorProvider provider)
|
||||||
{
|
{
|
||||||
return provider?.MetaData != null && provider.MetaData.ContainsKey("Email") &&
|
return provider?.MetaData != null && provider.MetaData.TryGetValue("Email", out var emailValue) &&
|
||||||
!string.IsNullOrWhiteSpace((string)provider.MetaData["Email"]);
|
!string.IsNullOrWhiteSpace((string)emailValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
||||||
var keys = LoadKeys(provider);
|
var keys = LoadKeys(provider);
|
||||||
|
|
||||||
if (!provider.MetaData.TryGetValue("login", out var value))
|
if (!provider.MetaData.TryGetValue("login", out var login))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
|
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
|
||||||
var jsonOptions = value.ToString();
|
var jsonOptions = login.ToString();
|
||||||
var options = AssertionOptions.FromJson(jsonOptions);
|
var options = AssertionOptions.FromJson(jsonOptions);
|
||||||
|
|
||||||
var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));
|
var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));
|
||||||
@ -148,9 +148,9 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
for (var i = 1; i <= 5; i++)
|
for (var i = 1; i <= 5; i++)
|
||||||
{
|
{
|
||||||
var keyName = $"Key{i}";
|
var keyName = $"Key{i}";
|
||||||
if (provider.MetaData.ContainsKey(keyName))
|
if (provider.MetaData.TryGetValue(keyName, out var value))
|
||||||
{
|
{
|
||||||
var key = new TwoFactorProvider.WebAuthnData((dynamic)provider.MetaData[keyName]);
|
var key = new TwoFactorProvider.WebAuthnData((dynamic)value);
|
||||||
|
|
||||||
keys.Add(new Tuple<string, TwoFactorProvider.WebAuthnData>(keyName, key));
|
keys.Add(new Tuple<string, TwoFactorProvider.WebAuthnData>(keyName, key));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.Repositories;
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Context;
|
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||||
@ -11,9 +10,6 @@ using Bit.Core.Repositories;
|
|||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Tokens;
|
using Bit.Core.Tokens;
|
||||||
using Bit.Core.Tools.Enums;
|
|
||||||
using Bit.Core.Tools.Models.Business;
|
|
||||||
using Bit.Core.Tools.Services;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
@ -26,15 +22,12 @@ 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;
|
||||||
private readonly IReferenceEventService _referenceEventService;
|
|
||||||
|
|
||||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||||
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> _registrationEmailVerificationTokenDataFactory;
|
||||||
private readonly IDataProtector _organizationServiceDataProtector;
|
private readonly IDataProtector _organizationServiceDataProtector;
|
||||||
private readonly IDataProtector _providerServiceDataProtector;
|
private readonly IDataProtector _providerServiceDataProtector;
|
||||||
|
|
||||||
private readonly ICurrentContext _currentContext;
|
|
||||||
|
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
|
|
||||||
@ -48,11 +41,9 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
IReferenceEventService referenceEventService,
|
|
||||||
IDataProtectionProvider dataProtectionProvider,
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||||
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
|
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
|
||||||
ICurrentContext currentContext,
|
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
|
IValidateRedemptionTokenCommand validateRedemptionTokenCommand,
|
||||||
@ -62,14 +53,12 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_policyRepository = policyRepository;
|
_policyRepository = policyRepository;
|
||||||
_referenceEventService = referenceEventService;
|
|
||||||
|
|
||||||
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
|
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
|
||||||
"OrganizationServiceDataProtector");
|
"OrganizationServiceDataProtector");
|
||||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||||
_registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory;
|
_registrationEmailVerificationTokenDataFactory = registrationEmailVerificationTokenDataFactory;
|
||||||
|
|
||||||
_currentContext = currentContext;
|
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
|
|
||||||
@ -86,7 +75,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -119,12 +107,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
sentWelcomeEmail = true;
|
sentWelcomeEmail = true;
|
||||||
if (!string.IsNullOrEmpty(initiationPath))
|
if (!string.IsNullOrEmpty(initiationPath))
|
||||||
{
|
{
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext)
|
|
||||||
{
|
|
||||||
SignupInitiationPath = initiationPath
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,8 +116,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -263,10 +243,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext)
|
|
||||||
{
|
|
||||||
ReceiveMarketingEmails = tokenable.ReceiveMarketingEmails
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -285,7 +261,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -306,7 +281,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -325,7 +299,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
if (result == IdentityResult.Success)
|
if (result == IdentityResult.Success)
|
||||||
{
|
{
|
||||||
await _mailService.SendWelcomeEmailAsync(user);
|
await _mailService.SendWelcomeEmailAsync(user);
|
||||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -420,7 +420,7 @@ public class OrganizationBillingService(
|
|||||||
var setNonUSBusinessUseToReverseCharge =
|
var setNonUSBusinessUseToReverseCharge =
|
||||||
featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
featureService.IsEnabled(FeatureFlagKeys.PM21092_SetNonUSBusinessUseToReverseCharge);
|
||||||
|
|
||||||
if (setNonUSBusinessUseToReverseCharge)
|
if (setNonUSBusinessUseToReverseCharge && customer.HasBillingLocation())
|
||||||
{
|
{
|
||||||
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ public static class Constants
|
|||||||
|
|
||||||
public const string Fido2KeyCipherMinimumVersion = "2023.10.0";
|
public const string Fido2KeyCipherMinimumVersion = "2023.10.0";
|
||||||
public const string SSHKeyCipherMinimumVersion = "2024.12.0";
|
public const string SSHKeyCipherMinimumVersion = "2024.12.0";
|
||||||
|
public const string DenyLegacyUserMinimumVersion = "2025.6.0";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used by IdentityServer to identify our own provider.
|
/// Used by IdentityServer to identify our own provider.
|
||||||
@ -144,9 +145,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships";
|
public const string PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships";
|
||||||
public const string UsePricingService = "use-pricing-service";
|
public const string UsePricingService = "use-pricing-service";
|
||||||
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
|
public const string PM12276Breadcrumbing = "pm-12276-breadcrumbing-for-business-features";
|
||||||
public const string PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method";
|
|
||||||
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
|
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
|
||||||
public const string PM18770_EnableOrganizationBusinessUnitConversion = "pm-18770-enable-organization-business-unit-conversion";
|
|
||||||
public const string PM199566_UpdateMSPToChargeAutomatically = "pm-199566-update-msp-to-charge-automatically";
|
public const string PM199566_UpdateMSPToChargeAutomatically = "pm-199566-update-msp-to-charge-automatically";
|
||||||
public const string PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup";
|
public const string PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup";
|
||||||
public const string UseOrganizationWarningsService = "use-organization-warnings-service";
|
public const string UseOrganizationWarningsService = "use-organization-warnings-service";
|
||||||
|
@ -64,39 +64,39 @@ public class CurrentContext : ICurrentContext
|
|||||||
HttpContext = httpContext;
|
HttpContext = httpContext;
|
||||||
await BuildAsync(httpContext.User, globalSettings);
|
await BuildAsync(httpContext.User, globalSettings);
|
||||||
|
|
||||||
if (DeviceIdentifier == null && httpContext.Request.Headers.ContainsKey("Device-Identifier"))
|
if (DeviceIdentifier == null && httpContext.Request.Headers.TryGetValue("Device-Identifier", out var deviceIdentifier))
|
||||||
{
|
{
|
||||||
DeviceIdentifier = httpContext.Request.Headers["Device-Identifier"];
|
DeviceIdentifier = deviceIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpContext.Request.Headers.ContainsKey("Device-Type") &&
|
if (httpContext.Request.Headers.TryGetValue("Device-Type", out var deviceType) &&
|
||||||
Enum.TryParse(httpContext.Request.Headers["Device-Type"].ToString(), out DeviceType dType))
|
Enum.TryParse(deviceType.ToString(), out DeviceType dType))
|
||||||
{
|
{
|
||||||
DeviceType = dType;
|
DeviceType = dType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BotScore.HasValue && httpContext.Request.Headers.ContainsKey("X-Cf-Bot-Score") &&
|
if (!BotScore.HasValue && httpContext.Request.Headers.TryGetValue("X-Cf-Bot-Score", out var cfBotScore) &&
|
||||||
int.TryParse(httpContext.Request.Headers["X-Cf-Bot-Score"], out var parsedBotScore))
|
int.TryParse(cfBotScore, out var parsedBotScore))
|
||||||
{
|
{
|
||||||
BotScore = parsedBotScore;
|
BotScore = parsedBotScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpContext.Request.Headers.ContainsKey("X-Cf-Worked-Proxied"))
|
if (httpContext.Request.Headers.TryGetValue("X-Cf-Worked-Proxied", out var cfWorkedProxied))
|
||||||
{
|
{
|
||||||
CloudflareWorkerProxied = httpContext.Request.Headers["X-Cf-Worked-Proxied"] == "1";
|
CloudflareWorkerProxied = cfWorkedProxied == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpContext.Request.Headers.ContainsKey("X-Cf-Is-Bot"))
|
if (httpContext.Request.Headers.TryGetValue("X-Cf-Is-Bot", out var cfIsBot))
|
||||||
{
|
{
|
||||||
IsBot = httpContext.Request.Headers["X-Cf-Is-Bot"] == "1";
|
IsBot = cfIsBot == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpContext.Request.Headers.ContainsKey("X-Cf-Maybe-Bot"))
|
if (httpContext.Request.Headers.TryGetValue("X-Cf-Maybe-Bot", out var cfMaybeBot))
|
||||||
{
|
{
|
||||||
MaybeBot = httpContext.Request.Headers["X-Cf-Maybe-Bot"] == "1";
|
MaybeBot = cfMaybeBot == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpContext.Request.Headers.ContainsKey("Bitwarden-Client-Version") && Version.TryParse(httpContext.Request.Headers["Bitwarden-Client-Version"], out var cVersion))
|
if (httpContext.Request.Headers.TryGetValue("Bitwarden-Client-Version", out var bitWardenClientVersion) && Version.TryParse(bitWardenClientVersion, out var cVersion))
|
||||||
{
|
{
|
||||||
ClientVersion = cVersion;
|
ClientVersion = cVersion;
|
||||||
}
|
}
|
||||||
@ -190,14 +190,14 @@ public class CurrentContext : ICurrentContext
|
|||||||
|
|
||||||
private List<CurrentContextOrganization> GetOrganizations(Dictionary<string, IEnumerable<Claim>> claimsDict, bool orgApi)
|
private List<CurrentContextOrganization> GetOrganizations(Dictionary<string, IEnumerable<Claim>> claimsDict, bool orgApi)
|
||||||
{
|
{
|
||||||
var accessSecretsManager = claimsDict.ContainsKey(Claims.SecretsManagerAccess)
|
var accessSecretsManager = claimsDict.TryGetValue(Claims.SecretsManagerAccess, out var secretsManagerAccessClaim)
|
||||||
? claimsDict[Claims.SecretsManagerAccess].ToDictionary(s => s.Value, _ => true)
|
? secretsManagerAccessClaim.ToDictionary(s => s.Value, _ => true)
|
||||||
: new Dictionary<string, bool>();
|
: new Dictionary<string, bool>();
|
||||||
|
|
||||||
var organizations = new List<CurrentContextOrganization>();
|
var organizations = new List<CurrentContextOrganization>();
|
||||||
if (claimsDict.ContainsKey(Claims.OrganizationOwner))
|
if (claimsDict.TryGetValue(Claims.OrganizationOwner, out var organizationOwnerClaim))
|
||||||
{
|
{
|
||||||
organizations.AddRange(claimsDict[Claims.OrganizationOwner].Select(c =>
|
organizations.AddRange(organizationOwnerClaim.Select(c =>
|
||||||
new CurrentContextOrganization
|
new CurrentContextOrganization
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -214,9 +214,9 @@ public class CurrentContext : ICurrentContext
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimsDict.ContainsKey(Claims.OrganizationAdmin))
|
if (claimsDict.TryGetValue(Claims.OrganizationAdmin, out var organizationAdminClaim))
|
||||||
{
|
{
|
||||||
organizations.AddRange(claimsDict[Claims.OrganizationAdmin].Select(c =>
|
organizations.AddRange(organizationAdminClaim.Select(c =>
|
||||||
new CurrentContextOrganization
|
new CurrentContextOrganization
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -225,9 +225,9 @@ public class CurrentContext : ICurrentContext
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimsDict.ContainsKey(Claims.OrganizationUser))
|
if (claimsDict.TryGetValue(Claims.OrganizationUser, out var organizationUserClaim))
|
||||||
{
|
{
|
||||||
organizations.AddRange(claimsDict[Claims.OrganizationUser].Select(c =>
|
organizations.AddRange(organizationUserClaim.Select(c =>
|
||||||
new CurrentContextOrganization
|
new CurrentContextOrganization
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -236,9 +236,9 @@ public class CurrentContext : ICurrentContext
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimsDict.ContainsKey(Claims.OrganizationCustom))
|
if (claimsDict.TryGetValue(Claims.OrganizationCustom, out var organizationCustomClaim))
|
||||||
{
|
{
|
||||||
organizations.AddRange(claimsDict[Claims.OrganizationCustom].Select(c =>
|
organizations.AddRange(organizationCustomClaim.Select(c =>
|
||||||
new CurrentContextOrganization
|
new CurrentContextOrganization
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -254,9 +254,9 @@ public class CurrentContext : ICurrentContext
|
|||||||
private List<CurrentContextProvider> GetProviders(Dictionary<string, IEnumerable<Claim>> claimsDict)
|
private List<CurrentContextProvider> GetProviders(Dictionary<string, IEnumerable<Claim>> claimsDict)
|
||||||
{
|
{
|
||||||
var providers = new List<CurrentContextProvider>();
|
var providers = new List<CurrentContextProvider>();
|
||||||
if (claimsDict.ContainsKey(Claims.ProviderAdmin))
|
if (claimsDict.TryGetValue(Claims.ProviderAdmin, out var providerAdminClaim))
|
||||||
{
|
{
|
||||||
providers.AddRange(claimsDict[Claims.ProviderAdmin].Select(c =>
|
providers.AddRange(providerAdminClaim.Select(c =>
|
||||||
new CurrentContextProvider
|
new CurrentContextProvider
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -264,9 +264,9 @@ public class CurrentContext : ICurrentContext
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimsDict.ContainsKey(Claims.ProviderServiceUser))
|
if (claimsDict.TryGetValue(Claims.ProviderServiceUser, out var providerServiceUserClaim))
|
||||||
{
|
{
|
||||||
providers.AddRange(claimsDict[Claims.ProviderServiceUser].Select(c =>
|
providers.AddRange(providerServiceUserClaim.Select(c =>
|
||||||
new CurrentContextProvider
|
new CurrentContextProvider
|
||||||
{
|
{
|
||||||
Id = new Guid(c.Value),
|
Id = new Guid(c.Value),
|
||||||
@ -504,20 +504,20 @@ public class CurrentContext : ICurrentContext
|
|||||||
|
|
||||||
private string GetClaimValue(Dictionary<string, IEnumerable<Claim>> claims, string type)
|
private string GetClaimValue(Dictionary<string, IEnumerable<Claim>> claims, string type)
|
||||||
{
|
{
|
||||||
if (!claims.ContainsKey(type))
|
if (!claims.TryGetValue(type, out var claim))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims[type].FirstOrDefault()?.Value;
|
return claim.FirstOrDefault()?.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary<string, IEnumerable<Claim>> claimsDict)
|
private Permissions SetOrganizationPermissionsFromClaims(string organizationId, Dictionary<string, IEnumerable<Claim>> claimsDict)
|
||||||
{
|
{
|
||||||
bool hasClaim(string claimKey)
|
bool hasClaim(string claimKey)
|
||||||
{
|
{
|
||||||
return claimsDict.ContainsKey(claimKey) ?
|
return claimsDict.TryGetValue(claimKey, out var claim) ?
|
||||||
claimsDict[claimKey].Any(x => x.Value == organizationId) : false;
|
claim.Any(x => x.Value == organizationId) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Permissions
|
return new Permissions
|
||||||
|
@ -3,7 +3,6 @@ using System.Text.Json;
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Tools.Entities;
|
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
@ -11,7 +10,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
|
|
||||||
namespace Bit.Core.Entities;
|
namespace Bit.Core.Entities;
|
||||||
|
|
||||||
public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFactorProvidersUser, IReferenceable
|
public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFactorProvidersUser
|
||||||
{
|
{
|
||||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider>? _twoFactorProviders;
|
private Dictionary<TwoFactorProviderType, TwoFactorProvider>? _twoFactorProviders;
|
||||||
|
|
||||||
@ -196,12 +195,7 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
||||||
{
|
{
|
||||||
var providers = GetTwoFactorProviders();
|
var providers = GetTwoFactorProviders();
|
||||||
if (providers == null || !providers.TryGetValue(provider, out var value))
|
return providers?.GetValueOrDefault(provider);
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long StorageBytesRemaining()
|
public long StorageBytesRemaining()
|
||||||
|
@ -63,6 +63,6 @@ public class DistributedCacheCookieManager : ICookieManager
|
|||||||
private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}";
|
private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}";
|
||||||
|
|
||||||
private string GetId(HttpContext context, string key) =>
|
private string GetId(HttpContext context, string key) =>
|
||||||
context.Request.Cookies.ContainsKey(key) ?
|
context.Request.Cookies.TryGetValue(key, out var cookie) ?
|
||||||
context.Request.Cookies[key] : null;
|
cookie : null;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@
|
|||||||
<td class="aligncenter social-icons" align="center" style="margin: 0; padding: 15px 0 0 0;" valign="top">
|
<td class="aligncenter social-icons" align="center" style="margin: 0; padding: 15px 0 0 0;" valign="top">
|
||||||
<table cellpadding="0" cellspacing="0" style="margin: 0 auto;">
|
<table cellpadding="0" cellspacing="0" style="margin: 0 auto;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://twitter.com/bitwarden" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-twitter.png" alt="Twitter" width="30" height="30" /></a></td>
|
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://x.com/bitwarden" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-x.png" alt="X" width="30" height="30" /></a></td>
|
||||||
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://www.reddit.com/r/Bitwarden/" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-reddit.png" alt="Reddit" width="30" height="30" /></a></td>
|
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://www.reddit.com/r/Bitwarden/" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-reddit.png" alt="Reddit" width="30" height="30" /></a></td>
|
||||||
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://community.bitwarden.com/" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-discourse.png" alt="CommunityForums" width="30" height="30" /></a></td>
|
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://community.bitwarden.com/" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-discourse.png" alt="CommunityForums" width="30" height="30" /></a></td>
|
||||||
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/bitwarden" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-github.png" alt="GitHub" width="30" height="30" /></a></td>
|
<td style="margin: 0; padding: 0 10px;" valign="top"><a href="https://github.com/bitwarden" target="_blank"><img src="https://assets.bitwarden.com/email/v1/mail-github.png" alt="GitHub" width="30" height="30" /></a></td>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user