Merge remote-tracking branch 'origin/master' into feature/self-hosted-families-for-enterprise

This commit is contained in:
Matt Gibson
2022-02-16 10:55:17 -05:00
198 changed files with 90914 additions and 2274 deletions

View File

@@ -29,6 +29,8 @@ jobs:
testing:
name: Testing
runs-on: windows-2019
env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Set up NuGet
uses: nuget/setup-nuget@04b0c2b8d1b97922f67eca497d7cf0bf17b8ffe1
@@ -38,29 +40,26 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d
- name: Set up Node
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea
with:
node-version: '14'
- name: Update NPM
run: npm install -g npm@7
- name: Print environment
run: |
nuget help | grep Version
msbuild -version
dotnet --info
node --version
npm --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- uses: actions/cache@937d24475381cd9c75ae6db12cb4e79714b926ed # 2.1.7
with:
path: ${{ github.workspace }}/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Restore
run: msbuild /t:restore
run: msbuild /t:restore /p:RestoreLockedMode=true
shell: pwsh
- name: Build solution
@@ -190,91 +189,61 @@ jobs:
include:
- service_name: Admin
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Api
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Attachments
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
- service_name: Events
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: EventsProcessor
base_path: ./src
docker_repo: bitwardenqa.azurecr.io
docker_repos: [bitwardenqa.azurecr.io]
dotnet: true
- service_name: Icons
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Identity
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: K8S-Proxy
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
- service_name: MsSql
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
- service_name: Nginx
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
- service_name: Notifications
base_path: ./src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Server
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Setup
base_path: ./util
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
- service_name: Sso
base_path: ./bitwarden_license/src
docker_repo: bitwarden
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
dotnet: true
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Login to Azure - Prod Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "docker-password,
docker-username,
dct-delegate-2-repo-passphrase,
dct-delegate-2-key"
- name: Log into Docker
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
env:
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- name: Setup Docker Trust
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
env:
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
run: |
mkdir -p ~/.docker/trust/private
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
########## Build Docker Image ##########
- name: Setup service name
id: setup
run: |
@@ -306,43 +275,7 @@ jobs:
${{ matrix.base_path }}/${{ matrix.service_name }}
fi
- name: Docker Trust setup
if: |
matrix.docker_repo == 'bitwarden'
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix')
env:
DCT_REPO_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
run: |
echo "DOCKER_CONTENT_TRUST=1" >> $GITHUB_ENV
echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV
- name: Tag and Push RC to Docker Hub
if: (github.ref == 'refs/heads/rc' && matrix.docker_repo == 'bitwarden')
run: |
docker tag ${{ steps.setup.outputs.service_name }} \
${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:rc
docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:rc
- name: Tag and Push Hotfix to Docker Hub
if: (github.ref == 'refs/heads/hotfix' && matrix.docker_repo == 'bitwarden')
run: |
docker tag ${{ steps.setup.outputs.service_name }} \
${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:hotfix
docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:hotfix
- name: Tag and Push Dev to Docker Hub
if: (github.ref == 'refs/heads/master' && matrix.docker_repo == 'bitwarden')
run: |
docker tag ${{ steps.setup.outputs.service_name }} \
${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev
docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev
- name: Log out of Docker and disable Docker Notary
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
########## ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
@@ -352,31 +285,89 @@ jobs:
run: az acr login -n bitwardenqa
- name: Tag and Push RC to Azure ACR QA registry
if: github.ref == 'refs/heads/rc'
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
docker tag ${{ steps.setup.outputs.service_name }} \
$REGISTRY/${{ steps.setup.outputs.service_name }}:rc
docker push $REGISTRY/${{ steps.setup.outputs.service_name }}:rc
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
- name: Tag and Push Hotfix to Azure ACR QA registry
if: github.ref == 'refs/heads/hotfix'
env:
REGISTRY: bitwardenqa.azurecr.io
run: |
docker tag ${{ steps.setup.outputs.service_name }} \
$REGISTRY/${{ steps.setup.outputs.service_name }}:hotfix
docker push $REGISTRY/${{ steps.setup.outputs.service_name }}:hotfix
$REGISTRY/${{ steps.setup.outputs.service_name }}:$IMAGE_TAG
docker push $REGISTRY/${{ steps.setup.outputs.service_name }}:$IMAGE_TAG
- name: Tag and Push Dev to Azure ACR QA registry
if: github.ref == 'refs/heads/master'
- name: Log out of Docker
run: docker logout
########## DockerHub ##########
- name: Login to Azure - Prod Subscription
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
with:
keyvault: "bitwarden-prod-kv"
secrets: "docker-password,
docker-username,
dct-delegate-2-repo-passphrase,
dct-delegate-2-key"
- name: Log into Docker
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
env:
REGISTRY: bitwardenqa.azurecr.io
DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }}
DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }}
run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- name: Setup Docker Trust
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
env:
DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c"
DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }}
DCT_REPO_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }}
run: |
mkdir -p ~/.docker/trust/private
echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key
echo "DOCKER_CONTENT_TRUST=1" >> $GITHUB_ENV
echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV
- name: Tag and Push RC to Docker Hub
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
env:
REGISTRY: bitwarden
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag ${{ steps.setup.outputs.service_name }} \
$REGISTRY/${{ steps.setup.outputs.service_name }}:dev
docker push $REGISTRY/${{ steps.setup.outputs.service_name }}:dev
$REGISTRY/${{ steps.setup.outputs.service_name }}:$IMAGE_TAG
docker push $REGISTRY/${{ steps.setup.outputs.service_name }}:$IMAGE_TAG
- name: Log out of Docker and disable Docker Notary
if: |
contains(matrix.docker_repos, 'bitwarden')
&& (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
run: |
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
upload:
@@ -391,12 +382,12 @@ jobs:
run: dotnet tool restore
- name: Make Docker stub
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
run: |
if [[ "${{ github.ref }}" == "rc" ]]; then
SETUP_IMAGE="bitwarden/setup:rc"
elif [[ "${{ github.ref }}" == "hotfix" ]]; then
SETUP_IMAGE="bitwarden/setup:hotfix"
elif [[ "${{ github.ref }}" == "hotfix-rc" ]]; then
SETUP_IMAGE="bitwarden/setup:hotfix-rc"
else
SETUP_IMAGE="bitwarden/setup:dev"
fi
@@ -411,7 +402,7 @@ jobs:
cd docker-stub; zip -r ../docker-stub.zip *; cd ..
- name: Upload Docker stub artifact
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix'
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700
with:
name: docker-stub.zip
@@ -458,7 +449,7 @@ jobs:
if: |
github.ref == 'refs/heads/master'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix'
|| github.ref == 'refs/heads/hotfix-rc'
env:
CLOC_STATUS: ${{ needs.cloc.result }}
TESTING_STATUS: ${{ needs.testing.result }}

66
.github/workflows/cleanup-after-pr.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
---
name: Clean After PR
on:
pull_request:
types: [closed]
jobs:
build-docker:
name: Remove feature branch docker images
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
########## ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
########## Remove Docker images ##########
- name: Remove the docker image from ACR
env:
REGISTRY_NAME: bitwardenqa
SERVICES: |
services:
- Admin
- Api
- Attachments
- Events
- EventsProcessor
- Icons
- Identity
- K8S-Proxy
- MsSql
- Nginx
- Notifications
- Server
- Setup
- Sso
run: |
for SERVICE in $(echo "${{ env.SERVICES }}" | yq e ".services[]" - )
do
SERVICE_NAME=$(echo $SERVICE | awk '{print tolower($0)}')
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
echo "[*] Checking if remote exists: $REGISTRY_NAME.azurecr.io/$SERVICE_NAME:$IMAGE_TAG"
TAG_EXISTS=$(
az acr repository show-tags --name $REGISTRY_NAME --repository $SERVICE_NAME \
| jq --arg $TAG "$IMAGE_TAG" -e '. | any(. == "$TAG")'
)
if [[ "$TAG_EXISTS" == "true" ]]; then
echo "[*] Tag exists. Removing tag"
az acr repository delete --name $REGISTRY_NAME --image $SERVICE_NAME:$IMAGE_TAG --yes
else
echo "[*] Tag does not exist. No action needed"
fi
done
- name: Log out of Docker
run: docker logout

View File

@@ -0,0 +1,96 @@
---
name: Container Registry Purge
on:
schedule:
- cron: '0 0 * * SUN'
workflow_dispatch:
inputs: {}
jobs:
purge:
name: Purge old images
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
include:
- name: bitwardenqa
- name: bitwardenprod
steps:
- name: Login to Azure
if: matrix.name == 'bitwardenprod'
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to Azure
if: matrix.name == 'bitwardenqa'
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Purge images
env:
REGISTRY: ${{ matrix.name }}
AGO_DUR_VER: "180d"
AGO_DUR: "30d"
run: |
REPO_LIST=$(az acr repository list -n $REGISTRY -o tsv)
for REPO in $REPO_LIST
do
TAG_LIST=$(az acr repository show-tags -n $REGISTRY --repository $REPO -o tsv)
for TAG in $TAG_LIST
do
if [ $TAG = "latest" ]; then
PURGE_CMD="acr purge --filter '$REPO:$TAG' --ago $AGO_DUR_VER --untagged --keep 1"
elif [[ $TAG =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
PURGE_CMD="acr purge --filter '$REPO:$TAG' --ago $AGO_DUR_VER --untagged"
else
PURGE_CMD="acr purge --filter '$REPO:$TAG' --ago $AGO_DUR --untagged"
fi
az acr run --cmd "$PURGE_CMD" --registry $REGISTRY /dev/null
done
done
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-20.04
needs:
- purge
steps:
- name: Check if any job failed
if: |
github.ref == 'refs/heads/master'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc'
env:
PURGE_STATUS: ${{ needs.purge.result }}
run: |
if [ "$PURGE_STATUS" = "failure" ]; then
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: Azure/get-keyvault-secrets@470858ec5d16542d1a87e1998c0988130ef304dd
if: failure()
with:
keyvault: "bitwarden-prod-kv"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@186ef8b81dc1f91522af6998d8019e2e886da2ca # v1.2.2
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

16
.github/workflows/enforce-labels.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
---
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: EnforceLabel
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with:
BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@@ -24,9 +24,9 @@ jobs:
steps:
- name: Branch check
run: |
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix-rc" ]]; then
echo "==================================="
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
echo "[!] Can only release from the 'rc' or 'hotfix-rc' branches"
echo "==================================="
exit 1
fi

View File

@@ -2,8 +2,9 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>1.45.3</Version>
<Version>1.46.1</Version>
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
</Project>
</Project>

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Entities;
@@ -25,7 +24,6 @@ using Microsoft.IdentityModel.Tokens;
using Sustainsys.Saml2.AspNetCore2;
using Sustainsys.Saml2.Configuration;
using Sustainsys.Saml2.Saml2P;
using U2F.Core.Utils;
namespace Bit.Core.Business.Sso
{
@@ -412,7 +410,7 @@ namespace Bit.Core.Business.Sso
}
if (!string.IsNullOrWhiteSpace(config.IdpX509PublicCert))
{
var cert = config.IdpX509PublicCert.Base64StringToByteArray();
var cert = CoreHelpers.Base64UrlDecode(config.IdpX509PublicCert);
idp.SigningKeys.AddConfiguredKey(new X509Certificate2(cert));
}
// This must happen last since it calls Validate() internally.

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

File diff suppressed because it is too large Load Diff

View File

@@ -26,9 +26,9 @@ $scriptsDir = "${output}\scripts"
$githubBaseUrl = "https://raw.githubusercontent.com/bitwarden/server/master"
# Please do not create pull requests modifying the version numbers.
$coreVersion = "1.45.2"
$webVersion = "2.25.0"
$keyConnectorVersion = "1.0.0"
$coreVersion = "1.46.1"
$webVersion = "2.26.1"
$keyConnectorVersion = "1.0.1"
# Functions

View File

@@ -34,9 +34,9 @@ SCRIPTS_DIR="$OUTPUT/scripts"
GITHUB_BASE_URL="https://raw.githubusercontent.com/bitwarden/server/master"
# Please do not create pull requests modifying the version numbers.
COREVERSION="1.45.2"
WEBVERSION="2.25.0"
KEYCONNECTORVERSION="1.0.0"
COREVERSION="1.46.1"
WEBVERSION="2.26.1"
KEYCONNECTORVERSION="1.0.1"
echo "bitwarden.sh version $COREVERSION"
docker --version

View File

@@ -191,7 +191,7 @@ function updatebw() {
KEY_CONNECTOR_ENABLED=$(grep -A3 'enable_key_connector:' $OUTPUT_DIR/config.yml | tail -n1 | awk '{ print $2}')
CORE_ID=$(docker-compose ps -q admin)
WEB_ID=$(docker-compose ps -q web)
if [ $KEY_CONNECTOR_ENABLED = true ];
if [ "$KEY_CONNECTOR_ENABLED" = true ];
then
KEYCONNECTOR_ID=$(docker-compose ps -q key-connector)
fi

View File

@@ -1,11 +1,12 @@
using System.Diagnostics;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Bit.Admin.Models;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
namespace Bit.Admin.Controllers
{
@@ -37,20 +38,21 @@ namespace Bit.Admin.Controllers
});
}
public async Task<IActionResult> GetLatestDockerHubVersion(string repository)
public async Task<IActionResult> GetLatestDockerHubVersion(string repository, CancellationToken cancellationToken)
{
try
{
var response = await _httpClient.GetAsync(
$"https://hub.docker.com/v2/repositories/bitwarden/{repository}/tags/");
$"https://hub.docker.com/v2/repositories/bitwarden/{repository}/tags/", cancellationToken);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var data = JObject.Parse(json);
var results = data["results"] as JArray;
foreach (var result in results)
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken);
var root = jsonDocument.RootElement;
var results = root.GetProperty("results");
foreach (var result in results.EnumerateArray())
{
var name = result["name"].ToString();
var name = result.GetProperty("name").GetString();
if (!string.IsNullOrWhiteSpace(name) && name.Length > 0 && char.IsNumber(name[0]))
{
return new JsonResult(name);
@@ -63,17 +65,17 @@ namespace Bit.Admin.Controllers
return new JsonResult("-");
}
public async Task<IActionResult> GetInstalledWebVersion()
public async Task<IActionResult> GetInstalledWebVersion(CancellationToken cancellationToken)
{
try
{
var response = await _httpClient.GetAsync(
$"{_globalSettings.BaseServiceUri.InternalVault}/version.json");
$"{_globalSettings.BaseServiceUri.InternalVault}/version.json", cancellationToken);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
var data = JObject.Parse(json);
return new JsonResult(data["version"].ToString());
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken);
var root = jsonDocument.RootElement;
return new JsonResult(root.GetProperty("version").GetString());
}
}
catch (HttpRequestException) { }

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Admin.Models;
using Bit.Core;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -14,7 +14,6 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Bit.Admin.Controllers
{
@@ -264,14 +263,16 @@ namespace Bit.Admin.Controllers
{
var license = await _organizationService.GenerateLicenseAsync(organization,
model.InstallationId.Value, model.Version);
return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)),
"text/plain", "bitwarden_organization_license.json");
var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented);
return File(ms, "text/plain", "bitwarden_organization_license.json");
}
else if (user != null)
{
var license = await _userService.GenerateLicenseAsync(user, null, model.Version);
return File(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(license, Formatting.Indented)),
"text/plain", "bitwarden_premium_license.json");
var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented);
return File(ms, "text/plain", "bitwarden_premium_license.json");
}
else
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Azure.Storage.Queues;
@@ -11,8 +12,6 @@ using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Bit.Admin.HostedServices
{
@@ -70,17 +69,19 @@ namespace Bit.Admin.HostedServices
{
try
{
var token = JToken.Parse(message.DecodeMessageText());
if (token is JArray)
using var document = JsonDocument.Parse(message.DecodeMessageText());
var root = document.RootElement;
if (root.ValueKind == JsonValueKind.Array)
{
foreach (var mailQueueMessage in token.ToObject<List<MailQueueMessage>>())
foreach (var mailQueueMessage in root.ToObject<List<MailQueueMessage>>())
{
await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage);
}
}
else if (token is JObject)
else if (root.ValueKind == JsonValueKind.Object)
{
var mailQueueMessage = token.ToObject<MailQueueMessage>();
var mailQueueMessage = root.ToObject<MailQueueMessage>();
await _mailService.SendEnqueuedMailMessageAsync(mailQueueMessage);
}
}

View File

@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using Bit.Core.Settings;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Bit.Admin.HostedServices
{
@@ -65,7 +64,7 @@ namespace Bit.Admin.HostedServices
request.RequestUri = new Uri("https://api.cloudflare.com/" +
$"client/v4/zones/{_adminSettings.Cloudflare.ZoneId}/firewall/access_rules/rules");
var bodyContent = JsonConvert.SerializeObject(new
request.Content = JsonContent.Create(new
{
mode = "block",
configuration = new
@@ -75,7 +74,6 @@ namespace Bit.Admin.HostedServices
},
notes = $"Rate limit abuse on {DateTime.UtcNow.ToString()}."
});
request.Content = new StringContent(bodyContent, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
@@ -83,8 +81,7 @@ namespace Bit.Admin.HostedServices
return;
}
var responseString = await response.Content.ReadAsStringAsync();
var accessRuleResponse = JsonConvert.DeserializeObject<AccessRuleResponse>(responseString);
var accessRuleResponse = await response.Content.ReadFromJsonAsync<AccessRuleResponse>(cancellationToken: cancellationToken);
if (!accessRuleResponse.Success)
{
return;
@@ -118,8 +115,7 @@ namespace Bit.Admin.HostedServices
return;
}
var responseString = await response.Content.ReadAsStringAsync();
var listResponse = JsonConvert.DeserializeObject<ListResponse>(responseString);
var listResponse = await response.Content.ReadFromJsonAsync<ListResponse>(cancellationToken: cancellationToken);
if (!listResponse.Success)
{
return;

3489
src/Admin/packages.lock.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Messaging.EventGrid;
using Bit.Api.Models.Request;
@@ -21,7 +22,6 @@ using Core.Models.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Api.Controllers
{
@@ -717,7 +717,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id), userId);
var result = await _cipherService.GetAttachmentDownloadDataAsync(cipher, attachmentId);
return new AttachmentResponseModel(result.Id, result.Data, result.Cipher, _globalSettings);
return new AttachmentResponseModel(result);
}
[HttpPost("{id}/attachment/{attachmentId}/share")]
@@ -802,7 +802,7 @@ namespace Bit.Api.Controllers
}
catch (Exception e)
{
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonConvert.SerializeObject(eventGridEvent)}");
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
return;
}
}

View File

@@ -148,6 +148,11 @@ namespace Bit.Api.Controllers
[HttpPut("{id}/users")]
public async Task PutUsers(string orgId, string id, [FromBody] IEnumerable<SelectionReadOnlyRequestModel> model)
{
if (!await CanEditCollectionAsync(orgId, id))
{
throw new NotFoundException();
}
var collection = await GetCollectionAsync(new Guid(id), new Guid(orgId));
await _collectionRepository.UpdateUsersAsync(collection.Id, model?.Select(g => g.ToSelectionReadOnly()));
}
@@ -220,7 +225,7 @@ namespace Bit.Api.Controllers
if (await _currentContext.EditAssignedCollections(orgId))
{
return null != _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
return null != await _collectionRepository.GetByIdAsync(collectionId, _currentContext.UserId.Value);
}
return false;

View File

@@ -175,7 +175,7 @@ namespace Bit.Api.Controllers
var user = await _userService.GetUserByPrincipalAsync(User);
var result =
await _emergencyAccessService.GetAttachmentDownloadAsync(new Guid(id), cipherId, attachmentId, user);
return new AttachmentResponseModel(result.Id, result.Data, result.Cipher, _globalSettings);
return new AttachmentResponseModel(result);
}
}
}

View File

@@ -85,14 +85,16 @@ namespace Bit.Api.Controllers
[SelfHosted(NotSelfHostedOnly = true)]
public async Task<bool> PreValidateSponsorshipToken([FromQuery] string sponsorshipToken)
{
return await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
return (await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email)).valid;
}
[HttpPost("redeem")]
[SelfHosted(NotSelfHostedOnly = true)]
public async Task RedeemSponsorship([FromQuery] string sponsorshipToken, [FromBody] OrganizationSponsorshipRedeemRequestModel model)
{
if (!await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email))
var (valid, sponsorship) = await _validateRedemptionTokenCommand.ValidateRedemptionTokenAsync(sponsorshipToken, (await CurrentUser).Email);
if (!valid)
{
throw new BadRequestException("Failed to parse sponsorship token.");
}
@@ -103,8 +105,7 @@ namespace Bit.Api.Controllers
}
await _setUpSponsorshipCommand.SetUpSponsorshipAsync(
await _organizationSponsorshipRepository
.GetByOfferedToEmailAsync((await CurrentUser).Email),
sponsorship,
await _organizationRepository.GetByIdAsync(model.SponsoredOrganizationId));
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Accounts;
@@ -17,7 +18,6 @@ using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Bit.Api.Controllers
{
@@ -185,7 +185,7 @@ namespace Bit.Api.Controllers
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false);
}
var data = JsonConvert.DeserializeObject<ResetPasswordDataModel>(resetPasswordPolicy.Data);
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data);
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false);
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Messaging.EventGrid;
using Bit.Api.Models.Request;
@@ -19,7 +20,6 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Api.Controllers
{
@@ -227,7 +227,7 @@ namespace Bit.Api.Controllers
var userId = _userService.GetProperUserId(User).Value;
var sendId = new Guid(id);
var send = await _sendRepository.GetByIdAsync(sendId);
var fileData = JsonConvert.DeserializeObject<SendFileData>(send?.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data);
if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) ||
!send.UserId.HasValue || fileData.Id != fileId || fileData.Validated)
@@ -289,7 +289,7 @@ namespace Bit.Api.Controllers
}
catch (Exception e)
{
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonConvert.SerializeObject(eventGridEvent)}");
_logger.LogError(e, $"Uncaught exception occurred while handling event grid event: {JsonSerializer.Serialize(eventGridEvent)}");
return;
}
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Text.Json;
using Bit.Core.Entities;
using Newtonsoft.Json;
namespace Bit.Api.Models.Public.Request
{
@@ -17,7 +17,7 @@ namespace Bit.Api.Models.Public.Request
public virtual Policy ToPolicy(Policy existingPolicy)
{
existingPolicy.Enabled = Enabled.GetValueOrDefault();
existingPolicy.Data = Data != null ? JsonConvert.SerializeObject(Data) : null;
existingPolicy.Data = Data != null ? JsonSerializer.Serialize(Data) : null;
return existingPolicy;
}
}

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Newtonsoft.Json;
using Bit.Core.Utilities;
namespace Bit.Api.Models.Public.Response
{
@@ -24,7 +25,7 @@ namespace Bit.Api.Models.Public.Response
Enabled = policy.Enabled;
if (!string.IsNullOrWhiteSpace(policy.Data))
{
Data = JsonConvert.DeserializeObject<Dictionary<string, object>>(policy.Data);
Data = JsonSerializer.Deserialize<Dictionary<string, object>>(policy.Data);
}
}

View File

@@ -2,13 +2,14 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Core.Models.Data;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NS = Newtonsoft.Json;
using NSL = Newtonsoft.Json.Linq;
namespace Bit.Api.Models.Request
{
@@ -69,22 +70,20 @@ namespace Bit.Api.Models.Request
switch (existingCipher.Type)
{
case CipherType.Login:
var loginObj = JObject.FromObject(ToCipherLoginData(),
new JsonSerializer { NullValueHandling = NullValueHandling.Ignore });
var loginObj = NSL.JObject.FromObject(ToCipherLoginData(),
new NS.JsonSerializer { NullValueHandling = NS.NullValueHandling.Ignore });
// TODO: Switch to JsonNode in .NET 6 https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter?pivots=dotnet-6-0
loginObj[nameof(CipherLoginData.Uri)]?.Parent?.Remove();
existingCipher.Data = loginObj.ToString(Formatting.None);
existingCipher.Data = loginObj.ToString(NS.Formatting.None);
break;
case CipherType.Card:
existingCipher.Data = JsonConvert.SerializeObject(ToCipherCardData(),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
existingCipher.Data = JsonSerializer.Serialize(ToCipherCardData(), JsonHelpers.IgnoreWritingNull);
break;
case CipherType.Identity:
existingCipher.Data = JsonConvert.SerializeObject(ToCipherIdentityData(),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
existingCipher.Data = JsonSerializer.Serialize(ToCipherIdentityData(), JsonHelpers.IgnoreWritingNull);
break;
case CipherType.SecureNote:
existingCipher.Data = JsonConvert.SerializeObject(ToCipherSecureNoteData(),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
existingCipher.Data = JsonSerializer.Serialize(ToCipherSecureNoteData(), JsonHelpers.IgnoreWritingNull);
break;
default:
throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");

View File

@@ -9,8 +9,8 @@ using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using Bit.Core.Sso;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using U2F.Core.Utils;
namespace Bit.Api.Models.Request.Organizations
{
@@ -147,7 +147,7 @@ namespace Bit.Api.Models.Request.Organizations
ValidationResult failedResult = null;
try
{
var certData = StripPemCertificateElements(IdpX509PublicCert).Base64StringToByteArray();
var certData = CoreHelpers.Base64UrlDecode(StripPemCertificateElements(IdpX509PublicCert));
new X509Certificate2(certData);
}
catch (FormatException)

View File

@@ -1,12 +1,12 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Api.Models.Request
{
@@ -65,15 +65,13 @@ namespace Bit.Api.Models.Request
switch (existingSend.Type)
{
case SendType.File:
var fileData = JsonConvert.DeserializeObject<SendFileData>(existingSend.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(existingSend.Data);
fileData.Name = Name;
fileData.Notes = Notes;
existingSend.Data = JsonConvert.SerializeObject(fileData,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
existingSend.Data = JsonSerializer.Serialize(fileData, JsonHelpers.IgnoreWritingNull);
break;
case SendType.Text:
existingSend.Data = JsonConvert.SerializeObject(ToSendData(),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
existingSend.Data = JsonSerializer.Serialize(ToSendTextData(), JsonHelpers.IgnoreWritingNull);
break;
default:
throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");
@@ -130,7 +128,7 @@ namespace Bit.Api.Models.Request
return existingSend;
}
private SendData ToSendData()
private SendTextData ToSendTextData()
{
return new SendTextData(Name, Notes, Text.Text, Text.Hidden);
}

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Newtonsoft.Json;
namespace Bit.Api.Models.Request
{
@@ -12,9 +12,9 @@ namespace Bit.Api.Models.Request
public User ToUser(User existingUser)
{
existingUser.EquivalentDomains = EquivalentDomains != null ? JsonConvert.SerializeObject(EquivalentDomains) : null;
existingUser.EquivalentDomains = EquivalentDomains != null ? JsonSerializer.Serialize(EquivalentDomains) : null;
existingUser.ExcludedGlobalEquivalentDomains = ExcludedGlobalEquivalentDomains != null ?
JsonConvert.SerializeObject(ExcludedGlobalEquivalentDomains) : null;
JsonSerializer.Serialize(ExcludedGlobalEquivalentDomains) : null;
return existingUser;
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
@@ -10,6 +11,16 @@ namespace Bit.Api.Models.Response
{
public class AttachmentResponseModel : ResponseModel
{
public AttachmentResponseModel(AttachmentResponseData data) : base("attachment")
{
Id = data.Id;
Url = data.Url;
FileName = data.Data.FileName;
Key = data.Data.Key;
Size = data.Data.Size;
SizeName = CoreHelpers.ReadableBytesSize(data.Data.Size);
}
public AttachmentResponseModel(string id, CipherAttachment.MetaData data, Cipher cipher,
IGlobalSettings globalSettings)
: base("attachment")
@@ -18,7 +29,7 @@ namespace Bit.Api.Models.Response
Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}";
FileName = data.FileName;
Key = data.Key;
Size = data.SizeString;
Size = data.Size;
SizeName = CoreHelpers.ReadableBytesSize(data.Size);
}
@@ -26,7 +37,8 @@ namespace Bit.Api.Models.Response
public string Url { get; set; }
public string FileName { get; set; }
public string Key { get; set; }
public string Size { get; set; }
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public long Size { get; set; }
public string SizeName { get; set; }
public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, IGlobalSettings globalSettings)

View File

@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
using Core.Models.Data;
using Newtonsoft.Json;
namespace Bit.Api.Models.Response
{
@@ -28,25 +28,25 @@ namespace Bit.Api.Models.Response
switch (cipher.Type)
{
case CipherType.Login:
var loginData = JsonConvert.DeserializeObject<CipherLoginData>(cipher.Data);
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data);
cipherData = loginData;
Data = loginData;
Login = new CipherLoginModel(loginData);
break;
case CipherType.SecureNote:
var secureNoteData = JsonConvert.DeserializeObject<CipherSecureNoteData>(cipher.Data);
var secureNoteData = JsonSerializer.Deserialize<CipherSecureNoteData>(cipher.Data);
Data = secureNoteData;
cipherData = secureNoteData;
SecureNote = new CipherSecureNoteModel(secureNoteData);
break;
case CipherType.Card:
var cardData = JsonConvert.DeserializeObject<CipherCardData>(cipher.Data);
var cardData = JsonSerializer.Deserialize<CipherCardData>(cipher.Data);
Data = cardData;
cipherData = cardData;
Card = new CipherCardModel(cardData);
break;
case CipherType.Identity:
var identityData = JsonConvert.DeserializeObject<CipherIdentityData>(cipher.Data);
var identityData = JsonSerializer.Deserialize<CipherIdentityData>(cipher.Data);
Data = identityData;
cipherData = identityData;
Identity = new CipherIdentityModel(identityData);

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Newtonsoft.Json;
namespace Bit.Api.Models.Response
{
@@ -19,10 +19,10 @@ namespace Bit.Api.Models.Response
}
EquivalentDomains = user.EquivalentDomains != null ?
JsonConvert.DeserializeObject<List<List<string>>>(user.EquivalentDomains) : null;
JsonSerializer.Deserialize<List<List<string>>>(user.EquivalentDomains) : null;
var excludedGlobalEquivalentDomains = user.ExcludedGlobalEquivalentDomains != null ?
JsonConvert.DeserializeObject<List<GlobalEquivalentDomainsType>>(user.ExcludedGlobalEquivalentDomains) :
JsonSerializer.Deserialize<List<GlobalEquivalentDomainsType>>(user.ExcludedGlobalEquivalentDomains) :
new List<GlobalEquivalentDomainsType>();
var globalDomains = new List<GlobalDomains>();
var domainsToInclude = excluded ? Core.Utilities.StaticStore.GlobalDomains :

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Newtonsoft.Json;
namespace Bit.Api.Models.Response
{
@@ -23,7 +23,7 @@ namespace Bit.Api.Models.Response
Enabled = policy.Enabled;
if (!string.IsNullOrWhiteSpace(policy.Data))
{
Data = JsonConvert.DeserializeObject<Dictionary<string, object>>(policy.Data);
Data = JsonSerializer.Deserialize<Dictionary<string, object>>(policy.Data);
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Api.Models.Response
{
@@ -26,12 +26,12 @@ namespace Bit.Api.Models.Response
switch (send.Type)
{
case SendType.File:
var fileData = JsonConvert.DeserializeObject<SendFileData>(send.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send.Data);
sendData = fileData;
File = new SendFileModel(fileData);
break;
case SendType.Text:
var textData = JsonConvert.DeserializeObject<SendTextData>(send.Data);
var textData = JsonSerializer.Deserialize<SendTextData>(send.Data);
sendData = textData;
Text = new SendTextModel(textData);
break;

View File

@@ -1,11 +1,11 @@
using System;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Api.Models.Response
{
@@ -36,12 +36,12 @@ namespace Bit.Api.Models.Response
switch (send.Type)
{
case SendType.File:
var fileData = JsonConvert.DeserializeObject<SendFileData>(send.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send.Data);
sendData = fileData;
File = new SendFileModel(fileData);
break;
case SendType.Text:
var textData = JsonConvert.DeserializeObject<SendTextData>(send.Data);
var textData = JsonSerializer.Deserialize<SendTextData>(send.Data);
sendData = textData;
Text = new SendTextModel(textData);
break;

View File

@@ -1,4 +1,5 @@
using Bit.Core.Models.Data;
using System.Text.Json.Serialization;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models
@@ -11,7 +12,7 @@ namespace Bit.Api.Models
{
Id = data.Id;
FileName = data.FileName;
Size = data.SizeString;
Size = data.Size;
SizeName = CoreHelpers.ReadableBytesSize(data.Size);
}
@@ -19,7 +20,8 @@ namespace Bit.Api.Models
[EncryptedString]
[EncryptedStringLength(1000)]
public string FileName { get; set; }
public string Size { get; set; }
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public long? Size { get; set; }
public string SizeName { get; set; }
}
}

View File

@@ -124,7 +124,7 @@ namespace Bit.Api
services.AddCoreLocalizationServices();
#if OSS
services.AddOosServices();
services.AddOosServices();
#else
services.AddCommCoreServices();
#endif

View File

@@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace Bit.Api.Utilities
{
@@ -21,15 +21,8 @@ namespace Bit.Api.Utilities
{
try
{
using (var stream = file.OpenReadStream())
using (var reader = new StreamReader(stream))
{
var s = await reader.ReadToEndAsync();
if (!string.IsNullOrWhiteSpace(s))
{
obj = JsonConvert.DeserializeObject<T>(s);
}
}
using var stream = file.OpenReadStream();
obj = await JsonSerializer.DeserializeAsync<T>(stream, JsonHelpers.IgnoreCase);
}
catch { }
}

View File

@@ -6,7 +6,7 @@ using InternalApi = Bit.Core.Models.Api;
namespace Bit.Api.Utilities
{
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
public class ModelStateValidationFilterAttribute : SharedWeb.Utilities.ModelStateValidationFilterAttribute
{
private readonly bool _publicApi;
@@ -15,24 +15,15 @@ namespace Bit.Api.Utilities
_publicApi = publicApi;
}
public override void OnActionExecuting(ActionExecutingContext context)
protected override void OnModelStateInvalid(ActionExecutingContext context)
{
var model = context.ActionArguments.FirstOrDefault(a => a.Key == "model");
if (model.Key == "model" && model.Value == null)
if (_publicApi)
{
context.ModelState.AddModelError(string.Empty, "Body is empty.");
context.Result = new BadRequestObjectResult(new ErrorResponseModel(context.ModelState));
}
if (!context.ModelState.IsValid)
else
{
if (_publicApi)
{
context.Result = new BadRequestObjectResult(new ErrorResponseModel(context.ModelState));
}
else
{
context.Result = new BadRequestObjectResult(new InternalApi.ErrorResponseModel(context.ModelState));
}
context.Result = new BadRequestObjectResult(new InternalApi.ErrorResponseModel(context.ModelState));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Api.Models.Request;
using Microsoft.AspNetCore.Http;
@@ -7,7 +8,6 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
namespace Bit.Api.Utilities
{
@@ -78,13 +78,6 @@ namespace Bit.Api.Utilities
{
if (ContentDispositionHeaderValue.TryParse(firstSection.ContentDisposition, out _))
{
// Request model json, then data
string requestModelJson = null;
using (var sr = new StreamReader(firstSection.Body))
{
requestModelJson = await sr.ReadToEndAsync();
}
var secondSection = await reader.ReadNextSectionAsync();
if (secondSection != null)
{
@@ -94,7 +87,7 @@ namespace Bit.Api.Utilities
var fileName = HeaderUtilities.RemoveQuotes(secondContent.FileName).ToString();
using (secondSection.Body)
{
var model = JsonConvert.DeserializeObject<SendRequestModel>(requestModelJson);
var model = await JsonSerializer.DeserializeAsync<SendRequestModel>(firstSection.Body);
await callback(secondSection.Body, fileName, model);
}
}

3420
src/Api/packages.lock.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Bit.Billing.Controllers
{
@@ -53,7 +53,7 @@ namespace Bit.Billing.Controllers
try
{
var json = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(body), Formatting.Indented);
var json = JsonSerializer.Serialize(JsonSerializer.Deserialize<JsonDocument>(body), JsonHelpers.Indented);
_logger.LogInformation(Constants.BypassFiltersEventId, "Apple IAP Notification:\n\n{0}", json);
return new OkResult();
}

View File

@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Repositories;
using Bit.Core.Settings;
@@ -13,7 +14,6 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Bit.Billing.Controllers
{
@@ -62,23 +62,18 @@ namespace Bit.Billing.Controllers
return new BadRequestResult();
}
string body = null;
using (var reader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8))
{
body = await reader.ReadToEndAsync();
}
if (string.IsNullOrWhiteSpace(body))
using var body = await JsonSerializer.DeserializeAsync<JsonDocument>(HttpContext.Request.Body);
var root = body.RootElement;
if (root.ValueKind != JsonValueKind.Object)
{
return new BadRequestResult();
}
try
{
dynamic data = JsonConvert.DeserializeObject(body);
string ticketId = data.ticket_id;
string ticketContactEmail = data.ticket_contact_email;
string ticketTags = data.ticket_tags;
var ticketId = root.GetProperty("ticket_id").GetString();
var ticketContactEmail = root.GetProperty("ticket_contact_email").GetString();
var ticketTags = root.GetProperty("ticket_tags").GetString();
if (string.IsNullOrWhiteSpace(ticketId) || string.IsNullOrWhiteSpace(ticketContactEmail))
{
return new BadRequestResult();
@@ -120,9 +115,11 @@ namespace Bit.Billing.Controllers
updateBody.Add("tags", tagsToUpdate);
}
var updateRequest = new HttpRequestMessage(HttpMethod.Put,
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", ticketId));
updateRequest.Content = new StringContent(JsonConvert.SerializeObject(updateBody),
Encoding.UTF8, "application/json");
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}", ticketId))
{
Content = JsonContent.Create(updateBody),
};
await CallFreshdeskApiAsync(updateRequest);
@@ -132,9 +129,10 @@ namespace Bit.Billing.Controllers
{ "private", true }
};
var noteRequest = new HttpRequestMessage(HttpMethod.Post,
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId));
noteRequest.Content = new StringContent(JsonConvert.SerializeObject(noteBody),
Encoding.UTF8, "application/json");
string.Format("https://bitwarden.freshdesk.com/api/v2/tickets/{0}/notes", ticketId))
{
Content = JsonContent.Create(noteBody),
};
await CallFreshdeskApiAsync(noteRequest);
}

View File

@@ -1,105 +0,0 @@
using System;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Bit.Billing.Models
{
public class AppleReceiptNotification
{
[JsonProperty("notification_type")]
public string NotificationType { get; set; }
[JsonProperty("environment")]
public string Environment { get; set; }
[JsonProperty("auto_renew_status")]
public string AutoRenewStatus { get; set; }
[JsonProperty("auto_renew_product_id")]
public string AutoRenewProductId { get; set; }
[JsonProperty("auto_renew_status_change_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime? AutoRenewStatusChangeDate { get; set; }
[JsonProperty("latest_receipt")]
public string LatestReceipt { get; set; }
[JsonProperty("latest_receipt_info")]
public AppleReceiptNotificationInfo LatestReceiptInfo { get; set; }
[JsonProperty("latest_expired_receipt")]
public string LatestExpiredReceipt { get; set; }
[JsonProperty("latest_expired_receipt_info")]
public AppleReceiptNotificationInfo LatestExpiredReceiptInfo { get; set; }
public string GetOriginalTransactionId()
{
if (LatestReceiptInfo != null)
{
return LatestReceiptInfo.OriginalTransactionId;
}
return LatestExpiredReceiptInfo?.OriginalTransactionId;
}
public string GetTransactionId()
{
if (LatestReceiptInfo != null)
{
return LatestReceiptInfo.TransactionId;
}
return LatestExpiredReceiptInfo?.TransactionId;
}
public DateTime? GetExpiresDate()
{
if (LatestReceiptInfo != null)
{
return LatestReceiptInfo.ExpiresDate;
}
return LatestExpiredReceiptInfo?.ExpiresDate;
}
public string GetReceiptData()
{
return string.IsNullOrWhiteSpace(LatestReceipt) ? LatestExpiredReceipt : LatestReceipt;
}
public class AppleReceiptNotificationInfo
{
[JsonProperty("bid")]
public string Bid { get; set; }
public string ProductId { get; set; }
[JsonProperty("original_purchase_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime? OriginalPurchaseDate { get; set; }
[JsonProperty("expires_date")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime? ExpiresDate { get; set; }
[JsonProperty("purchase_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime? PurchaseDate { get; set; }
[JsonProperty("subscription_group_identifier")]
public string SubscriptionGroupIdentifier { get; set; }
[JsonProperty("unique_identifier")]
public string UniqueIdentifier { get; set; }
[JsonProperty("original_transaction_id")]
public string OriginalTransactionId { get; set; }
[JsonProperty("transaction_id")]
public string TransactionId { get; set; }
[JsonProperty("quantity")]
public string Quantity { get; set; }
[JsonProperty("web_order_line_item_id")]
public string WebOrderLineItemId { get; set; }
[JsonProperty("item_id")]
public string ItemId { get; set; }
}
public class MsEpochConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(CoreHelpers.ToEpocMilliseconds((DateTime)value).ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return CoreHelpers.FromEpocMilliseconds(long.Parse(reader.Value.ToString()));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
<PackageReference Include="Azure.Storage.Blobs" Version="12.10.0" />
<PackageReference Include="Azure.Storage.Queues" Version="12.3.2" />
<PackageReference Include="BitPay.Light" Version="1.0.1907" />
<PackageReference Include="Fido2.AspNet" Version="1.1.0" />
<PackageReference Include="Fido2.AspNet" Version="3.0.0-beta2" />
<PackageReference Include="Handlebars.Net" Version="1.10.1" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="MailKit" Version="2.8.0" />
@@ -33,7 +33,6 @@
<PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.7" />
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="3.3.0" />
<PackageReference Include="Microsoft.Azure.ServiceBus" Version="5.1.3" />
<PackageReference Include="Microsoft.Azure.Storage.Blob" Version="11.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="5.0.9" />
@@ -50,7 +49,6 @@
<PackageReference Include="AspNetCoreRateLimit" Version="2.1.0" />
<PackageReference Include="Braintree" Version="4.18.0" />
<PackageReference Include="Stripe.net" Version="37.26.0" />
<PackageReference Include="U2F.Core" Version="1.0.4" />
<PackageReference Include="Otp.NET" Version="1.2.2" />
<PackageReference Include="YubicoDotNetClient" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Entities
{
@@ -42,7 +42,7 @@ namespace Bit.Core.Entities
try
{
_attachmentData = JsonConvert.DeserializeObject<Dictionary<string, CipherAttachment.MetaData>>(Attachments);
_attachmentData = JsonSerializer.Deserialize<Dictionary<string, CipherAttachment.MetaData>>(Attachments);
foreach (var kvp in _attachmentData)
{
kvp.Value.AttachmentId = kvp.Key;
@@ -65,7 +65,7 @@ namespace Bit.Core.Entities
}
_attachmentData = data;
Attachments = JsonConvert.SerializeObject(_attachmentData);
Attachments = JsonSerializer.Serialize(_attachmentData);
}
public void AddAttachment(string id, CipherAttachment.MetaData data)

View File

@@ -2,10 +2,10 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Entities
{
@@ -142,13 +142,13 @@ namespace Bit.Core.Entities
if (_twoFactorProviders == null)
{
_twoFactorProviders =
JsonConvert.DeserializeObject<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
TwoFactorProviders);
}
return _twoFactorProviders;
}
catch (JsonSerializationException)
catch (JsonException)
{
return null;
}
@@ -163,10 +163,7 @@ namespace Bit.Core.Entities
return;
}
TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings
{
ContractResolver = new EnumKeyResolver<byte>()
});
TwoFactorProviders = JsonHelpers.LegacySerialize(providers, JsonHelpers.LegacyEnumKeyResolver);
_twoFactorProviders = providers;
}

View File

@@ -1,26 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Entities
{
public class U2f : ITableObject<int>
{
public int Id { get; set; }
public Guid UserId { get; set; }
[MaxLength(200)]
public string KeyHandle { get; set; }
[MaxLength(200)]
public string Challenge { get; set; }
[MaxLength(50)]
public string AppId { get; set; }
[MaxLength(20)]
public string Version { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public void SetNewId()
{
// int will be auto-populated
Id = 0;
}
}
}

View File

@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
namespace Bit.Core.Entities
{
@@ -108,13 +108,13 @@ namespace Bit.Core.Entities
if (_twoFactorProviders == null)
{
_twoFactorProviders =
JsonConvert.DeserializeObject<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
TwoFactorProviders);
}
return _twoFactorProviders;
}
catch (JsonSerializationException)
catch (JsonException)
{
return null;
}
@@ -132,10 +132,8 @@ namespace Bit.Core.Entities
public void SetTwoFactorProviders(Dictionary<TwoFactorProviderType, TwoFactorProvider> providers)
{
TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings
{
ContractResolver = new EnumKeyResolver<byte>()
});
// When replacing with system.text remember to remove the extra serialization in WebAuthnTokenProvider.
TwoFactorProviders = JsonHelpers.LegacySerialize(providers, JsonHelpers.LegacyEnumKeyResolver);
_twoFactorProviders = providers;
}

View File

@@ -0,0 +1,8 @@
namespace Bit.Core.Enums
{
public enum LicenseType : byte
{
User = 0,
Organization = 1,
}
}

View File

@@ -6,7 +6,7 @@
Email = 1,
Duo = 2,
YubiKey = 3,
U2f = 4,
U2f = 4, // Deprecated
Remember = 5,
OrganizationDuo = 6,
WebAuthn = 7,

View File

@@ -44,8 +44,8 @@ namespace Bit.Core.Identity
return Task.FromResult<string>(null);
}
var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"],
(string)provider.MetaData["SKey"], _globalSettings.Duo.AKey, user.Email);
var signatureRequest = DuoWeb.SignRequest(provider.MetaData["IKey"].ToString(),
provider.MetaData["SKey"].ToString(), _globalSettings.Duo.AKey, user.Email);
return Task.FromResult(signatureRequest);
}
@@ -62,8 +62,8 @@ namespace Bit.Core.Identity
return Task.FromResult(false);
}
var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"],
(string)provider.MetaData["SKey"], _globalSettings.Duo.AKey, token);
var response = DuoWeb.VerifyResponse(provider.MetaData["IKey"].ToString(),
provider.MetaData["SKey"].ToString(), _globalSettings.Duo.AKey, token);
return Task.FromResult(response == user.Email);
}

View File

@@ -1,221 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using U2F.Core.Exceptions;
using U2F.Core.Models;
using U2F.Core.Utils;
using U2fLib = U2F.Core.Crypto.U2F;
namespace Bit.Core.Identity
{
public class U2fTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
private readonly IU2fRepository _u2fRepository;
private readonly GlobalSettings _globalSettings;
public U2fTokenProvider(
IServiceProvider serviceProvider,
IU2fRepository u2fRepository,
GlobalSettings globalSettings)
{
_serviceProvider = serviceProvider;
_u2fRepository = u2fRepository;
_globalSettings = globalSettings;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
if (!HasProperMetaData(provider))
{
return false;
}
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.U2f, user);
}
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return null;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
var keys = LoadKeys(provider);
if (keys.Count == 0)
{
return null;
}
await _u2fRepository.DeleteManyByUserIdAsync(user.Id);
try
{
var challengeBytes = U2fLib.Crypto.GenerateChallenge();
var appId = Utilities.CoreHelpers.U2fAppIdUrl(_globalSettings);
var oldChallenges = new List<object>();
var challengeKeys = new List<object>();
foreach (var key in keys)
{
var registration = new DeviceRegistration(key.Item2.KeyHandleBytes, key.Item2.PublicKeyBytes,
key.Item2.CertificateBytes, key.Item2.Counter);
var auth = U2fLib.StartAuthentication(appId, registration, challengeBytes);
// TODO: Maybe move this to a bulk create?
await _u2fRepository.CreateAsync(new U2f
{
AppId = auth.AppId,
Challenge = auth.Challenge,
KeyHandle = auth.KeyHandle,
Version = auth.Version,
UserId = user.Id,
CreationDate = DateTime.UtcNow
});
challengeKeys.Add(new
{
keyHandle = auth.KeyHandle,
version = auth.Version
});
// TODO: Old challenges array is here for backwards compat. Remove in the future.
oldChallenges.Add(new
{
appId = auth.AppId,
challenge = auth.Challenge,
keyHandle = auth.KeyHandle,
version = auth.Version
});
}
var oldToken = JsonConvert.SerializeObject(oldChallenges);
var token = JsonConvert.SerializeObject(new
{
appId = appId,
challenge = challengeBytes.ByteArrayToBase64String(),
keys = challengeKeys
});
return $"{token}|{oldToken}";
}
catch (U2fException)
{
return null;
}
}
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
var keys = LoadKeys(provider);
if (keys.Count == 0)
{
return false;
}
var authenticateResponse = BaseModel.FromJson<AuthenticateResponse>(token);
var key = keys.FirstOrDefault(f => f.Item2.KeyHandle == authenticateResponse.KeyHandle);
if (key == null)
{
return false;
}
var challenges = await _u2fRepository.GetManyByUserIdAsync(user.Id);
if (challenges.Count == 0)
{
return false;
}
// User will have a authentication request for each device they have registered so get the one that matches
// the device key handle
var challenge = challenges.FirstOrDefault(c => c.KeyHandle == authenticateResponse.KeyHandle);
if (challenge == null)
{
return false;
}
var success = true;
var registration = new DeviceRegistration(key.Item2.KeyHandleBytes, key.Item2.PublicKeyBytes,
key.Item2.CertificateBytes, key.Item2.Counter);
try
{
var auth = new StartedAuthentication(challenge.Challenge, challenge.AppId, challenge.KeyHandle);
U2fLib.FinishAuthentication(auth, authenticateResponse, registration);
}
catch (U2fException)
{
success = false;
}
// Update database
await _u2fRepository.DeleteManyByUserIdAsync(user.Id);
key.Item2.Counter = registration.Counter;
if (key.Item2.Counter > 0)
{
key.Item2.Compromised = registration.IsCompromised;
}
var providers = user.GetTwoFactorProviders();
providers[TwoFactorProviderType.U2f].MetaData[key.Item1] = key.Item2;
user.SetTwoFactorProviders(providers);
await manager.UpdateAsync(user);
return success;
}
private bool HasProperMetaData(TwoFactorProvider provider)
{
return (provider?.MetaData?.Count ?? 0) > 0;
}
private List<Tuple<string, TwoFactorProvider.U2fMetaData>> LoadKeys(TwoFactorProvider provider)
{
var keys = new List<Tuple<string, TwoFactorProvider.U2fMetaData>>();
if (!HasProperMetaData(provider))
{
return keys;
}
// Support up to 5 keys
for (var i = 1; i <= 5; i++)
{
var keyName = $"Key{i}";
if (provider.MetaData.ContainsKey(keyName))
{
var key = new TwoFactorProvider.U2fMetaData((dynamic)provider.MetaData[keyName]);
if (!key?.Compromised ?? false)
{
keys.Add(new Tuple<string, TwoFactorProvider.U2fMetaData>(keyName, key));
}
}
}
return keys;
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -12,7 +13,6 @@ using Fido2NetLib;
using Fido2NetLib.Objects;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
namespace Bit.Core.Identity
{
@@ -65,14 +65,14 @@ namespace Bit.Core.Identity
var exts = new AuthenticationExtensionsClientInputs()
{
UserVerificationIndex = true,
UserVerificationMethod = true,
AppID = CoreHelpers.U2fAppIdUrl(_globalSettings),
};
var options = _fido2.GetAssertionOptions(existingCredentials, UserVerificationRequirement.Discouraged, exts);
provider.MetaData["login"] = options;
// TODO: Remove this when newtonsoft legacy converters are gone
provider.MetaData["login"] = JsonSerializer.Serialize(options);
var providers = user.GetTwoFactorProviders();
providers[TwoFactorProviderType.WebAuthn] = provider;
@@ -98,7 +98,8 @@ namespace Bit.Core.Identity
return false;
}
var clientResponse = JsonConvert.DeserializeObject<AuthenticatorAssertionRawResponse>(token);
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var jsonOptions = provider.MetaData["login"].ToString();
var options = AssertionOptions.FromJson(jsonOptions);

View File

@@ -375,7 +375,6 @@ namespace Bit.Core.IdentityServer
case TwoFactorProviderType.Email:
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
case TwoFactorProviderType.Remember:
if (type != TwoFactorProviderType.Remember &&
@@ -403,7 +402,6 @@ namespace Bit.Core.IdentityServer
switch (type)
{
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
case TwoFactorProviderType.Email:
case TwoFactorProviderType.YubiKey:
@@ -422,16 +420,6 @@ namespace Bit.Core.IdentityServer
["Signature"] = token
};
}
else if (type == TwoFactorProviderType.U2f)
{
// TODO: Remove "Challenges" in a future update. Deprecated.
var tokens = token?.Split('|');
return new Dictionary<string, object>
{
["Challenge"] = tokens != null && tokens.Length > 0 ? tokens[0] : null,
["Challenges"] = tokens != null && tokens.Length > 1 ? tokens[1] : null
};
}
else if (type == TwoFactorProviderType.WebAuthn)
{
if (token == null)

View File

@@ -15,7 +15,7 @@
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
If you do not wish to join this organization, you can safely ignore this email.
{{#if OrganizationCanSponsor}}
{{#jsonIf OrganizationCanSponsor}}
<p style="margin-top:10px">
<b
style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
@@ -24,7 +24,7 @@
Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more at the
following link: https://bitwarden.com/help/article/families-for-enterprise/
</p>
{{/if}}
{{/jsonIf}}
</td>
</tr>
</table>

View File

@@ -6,7 +6,7 @@ You have been invited to join the {{OrganizationName}} organization. To accept t
This link expires on {{ExpirationDate}}.
If you do not wish to join this organization, you can safely ignore this email.
{{#if OrganizationCanSponsor}}
{{#jsonIf OrganizationCanSponsor}}
Did you know? Members of {{OrganizationName}} receive a complimentary Families subscription. Learn more here: https://bitwarden.com/help/article/families-for-enterprise/
{{/if}}
{{/jsonIf}}
{{/BasicTextLayout}}

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Models.Api.Request.Accounts
{
@@ -43,7 +43,7 @@ namespace Bit.Core.Models.Api.Request.Accounts
if (ReferenceData != null)
{
user.ReferenceData = JsonConvert.SerializeObject(ReferenceData);
user.ReferenceData = JsonSerializer.Serialize(ReferenceData);
}
if (Key != null)

View File

@@ -1,27 +1,28 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Bit.Billing.Models
{
public class AppleReceiptStatus
{
[JsonProperty("status")]
[JsonPropertyName("status")]
public int? Status { get; set; }
[JsonProperty("environment")]
[JsonPropertyName("environment")]
public string Environment { get; set; }
[JsonProperty("latest_receipt")]
[JsonPropertyName("latest_receipt")]
public string LatestReceipt { get; set; }
[JsonProperty("receipt")]
[JsonPropertyName("receipt")]
public AppleReceipt Receipt { get; set; }
[JsonProperty("latest_receipt_info")]
[JsonPropertyName("latest_receipt_info")]
public List<AppleTransaction> LatestReceiptInfo { get; set; }
[JsonProperty("pending_renewal_info")]
[JsonPropertyName("pending_renewal_info")]
public List<AppleRenewalInfo> PendingRenewalInfo { get; set; }
public string GetOriginalTransactionId()
@@ -81,72 +82,59 @@ namespace Bit.Billing.Models
public class AppleReceipt
{
[JsonProperty("receipt_type")]
[JsonPropertyName("receipt_type")]
public string ReceiptType { get; set; }
[JsonProperty("bundle_id")]
[JsonPropertyName("bundle_id")]
public string BundleId { get; set; }
[JsonProperty("receipt_creation_date_ms")]
[JsonPropertyName("receipt_creation_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime ReceiptCreationDate { get; set; }
[JsonProperty("in_app")]
[JsonPropertyName("in_app")]
public List<AppleTransaction> InApp { get; set; }
}
public class AppleRenewalInfo
{
[JsonProperty("expiration_intent")]
[JsonPropertyName("expiration_intent")]
public string ExpirationIntent { get; set; }
[JsonProperty("auto_renew_product_id")]
[JsonPropertyName("auto_renew_product_id")]
public string AutoRenewProductId { get; set; }
[JsonProperty("original_transaction_id")]
[JsonPropertyName("original_transaction_id")]
public string OriginalTransactionId { get; set; }
[JsonProperty("is_in_billing_retry_period")]
[JsonPropertyName("is_in_billing_retry_period")]
public string IsInBillingRetryPeriod { get; set; }
[JsonProperty("product_id")]
[JsonPropertyName("product_id")]
public string ProductId { get; set; }
[JsonProperty("auto_renew_status")]
[JsonPropertyName("auto_renew_status")]
public string AutoRenewStatus { get; set; }
}
public class AppleTransaction
{
[JsonProperty("quantity")]
[JsonPropertyName("quantity")]
public string Quantity { get; set; }
[JsonProperty("product_id")]
[JsonPropertyName("product_id")]
public string ProductId { get; set; }
[JsonProperty("transaction_id")]
[JsonPropertyName("transaction_id")]
public string TransactionId { get; set; }
[JsonProperty("original_transaction_id")]
[JsonPropertyName("original_transaction_id")]
public string OriginalTransactionId { get; set; }
[JsonProperty("purchase_date_ms")]
[JsonPropertyName("purchase_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime PurchaseDate { get; set; }
[JsonProperty("original_purchase_date_ms")]
[JsonPropertyName("original_purchase_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime OriginalPurchaseDate { get; set; }
[JsonProperty("expires_date_ms")]
[JsonPropertyName("expires_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime ExpiresDate { get; set; }
[JsonProperty("cancellation_date_ms")]
[JsonPropertyName("cancellation_date_ms")]
[JsonConverter(typeof(MsEpochConverter))]
public DateTime? CancellationDate { get; set; }
[JsonProperty("web_order_line_item_id")]
[JsonPropertyName("web_order_line_item_id")]
public string WebOrderLineItemId { get; set; }
[JsonProperty("cancellation_reason")]
[JsonPropertyName("cancellation_reason")]
public string CancellationReason { get; set; }
}
public class MsEpochConverter : DateTimeConverterBase
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(CoreHelpers.ToEpocMilliseconds((DateTime)value).ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return CoreHelpers.FromEpocMilliseconds(long.Parse(reader.Value.ToString()));
}
}
}
}

View File

@@ -4,11 +4,11 @@ using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Settings;
using Newtonsoft.Json;
namespace Bit.Core.Models.Business
{
@@ -21,6 +21,7 @@ namespace Bit.Core.Models.Business
ILicensingService licenseService, int? version = null)
{
Version = version.GetValueOrDefault(CURRENT_LICENSE_FILE_VERSION); // TODO: Remember to change the constant
LicenseType = Enums.LicenseType.Organization;
LicenseKey = org.LicenseKey;
InstallationId = installationId;
Id = org.Id;
@@ -121,6 +122,7 @@ namespace Bit.Core.Models.Business
public DateTime? Refresh { get; set; }
public DateTime? Expires { get; set; }
public bool Trial { get; set; }
public LicenseType? LicenseType { get; set; }
public string Hash { get; set; }
public string Signature { get; set; }
[JsonIgnore]

View File

@@ -1,13 +1,10 @@
using System;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
namespace Bit.Core.Models.Business
{
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class ReferenceEvent
{
public ReferenceEvent() { }
@@ -23,10 +20,10 @@ namespace Bit.Core.Models.Business
}
}
[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter))]
public ReferenceEventType Type { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter))]
public ReferenceEventSource Source { get; set; }
public Guid Id { get; set; }
@@ -52,7 +49,7 @@ namespace Bit.Core.Models.Business
public short? Storage { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[JsonConverter(typeof(JsonStringEnumConverter))]
public SendType? SendType { get; set; }
public int? MaxAccessCount { get; set; }

View File

@@ -1,16 +0,0 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Bit.Core.Models.Business
{
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class ReferenceEventData
{
public string Id { get; set; }
public string Layout { get; set; }
public string Flow { get; set; }
}
}

View File

@@ -4,9 +4,10 @@ using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json.Serialization;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Services;
using Newtonsoft.Json;
namespace Bit.Core.Models.Business
{
@@ -18,6 +19,7 @@ namespace Bit.Core.Models.Business
public UserLicense(User user, SubscriptionInfo subscriptionInfo, ILicensingService licenseService,
int? version = null)
{
LicenseType = Enums.LicenseType.User;
LicenseKey = user.LicenseKey;
Id = user.Id;
Name = user.Name;
@@ -39,6 +41,7 @@ namespace Bit.Core.Models.Business
public UserLicense(User user, ILicensingService licenseService, int? version = null)
{
LicenseType = Enums.LicenseType.User;
LicenseKey = user.LicenseKey;
Id = user.Id;
Name = user.Name;
@@ -66,6 +69,7 @@ namespace Bit.Core.Models.Business
public DateTime? Refresh { get; set; }
public DateTime? Expires { get; set; }
public bool Trial { get; set; }
public LicenseType? LicenseType { get; set; }
public string Hash { get; set; }
public string Signature { get; set; }
[JsonIgnore]

View File

@@ -1,5 +1,5 @@
using System;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Data
{
@@ -15,21 +15,14 @@ namespace Bit.Core.Models.Data
{
private long _size;
[JsonIgnore]
// We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
public long Size
{
get { return _size; }
set { _size = value; }
}
// We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers
[JsonProperty("Size")]
public string SizeString
{
get { return _size.ToString(); }
set { _size = Convert.ToInt64(value); }
}
public string FileName { get; set; }
public string Key { get; set; }

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Newtonsoft.Json;
namespace Bit.Core.Models.Data
{

View File

@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using Bit.Core.Enums;
using Newtonsoft.Json;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Data
{
@@ -37,13 +38,13 @@ namespace Bit.Core.Models.Data
if (_twoFactorProviders == null)
{
_twoFactorProviders =
JsonConvert.DeserializeObject<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
TwoFactorProviders);
}
return _twoFactorProviders;
}
catch (JsonSerializationException)
catch (Newtonsoft.Json.JsonException)
{
return null;
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Data
{
@@ -25,7 +25,6 @@ namespace Bit.Core.Models.Data
public bool ManageResetPassword { get; set; }
[JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public List<(bool Permission, string ClaimName)> ClaimsMap => new()
{
(AccessEventLogs, "accesseventlogs"),

View File

@@ -1,12 +1,10 @@
using System;
using Newtonsoft.Json;
using System.Text.Json.Serialization;
namespace Bit.Core.Models.Data
{
public class SendFileData : SendData
{
private long _size;
public SendFileData() { }
public SendFileData(string name, string notes, string fileName)
@@ -15,20 +13,9 @@ namespace Bit.Core.Models.Data
FileName = fileName;
}
[JsonIgnore]
public long Size
{
get { return _size; }
set { _size = value; }
}
// We serialize Size as a string since JSON (or Javascript) doesn't support full precision for long numbers
[JsonProperty("Size")]
public string SizeString
{
get { return _size.ToString(); }
set { _size = Convert.ToInt64(value); }
}
[JsonNumberHandling(JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString)]
public long Size { get; set; }
public string Id { get; set; }
public string FileName { get; set; }

View File

@@ -9,6 +9,6 @@ namespace Bit.Core.Models.Mail
IEnumerable<string> BccEmails { get; set; }
string Category { get; set; }
string TemplateName { get; set; }
dynamic Model { get; set; }
object Model { get; set; }
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Models.Mail
{
@@ -11,12 +11,13 @@ namespace Bit.Core.Models.Mail
public IEnumerable<string> BccEmails { get; set; }
public string Category { get; set; }
public string TemplateName { get; set; }
[JsonConverter(typeof(ExpandoObjectJsonConverter))]
public dynamic Model { get; set; }
[JsonConverter(typeof(HandlebarsObjectJsonConverter))]
public object Model { get; set; }
public MailQueueMessage() { }
public MailQueueMessage(MailMessage message, string templateName, dynamic model)
public MailQueueMessage(MailMessage message, string templateName, object model)
{
Subject = message.Subject;
ToEmails = message.ToEmails;

View File

@@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.Json;
using Bit.Core.Enums;
using Fido2NetLib.Objects;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using PeterO.Cbor;
using U2F.Core.Utils;
namespace Bit.Core.Models
{
@@ -15,79 +11,6 @@ namespace Bit.Core.Models
public bool Enabled { get; set; }
public Dictionary<string, object> MetaData { get; set; } = new Dictionary<string, object>();
public class U2fMetaData
{
public U2fMetaData() { }
public U2fMetaData(dynamic o)
{
Name = o.Name;
KeyHandle = o.KeyHandle;
PublicKey = o.PublicKey;
Certificate = o.Certificate;
Counter = o.Counter;
Compromised = o.Compromised;
}
public string Name { get; set; }
public string KeyHandle { get; set; }
[JsonIgnore]
public byte[] KeyHandleBytes =>
string.IsNullOrWhiteSpace(KeyHandle) ? null : Utils.Base64StringToByteArray(KeyHandle);
public string PublicKey { get; set; }
[JsonIgnore]
public byte[] PublicKeyBytes =>
string.IsNullOrWhiteSpace(PublicKey) ? null : Utils.Base64StringToByteArray(PublicKey);
public string Certificate { get; set; }
[JsonIgnore]
public byte[] CertificateBytes =>
string.IsNullOrWhiteSpace(Certificate) ? null : Utils.Base64StringToByteArray(Certificate);
public uint Counter { get; set; }
public bool Compromised { get; set; }
private static CBORObject CreatePublicKeyFromU2fRegistrationData(byte[] keyHandleData, byte[] publicKeyData)
{
var x = new byte[32];
var y = new byte[32];
Buffer.BlockCopy(publicKeyData, 1, x, 0, 32);
Buffer.BlockCopy(publicKeyData, 33, y, 0, 32);
var point = new ECPoint
{
X = x,
Y = y,
};
var coseKey = CBORObject.NewMap();
coseKey.Add(COSE.KeyCommonParameter.KeyType, COSE.KeyType.EC2);
coseKey.Add(COSE.KeyCommonParameter.Alg, -7);
coseKey.Add(COSE.KeyTypeParameter.Crv, COSE.EllipticCurve.P256);
coseKey.Add(COSE.KeyTypeParameter.X, point.X);
coseKey.Add(COSE.KeyTypeParameter.Y, point.Y);
return coseKey;
}
public WebAuthnData ToWebAuthnData()
{
return new WebAuthnData
{
Name = Name,
Descriptor = new PublicKeyCredentialDescriptor
{
Id = KeyHandleBytes,
Type = PublicKeyCredentialType.PublicKey
},
PublicKey = CreatePublicKeyFromU2fRegistrationData(KeyHandleBytes, PublicKeyBytes).EncodeToBytes(),
SignatureCounter = Counter,
Migrated = true,
};
}
}
public class WebAuthnData
{
public WebAuthnData() { }
@@ -101,8 +24,13 @@ namespace Bit.Core.Models
}
catch
{
// Handle newtonsoft parsing
Descriptor = JsonConvert.DeserializeObject<PublicKeyCredentialDescriptor>(o.Descriptor.ToString());
// Fallback for older newtonsoft serialized tokens.
if (o.Descriptor.Type == 0)
{
o.Descriptor.Type = "public-key";
}
Descriptor = JsonSerializer.Deserialize<PublicKeyCredentialDescriptor>(o.Descriptor.ToString(),
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
PublicKey = o.PublicKey;
UserHandle = o.UserHandle;
@@ -130,7 +58,6 @@ namespace Bit.Core.Models
{
case TwoFactorProviderType.Duo:
case TwoFactorProviderType.YubiKey:
case TwoFactorProviderType.U2f:
case TwoFactorProviderType.WebAuthn:
return true;
default:

View File

@@ -1,9 +1,10 @@
using System.Threading.Tasks;
using Bit.Core.Entities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces
{
public interface IValidateRedemptionTokenCommand
{
Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail);
Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail);
}
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Models.Business.Tokenables;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
@@ -18,20 +19,20 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte
_tokenFactory = tokenFactory;
}
public async Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail)
public async Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail)
{
if (!_tokenFactory.TryUnprotect(encryptedToken, out var tokenable))
{
return false;
return (false, null);
}
var sponsorship = await _organizationSponsorshipRepository.GetByIdAsync(tokenable.Id);
if (!tokenable.IsValid(sponsorship, sponsoredUserEmail))
{
return false;
return (false, sponsorship);
}
return true;
return (true, sponsorship);
}
}
}

View File

@@ -10,6 +10,5 @@ namespace Bit.Core.Repositories
{
Task<OrganizationSponsorship> GetBySponsoringOrganizationUserIdAsync(Guid sponsoringOrganizationUserId);
Task<OrganizationSponsorship> GetBySponsoredOrganizationIdAsync(Guid sponsoredOrganizationId);
Task<OrganizationSponsorship> GetByOfferedToEmailAsync(string email);
}
}

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Entities;
namespace Bit.Core.Repositories
{
public interface IU2fRepository : IRepository<U2f, int>
{
Task<ICollection<U2f>> GetManyByUserIdAsync(Guid userId);
Task DeleteManyByUserIdAsync(Guid userId);
}
}

View File

@@ -7,7 +7,7 @@ namespace Bit.Core.Services
{
public interface IOrganizationSponsorshipService
{
Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string currentUserEmail);
Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string currentUserEmail);
Task OfferSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string sponsoringUserEmail);
Task ResendSponsorshipOfferAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Bit.Billing.Models;
using Bit.Core.Repositories;
@@ -9,8 +11,6 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services
{
@@ -97,14 +97,16 @@ namespace Bit.Core.Services
}
var url = string.Format("https://{0}.itunes.apple.com/verifyReceipt", prod ? "buy" : "sandbox");
var json = new JObject(new JProperty("receipt-data", receiptData),
new JProperty("password", _globalSettings.AppleIap.Password)).ToString();
var response = await _httpClient.PostAsync(url, new StringContent(json));
var response = await _httpClient.PostAsJsonAsync(url, new AppleVerifyReceiptRequestModel
{
ReceiptData = receiptData,
Password = _globalSettings.AppleIap.Password
});
if (response.IsSuccessStatusCode)
{
var responseJson = await response.Content.ReadAsStringAsync();
var receiptStatus = JsonConvert.DeserializeObject<AppleReceiptStatus>(responseJson);
var receiptStatus = await response.Content.ReadFromJsonAsync<AppleReceiptStatus>();
if (receiptStatus.Status == 21007)
{
return await GetReceiptStatusAsync(receiptData, false, attempt + 1, receiptStatus);
@@ -124,4 +126,12 @@ namespace Bit.Core.Services
return null;
}
}
public class AppleVerifyReceiptRequestModel
{
[JsonPropertyName("receipt-data")]
public string ReceiptData { get; set; }
[JsonPropertyName("password")]
public string Password { get; set; }
}
}

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Azure.Storage.Queues;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
using Newtonsoft.Json;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
@@ -13,7 +13,9 @@ namespace Bit.Core.Services
{
public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
new QueueClient(globalSettings.Events.ConnectionString, "event"),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
JsonHelpers.IgnoreWritingNull)
{ }
public Task CreateAsync(IEvent e) => CreateManyAsync(new[] { e });
}
}

View File

@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Azure.Storage.Queues;
using Bit.Core.Models.Mail;
using Bit.Core.Settings;
using Newtonsoft.Json;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
@@ -13,11 +13,11 @@ namespace Bit.Core.Services
{
public AzureQueueMailService(GlobalSettings globalSettings) : base(
new QueueClient(globalSettings.Mail.ConnectionString, "mail"),
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })
JsonHelpers.IgnoreWritingNull)
{ }
public Task EnqueueAsync(IMailQueueMessage message, Func<IMailQueueMessage, Task> fallback) =>
CreateAsync(message);
CreateManyAsync(new[] { message });
public Task EnqueueManyAsync(IEnumerable<IMailQueueMessage> messages, Func<IMailQueueMessage, Task> fallback) =>
CreateManyAsync(messages);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Storage.Queues;
using Bit.Core.Context;
@@ -7,8 +8,8 @@ using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -18,11 +19,6 @@ namespace Bit.Core.Services
private readonly GlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
public AzureQueuePushNotificationService(
GlobalSettings globalSettings,
IHttpContextAccessor httpContextAccessor)
@@ -170,8 +166,8 @@ namespace Bit.Core.Services
private async Task SendMessageAsync<T>(PushType type, T payload, bool excludeCurrentContext)
{
var contextId = GetContextIdentifier(excludeCurrentContext);
var message = JsonConvert.SerializeObject(new PushNotificationData<T>(type, payload, contextId),
_jsonSettings);
var message = JsonSerializer.Serialize(new PushNotificationData<T>(type, payload, contextId),
JsonHelpers.IgnoreWritingNull);
await _queueClient.SendMessageAsync(message);
}

View File

@@ -1,10 +1,11 @@
using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Storage.Queues;
using Bit.Core.Models.Business;
using Bit.Core.Settings;
using Newtonsoft.Json;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
@@ -14,10 +15,6 @@ namespace Bit.Core.Services
private readonly QueueClient _queueClient;
private readonly GlobalSettings _globalSettings;
private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
};
public AzureQueueReferenceEventService(
GlobalSettings globalSettings)
@@ -40,7 +37,7 @@ namespace Bit.Core.Services
}
try
{
var message = JsonConvert.SerializeObject(referenceEvent, _jsonSerializerSettings);
var message = JsonSerializer.Serialize(referenceEvent, JsonHelpers.IgnoreWritingNullAndCamelCase);
// Messages need to be base64 encoded
var encodedMessage = Convert.ToBase64String(Encoding.UTF8.GetBytes(message));
await _queueClient.SendMessageAsync(encodedMessage);

View File

@@ -1,29 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Storage.Queues;
using Bit.Core.Utilities;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
public abstract class AzureQueueService<T>
{
protected QueueClient _queueClient;
protected JsonSerializerSettings _jsonSettings;
protected JsonSerializerOptions _jsonOptions;
protected AzureQueueService(QueueClient queueClient, JsonSerializerSettings jsonSettings)
protected AzureQueueService(QueueClient queueClient, JsonSerializerOptions jsonOptions)
{
_queueClient = queueClient;
_jsonSettings = jsonSettings;
}
public async Task CreateAsync(T message)
{
var json = JsonConvert.SerializeObject(message, _jsonSettings);
var base64 = CoreHelpers.Base64EncodeString(json);
await _queueClient.SendMessageAsync(base64);
_jsonOptions = jsonOptions;
}
public async Task CreateManyAsync(IEnumerable<T> messages)
@@ -33,19 +26,13 @@ namespace Bit.Core.Services
return;
}
if (!messages.Skip(1).Any())
{
await CreateAsync(messages.First());
return;
}
foreach (var json in SerializeMany(messages, _jsonSettings))
foreach (var json in SerializeMany(messages, _jsonOptions))
{
await _queueClient.SendMessageAsync(json);
}
}
protected IEnumerable<string> SerializeMany(IEnumerable<T> messages, JsonSerializerSettings jsonSettings)
protected IEnumerable<string> SerializeMany(IEnumerable<T> messages, JsonSerializerOptions jsonOptions)
{
// Calculate Base-64 encoded text with padding
int getBase64Size(int byteCount) => ((4 * byteCount / 3) + 3) & ~3;
@@ -69,7 +56,7 @@ namespace Bit.Core.Services
}
var serializedMessages = messages.Select(message =>
JsonConvert.SerializeObject(message, jsonSettings));
JsonSerializer.Serialize(message, jsonOptions));
foreach (var message in serializedMessages)
{

View File

@@ -3,23 +3,23 @@ using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Utilities;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Bit.Core.Services
{
public abstract class BaseIdentityClientService
public abstract class BaseIdentityClientService : IDisposable
{
private readonly string _identityScope;
private readonly string _identityClientId;
private readonly string _identityClientSecret;
private readonly ILogger<BaseIdentityClientService> _logger;
private dynamic _decodedToken;
private JsonDocument _decodedToken;
private DateTime? _nextAuthAttempt = null;
public BaseIdentityClientService(
@@ -127,9 +127,9 @@ namespace Bit.Core.Services
return false;
}
var responseContent = await response.Content.ReadAsStringAsync();
dynamic tokenResponse = JsonConvert.DeserializeObject(responseContent);
AccessToken = (string)tokenResponse.access_token;
using var jsonDocument = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
AccessToken = jsonDocument.RootElement.GetProperty("access_token").GetString();
return true;
}
@@ -145,8 +145,7 @@ namespace Bit.Core.Services
{
if (requestObject != null)
{
var stringContent = JsonConvert.SerializeObject(requestObject);
Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
Content = JsonContent.Create(requestObject);
}
}
}
@@ -154,17 +153,16 @@ namespace Bit.Core.Services
protected bool TokenNeedsRefresh(int minutes = 5)
{
var decoded = DecodeToken();
var exp = decoded?["exp"];
if (exp == null)
if (!decoded.RootElement.TryGetProperty("exp", out var expProp))
{
throw new InvalidOperationException("No exp in token.");
}
var expiration = CoreHelpers.FromEpocSeconds(exp.Value<long>());
var expiration = CoreHelpers.FromEpocSeconds(expProp.GetInt64());
return DateTime.UtcNow.AddMinutes(-1 * minutes) > expiration;
}
protected JObject DecodeToken()
protected JsonDocument DecodeToken()
{
if (_decodedToken != null)
{
@@ -188,8 +186,13 @@ namespace Bit.Core.Services
throw new InvalidOperationException($"{nameof(AccessToken)} must have 3 parts");
}
_decodedToken = JObject.Parse(Encoding.UTF8.GetString(decodedBytes, 0, decodedBytes.Length));
_decodedToken = JsonDocument.Parse(decodedBytes);
return _decodedToken;
}
public void Dispose()
{
_decodedToken.Dispose();
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Enums;
@@ -12,7 +13,6 @@ using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Core.Models.Data;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -208,7 +208,7 @@ namespace Bit.Core.Services
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
AttachmentData = JsonConvert.SerializeObject(data)
AttachmentData = JsonSerializer.Serialize(data)
});
cipher.AddAttachment(attachmentId, data);
await _pushService.PushSyncCipherUpdateAsync(cipher, null);
@@ -241,7 +241,7 @@ namespace Bit.Core.Services
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
AttachmentData = JsonConvert.SerializeObject(data)
AttachmentData = JsonSerializer.Serialize(data)
};
await _cipherRepository.UpdateAttachmentAsync(attachment);
@@ -312,7 +312,7 @@ namespace Bit.Core.Services
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentId,
AttachmentData = JsonConvert.SerializeObject(attachments[attachmentId])
AttachmentData = JsonSerializer.Serialize(attachments[attachmentId])
};
await _cipherRepository.UpdateAttachmentAsync(updatedAttachment);
@@ -347,7 +347,7 @@ namespace Bit.Core.Services
UserId = cipher.UserId,
OrganizationId = cipher.OrganizationId,
AttachmentId = attachmentData.AttachmentId,
AttachmentData = JsonConvert.SerializeObject(attachmentData)
AttachmentData = JsonSerializer.Serialize(attachmentData)
};

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Entities;
@@ -8,7 +10,6 @@ using Bit.Core.Models.Business.Tokenables;
using Bit.Core.Settings;
using Bit.Core.Tokens;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -77,9 +78,9 @@ namespace Bit.Core.Services
return false;
}
var responseContent = await responseMessage.Content.ReadAsStringAsync();
dynamic jsonResponse = JsonConvert.DeserializeObject(responseContent);
return (bool)jsonResponse.success;
using var jsonDocument = await responseMessage.Content.ReadFromJsonAsync<JsonDocument>();
var root = jsonDocument.RootElement;
return root.GetProperty("success").GetBoolean();
}
public bool RequireCaptchaValidation(ICurrentContext currentContext) =>

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
@@ -565,6 +566,35 @@ namespace Bit.Core.Services
var clickTrackingText = (clickTrackingOff ? "clicktracking=off" : string.Empty);
writer.WriteSafeString($"<a href=\"{href}\" target=\"_blank\" {clickTrackingText}>{text}</a>");
});
Handlebars.RegisterHelper("jsonIf", (output, options, context, arguments) =>
{
// Special case for JsonElement
if (arguments[0] is JsonElement jsonElement
&& (jsonElement.ValueKind == JsonValueKind.True || jsonElement.ValueKind == JsonValueKind.False))
{
if (jsonElement.GetBoolean())
{
options.Template(output, context);
}
else
{
options.Inverse(output, context);
}
return;
}
// Fallback to normal
if (HandlebarsUtils.IsTruthy(arguments[0]))
{
options.Template(output, context);
}
else
{
options.Inverse(output, context);
}
});
}
public async Task SendEmergencyAccessInviteEmailAsync(EmergencyAccess emergencyAccess, string name, string token)

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Entities;
using Bit.Core.Models.Business;
@@ -13,7 +14,6 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -246,7 +246,7 @@ namespace Bit.Core.Services
}
var data = File.ReadAllText(filePath, Encoding.UTF8);
return JsonConvert.DeserializeObject<UserLicense>(data);
return JsonSerializer.Deserialize<UserLicense>(data);
}
private OrganizationLicense ReadOrganizationLicense(Organization organization)
@@ -258,7 +258,7 @@ namespace Bit.Core.Services
}
var data = File.ReadAllText(filePath, Encoding.UTF8);
return JsonConvert.DeserializeObject<OrganizationLicense>(data);
return JsonSerializer.Deserialize<OrganizationLicense>(data);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Bit.Core.Context;
@@ -11,7 +12,6 @@ using Bit.Core.Repositories;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.NotificationHubs;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -229,7 +229,7 @@ namespace Bit.Core.Services
new Dictionary<string, string>
{
{ "type", ((byte)type).ToString() },
{ "payload", JsonConvert.SerializeObject(payload) }
{ "payload", JsonSerializer.Serialize(payload) }
}, tag);
}

View File

@@ -9,7 +9,6 @@ using Bit.Core.Models;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -18,11 +17,6 @@ namespace Bit.Core.Services
private readonly GlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
private JsonSerializerSettings _jsonSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
public NotificationsApiPushNotificationService(
GlobalSettings globalSettings,
IHttpContextAccessor httpContextAccessor,

View File

@@ -15,7 +15,6 @@ using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Stripe;
namespace Bit.Core.Services
@@ -662,6 +661,12 @@ namespace Bit.Core.Services
OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey,
string privateKey)
{
if (license?.LicenseType != null && license.LicenseType != LicenseType.Organization)
{
throw new BadRequestException("Premium licenses cannot be applied to an organization. "
+ "Upload this license from your personal account settings page.");
}
if (license == null || !_licensingService.VerifyLicense(license))
{
throw new BadRequestException("Invalid license.");
@@ -727,8 +732,8 @@ namespace Bit.Core.Services
var dir = $"{_globalSettings.LicenseDirectory}/organization";
Directory.CreateDirectory(dir);
System.IO.File.WriteAllText($"{dir}/{organization.Id}.json",
JsonConvert.SerializeObject(license, Formatting.Indented));
using var fs = System.IO.File.OpenWrite(Path.Combine(dir, $"{organization.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
return result;
}
@@ -807,6 +812,12 @@ namespace Bit.Core.Services
throw new InvalidOperationException("Licenses require self hosting.");
}
if (license?.LicenseType != null && license.LicenseType != LicenseType.Organization)
{
throw new BadRequestException("Premium licenses cannot be applied to an organization. "
+ "Upload this license from your personal account settings page.");
}
if (license == null || !_licensingService.VerifyLicense(license))
{
throw new BadRequestException("Invalid license.");
@@ -900,8 +911,8 @@ namespace Bit.Core.Services
var dir = $"{_globalSettings.LicenseDirectory}/organization";
Directory.CreateDirectory(dir);
System.IO.File.WriteAllText($"{dir}/{organization.Id}.json",
JsonConvert.SerializeObject(license, Formatting.Indented));
using var fs = System.IO.File.OpenWrite(Path.Combine(dir, $"{organization.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
organization.Name = license.Name;
organization.BusinessName = license.BusinessName;
@@ -1146,10 +1157,7 @@ namespace Bit.Core.Services
if (invite.Permissions != null)
{
orgUser.Permissions = System.Text.Json.JsonSerializer.Serialize(invite.Permissions, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
orgUser.Permissions = JsonSerializer.Serialize(invite.Permissions, JsonHelpers.CamelCase);
}
if (!orgUser.AccessAll && invite.Collections.Any())
@@ -1765,7 +1773,7 @@ namespace Bit.Core.Services
// Block the user from withdrawal if auto enrollment is enabled
if (resetPasswordKey == null && resetPasswordPolicy.Data != null)
{
var data = JsonConvert.DeserializeObject<ResetPasswordDataModel>(resetPasswordPolicy.Data);
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data);
if (data?.AutoEnrollEnabled ?? false)
{

View File

@@ -37,11 +37,11 @@ namespace Bit.Core.Services
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationSponsorshipServiceDataProtector");
}
public async Task<bool> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail)
public async Task<(bool valid, OrganizationSponsorship sponsorship)> ValidateRedemptionTokenAsync(string encryptedToken, string sponsoredUserEmail)
{
if (!encryptedToken.StartsWith(TokenClearTextPrefix) || sponsoredUserEmail == null)
{
return false;
return (false, null);
}
var decryptedToken = _dataProtector.Unprotect(encryptedToken[TokenClearTextPrefix.Length..]);
@@ -49,7 +49,7 @@ namespace Bit.Core.Services
if (dataParts.Length != 3)
{
return false;
return (false, null);
}
if (dataParts[0].Equals(FamiliesForEnterpriseTokenName))
@@ -57,7 +57,7 @@ namespace Bit.Core.Services
if (!Guid.TryParse(dataParts[1], out Guid sponsorshipId) ||
!Enum.TryParse<PlanSponsorshipType>(dataParts[2], true, out var sponsorshipType))
{
return false;
return (false, null);
}
var sponsorship = await _organizationSponsorshipRepository.GetByIdAsync(sponsorshipId);
@@ -65,13 +65,13 @@ namespace Bit.Core.Services
sponsorship.PlanSponsorshipType != sponsorshipType ||
sponsorship.OfferedToEmail != sponsoredUserEmail)
{
return false;
return (false, sponsorship);
}
return true;
return (true, sponsorship);
}
return false;
return (false, null);
}
private string RedemptionToken(Guid sponsorshipId, PlanSponsorshipType sponsorshipType) =>

View File

@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -76,15 +75,13 @@ namespace Bit.Core.Services
request.tag = string.Concat(request.tag, "-Cat_", message.Category);
}
var reqJson = JsonConvert.SerializeObject(request);
var responseMessage = await httpClient.PostAsync(
var responseMessage = await httpClient.PostAsJsonAsync(
$"https://{_globalSettings.Mail.PostalDomain}/api/v1/send/message",
new StringContent(reqJson, Encoding.UTF8, "application/json"));
request);
if (responseMessage.IsSuccessStatusCode)
{
var json = await responseMessage.Content.ReadAsStringAsync();
var response = JsonConvert.DeserializeObject<PostalResponse>(json);
var response = await responseMessage.Content.ReadFromJsonAsync<PostalResponse>();
if (response.status != "success")
{
_logger.LogError("Postal send status was not successful: {0}, {1}",

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Entities;
@@ -12,7 +13,6 @@ using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
namespace Bit.Core.Services
{
@@ -104,8 +104,8 @@ namespace Bit.Core.Services
data.Id = fileId;
data.Size = fileLength;
data.Validated = false;
send.Data = JsonConvert.SerializeObject(data,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
send.Data = JsonSerializer.Serialize(data,
JsonHelpers.IgnoreWritingNull);
await SaveSendAsync(send);
return await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId);
}
@@ -129,7 +129,7 @@ namespace Bit.Core.Services
throw new BadRequestException("Not a File Type Send.");
}
var data = JsonConvert.DeserializeObject<SendFileData>(send.Data);
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
if (data.Validated)
{
@@ -146,7 +146,7 @@ namespace Bit.Core.Services
public async Task<bool> ValidateSendFile(Send send)
{
var fileData = JsonConvert.DeserializeObject<SendFileData>(send.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send.Data);
var (valid, realSize) = await _sendFileStorageService.ValidateFileAsync(send, fileData.Id, fileData.Size, _fileSizeLeeway);
@@ -163,8 +163,8 @@ namespace Bit.Core.Services
fileData.Size = realSize.Value;
}
fileData.Validated = true;
send.Data = JsonConvert.SerializeObject(fileData,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
send.Data = JsonSerializer.Serialize(fileData,
JsonHelpers.IgnoreWritingNull);
await SaveSendAsync(send);
return valid;
@@ -175,7 +175,7 @@ namespace Bit.Core.Services
await _sendRepository.DeleteAsync(send);
if (send.Type == Enums.SendType.File)
{
var data = JsonConvert.DeserializeObject<SendFileData>(send.Data);
var data = JsonSerializer.Deserialize<SendFileData>(send.Data);
await _sendFileStorageService.DeleteFileAsync(send, data.Id);
}
await _pushService.PushSyncSendDeleteAsync(send);

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Bit.Core.Context;
using Bit.Core.Entities;
@@ -19,9 +20,7 @@ using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using File = System.IO.File;
using U2fLib = U2F.Core.Crypto.U2F;
namespace Bit.Core.Services
{
@@ -481,25 +480,6 @@ namespace Bit.Core.Services
return false;
}
// Delete U2F token is this is a migrated WebAuthn token.
var entry = new TwoFactorProvider.WebAuthnData(provider.MetaData[keyName]);
if (entry?.Migrated ?? false)
{
var u2fProvider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f);
if (u2fProvider?.MetaData?.ContainsKey(keyName) ?? false)
{
u2fProvider.MetaData.Remove(keyName);
if (u2fProvider.MetaData.Count > 0)
{
providers[TwoFactorProviderType.U2f] = u2fProvider;
}
else
{
providers.Remove(TwoFactorProviderType.U2f);
}
}
}
provider.MetaData.Remove(keyName);
providers[TwoFactorProviderType.WebAuthn] = provider;
user.SetTwoFactorProviders(providers);
@@ -899,13 +879,6 @@ namespace Bit.Core.Services
return;
}
// Since the user can no longer directly manipulate U2F tokens, we should
// disable them when the user disables WebAuthn.
if (type == TwoFactorProviderType.WebAuthn)
{
providers.Remove(TwoFactorProviderType.U2f);
}
providers.Remove(type);
user.SetTwoFactorProviders(providers);
await SaveUserAsync(user);
@@ -983,7 +956,8 @@ namespace Bit.Core.Services
var dir = $"{_globalSettings.LicenseDirectory}/user";
Directory.CreateDirectory(dir);
File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented));
using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
}
else
{
@@ -1056,6 +1030,12 @@ namespace Bit.Core.Services
throw new InvalidOperationException("Licenses require self hosting.");
}
if (license?.LicenseType != null && license.LicenseType != LicenseType.User)
{
throw new BadRequestException("Organization licenses cannot be applied to a user. "
+ "Upload this license from the Organization settings page.");
}
if (license == null || !_licenseService.VerifyLicense(license))
{
throw new BadRequestException("Invalid license.");
@@ -1068,7 +1048,8 @@ namespace Bit.Core.Services
var dir = $"{_globalSettings.LicenseDirectory}/user";
Directory.CreateDirectory(dir);
File.WriteAllText($"{dir}/{user.Id}.json", JsonConvert.SerializeObject(license, Formatting.Indented));
using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
user.Premium = license.Premium;
user.RevisionDate = DateTime.UtcNow;

View File

@@ -23,7 +23,6 @@ using Bit.Core.Settings;
using IdentityModel;
using Microsoft.AspNetCore.DataProtection;
using MimeKit;
using Newtonsoft.Json;
namespace Bit.Core.Utilities
{
@@ -332,12 +331,12 @@ namespace Bit.Core.Utilities
/// <summary>
/// Creates a clone of the given object through serializing to json and deserializing.
/// This method is subject to the limitations of Newstonsoft. For example, properties with
/// This method is subject to the limitations of System.Text.Json. For example, properties with
/// inaccessible setters will not be set.
/// </summary>
public static T CloneObject<T>(T obj)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj));
return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(obj));
}
public static bool SettingHasValue(string setting)

View File

@@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Bit.Core.Utilities
{
@@ -41,10 +40,8 @@ namespace Bit.Core.Utilities
$"Slow down! Too many requests. Try again in {rule.Period}." : _options.QuotaExceededMessage;
httpContext.Response.Headers["Retry-After"] = retryAfter;
httpContext.Response.StatusCode = _options.HttpStatusCode;
httpContext.Response.ContentType = "application/json";
var errorModel = new ErrorResponseModel { Message = message };
return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(errorModel));
return httpContext.Response.WriteAsJsonAsync(errorModel, cancellationToken: httpContext.RequestAborted);
}
public override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity,

View File

@@ -15,9 +15,9 @@ using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Web;
using Newtonsoft.Json;
namespace Bit.Core.Utilities.Duo
{
@@ -175,22 +175,21 @@ namespace Bit.Core.Utilities.Duo
var res = ApiCall(method, path, parameters, timeout, out var statusCode);
try
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(res);
if (dict["stat"] as string == "OK")
// TODO: We should deserialize this into our own DTO and not work on dictionaries.
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(res);
if (dict["stat"].ToString() == "OK")
{
return dict["response"] as T;
return JsonSerializer.Deserialize<T>(dict["response"].ToString());
}
else
var check = ToNullableInt(dict["code"].ToString());
var code = check.GetValueOrDefault(0);
var messageDetail = string.Empty;
if (dict.ContainsKey("message_detail"))
{
var check = dict["code"] as int?;
var code = check.GetValueOrDefault(0);
var messageDetail = string.Empty;
if (dict.ContainsKey("message_detail"))
{
messageDetail = dict["message_detail"] as string;
}
throw new ApiException(code, (int)statusCode, dict["message"] as string, messageDetail);
messageDetail = dict["message_detail"].ToString();
}
throw new ApiException(code, (int)statusCode, dict["message"].ToString(), messageDetail);
}
catch (ApiException)
{
@@ -202,6 +201,16 @@ namespace Bit.Core.Utilities.Duo
}
}
private int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i))
{
return i;
}
return null;
}
private string HmacSign(string data)
{
var keyBytes = Encoding.ASCII.GetBytes(_skey);

Some files were not shown because too many files have changed in this diff Show More