1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00

Merge branch 'master' into feature/billing-obfuscation

This commit is contained in:
Rui Tome
2023-02-27 14:52:51 +00:00
323 changed files with 39115 additions and 2469 deletions

View File

@ -0,0 +1,14 @@
---
name: Build Migrator CLI
on:
workflow_dispatch:
jobs:
stub:
name: Stub
runs-on: ubuntu-22.04
steps:
- name: Stub
run: echo "Stub"

View File

@ -9,6 +9,12 @@ on:
paths-ignore:
- ".github/workflows/**"
workflow_dispatch:
pull_request:
branches-ignore:
- "l10n_master"
- "gh-pages"
paths:
- ".github/workflows/build-self-host.yml"
jobs:
build-docker:
@ -48,11 +54,21 @@ jobs:
run: az acr login -n bitwardenqa
- name: Login to Azure - Prod Subscription
if: ${{ env.is_publish_branch == 'true' }}
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenprod
- name: Retrieve github PAT secrets
id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Retrieve secrets
if: ${{ env.is_publish_branch == 'true' }}
id: retrieve-secrets
@ -102,9 +118,9 @@ jobs:
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
run: |
if [ "$IMAGE_TAG" = "dev" ] || [ "$IMAGE_TAG" = "beta" ]; then
echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT
echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT
else
echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT
echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT
fi
- name: Build Docker image
@ -118,6 +134,8 @@ jobs:
linux/arm64/v8
push: true
tags: ${{ steps.tag-list.outputs.tags }}
secrets: |
"GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
- name: Log out of Docker and disable Docker Notary
if: ${{ env.is_publish_branch == 'true' }}

View File

@ -191,74 +191,145 @@ jobs:
include:
- project_name: Admin
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Api
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Attachments
base_path: ./util
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
- project_name: Events
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: EventsProcessor
base_path: ./src
docker_repos: [bitwardenqa.azurecr.io]
docker_repos: [bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Icons
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Identity
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: MsSql
base_path: ./util
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
- project_name: Nginx
base_path: ./util
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
- project_name: Notifications
base_path: ./src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Server
base_path: ./util
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Setup
base_path: ./util
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Sso
base_path: ./bitwarden_license/src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Scim
base_path: ./bitwarden_license/src
docker_repos: [bitwarden, bitwardenqa.azurecr.io]
docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
- project_name: Billing
base_path: ./src
docker_repos: [bitwardenqa.azurecr.io]
docker_repos: [bitwardenprod.azurecr.io, bitwardenqa.azurecr.io]
dotnet: true
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Set up image tag
- name: Check Branch to Publish
env:
PUBLISH_BRANCHES: "master,rc,hotfix-rc"
id: publish-branch-check
run: |
IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES
if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then
echo "is_publish_branch=true" >> $GITHUB_ENV
else
echo "is_publish_branch=false" >> $GITHUB_ENV
fi
########## ACRs ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to QA ACR
run: az acr login -n bitwardenqa
- name: Login to Azure - PROD Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to PROD ACR
run: az acr login -n bitwardenprod
- name: Retrieve github PAT secrets
id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Retrieve secrets
if: ${{ env.is_publish_branch == 'true' }}
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "docker-password,
docker-username,
dct-delegate-2-repo-passphrase,
dct-delegate-2-key"
- name: Log into Docker
if: ${{ env.is_publish_branch == 'true' }}
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: ${{ env.is_publish_branch == 'true' }}
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
########## Generate image tag and build Docker image ##########
- name: Generate Docker image tag
id: tag
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
elif [[ "$IMAGE_TAG" == "rc" ]] || [[ "$IMAGE_TAG" == "hotfix-rc" ]]; then
IMAGE_TAG=beta
fi
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
########## Build Docker Image ##########
echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Setup project name
id: setup
run: |
@ -267,6 +338,13 @@ jobs:
echo "PROJECT_NAME: $PROJECT_NAME"
echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT
- name: Generate tag list
id: tag-list
env:
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
run: echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT
- name: Get build artifact
if: ${{ matrix.dotnet }}
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741
@ -281,125 +359,26 @@ jobs:
-d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish
- name: Build Docker image
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
run: docker build -t $PROJECT_NAME ${{ matrix.base_path }}/${{ matrix.project_name }}
########## QA ACR ##########
- name: Login to Azure - QA Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
context: ${{ matrix.base_path }}/${{ matrix.project_name }}
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.tag-list.outputs.tags }}
secrets: |
"GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
- name: Login to QA ACR
run: az acr login -n bitwardenqa
- name: Tag and push image to QA ACR
- name: Push to DockerHub
if: contains(matrix.docker_repos, 'bitwarden') && env.is_publish_branch == 'true'
env:
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenqa.azurecr.io
run: |
docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker tag bitwardenprod.azurecr.io/$PROJECT_NAME:$IMAGE_TAG bitwarden/$PROJECT_NAME:$IMAGE_TAG
docker push bitwarden/$PROJECT_NAME:$IMAGE_TAG
- name: Log out of Docker
run: docker logout
########## PROD ACR ##########
- name: Login to Azure - PROD Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to PROD ACR
run: az acr login -n bitwardenprod
- name: Tag and push image to PROD ACR
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenprod.azurecr.io
run: |
docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
- 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@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
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: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
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:
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:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwarden
run: |
docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:${{ env.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

View File

@ -22,10 +22,21 @@ jobs:
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Login to Azure - PROD Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenprod
########## Remove Docker images ##########
- name: Remove the docker image from ACR
env:
REGISTRY_NAME: bitwardenqa
REGISTRIES: |
registries:
- bitwardenprod
- bitwardenqa
SERVICES: |
services:
- Admin
@ -45,21 +56,24 @@ jobs:
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
for REGISTRY in $( echo "${{ env.REGISTRIES }}" | yq e ".registries[]" - )
do
SERVICE_NAME=$(echo $SERVICE | awk '{print tolower($0)}')
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
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")'
)
echo "[*] Checking if remote exists: $REGISTRY.azurecr.io/$SERVICE_NAME:$IMAGE_TAG"
TAG_EXISTS=$(
az acr repository show-tags --name $REGISTRY --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
if [[ "$TAG_EXISTS" == "true" ]]; then
echo "[*] Tag exists. Removing tag"
az acr repository delete --name $REGISTRY --image $SERVICE_NAME:$IMAGE_TAG --yes
else
echo "[*] Tag does not exist. No action needed"
fi
done
done
- name: Log out of Docker

View File

@ -11,28 +11,15 @@ 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 }}
REGISTRY: bitwardenprod
AGO_DUR_VER: "180d"
AGO_DUR: "30d"
run: |

View File

@ -83,11 +83,17 @@ jobs:
run: /usr/local/sqlpackage/sqlpackage /action:DeployReport /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"report.xml" /p:IgnoreColumnOrder=True /p:IgnoreComments=True
shell: pwsh
- name: Generate SQL file
run: /usr/local/sqlpackage/sqlpackage /action:Script /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"diff.sql" /p:IgnoreColumnOrder=True /p:IgnoreComments=True
shell: pwsh
- name: Upload Report
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: report.xml
path: report.xml
path: |
report.xml
diff.sql
- name: Validate XML
run: |

View File

@ -188,7 +188,7 @@ jobs:
origin_docker_repo: bitwarden
- project_name: EventsProcessor
prod_acr: true
origin_docker_repo: bitwardenqa.azurecr.io
origin_docker_repo: bitwardenprod.azurecr.io
- project_name: Icons
origin_docker_repo: bitwarden
prod_acr: true
@ -209,7 +209,7 @@ jobs:
- project_name: Scim
origin_docker_repo: bitwarden
- project_name: Billing
origin_docker_repo: bitwardenqa.azurecr.io
origin_docker_repo: bitwardenprod.azurecr.io
steps:
- name: Print environment
env:
@ -277,31 +277,19 @@ jobs:
docker logout
echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV
########## ACR QA ##########
- name: Login to Azure - QA Subscription
########## ACR PROD ##########
- name: Login to Azure - PROD Subscription
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to Azure ACR
run: az acr login -n bitwardenqa
- name: Pull latest project image
if: matrix.origin_docker_repo == 'bitwardenqa.azurecr.io'
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenqa.azurecr.io
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker pull $REGISTRY/$PROJECT_NAME:latest
else
docker pull $REGISTRY/$PROJECT_NAME:$_BRANCH_NAME
fi
run: az acr login -n bitwardenprod
- name: Tag version and latest
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenqa.azurecr.io
REGISTRY: bitwardenprod.azurecr.io
ORIGIN_REGISTRY: ${{ matrix.origin_docker_repo }}
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
@ -313,43 +301,6 @@ jobs:
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenqa.azurecr.io
run: |
docker push $REGISTRY/$PROJECT_NAME:latest
docker push $REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION
- name: Log out of Docker
run: docker logout
########## ACR PROD ##########
- name: Login to Azure - PROD Subscription
if: matrix.prod_acr == true
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to Azure ACR
if: matrix.prod_acr == true
run: az acr login -n bitwardenprod
- name: Tag version and latest
if: matrix.prod_acr == true
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenprod.azurecr.io
ORIGIN_REGISTRY: ${{ matrix.origin_docker_repo }}
run: |
if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then
docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:latest $REGISTRY/$PROJECT_NAME:dryrun
else
docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME $REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION
docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME $REGISTRY/$PROJECT_NAME:latest
fi
- name: Push version and latest image
if: ${{ github.event.inputs.release_type != 'Dry Run' && matrix.prod_acr == true }}
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenprod.azurecr.io
@ -358,7 +309,6 @@ jobs:
docker push $REGISTRY/$PROJECT_NAME:latest
- name: Log out of Docker
if: matrix.prod_acr == true
run: docker logout
release:

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<!--2022.6.2-->
<Version>2023.1.0</Version>
<Version>2023.2.1</Version>
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ImplicitUsings>enable</ImplicitUsings>

View File

@ -80,16 +80,3 @@ Consider installing our git pre-commit hook for automatic formatting.
```bash
git config --local core.hooksPath .git-hooks
```
### File Scoped Namespaces
We recently migrated to using file scoped namespaces to save some horizontal space. All previous branches will need to update to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge 9b7aef0763ad14e229b337c3b5b27cb411009792`
3. Resolve any merge conflicts, commit.
4. Run `dotnet format`
5. Commit
6. Run `git merge -Xours 7f5f010e1eea400300c47f776604ecf46c4b4f2d`
7. Fix Merge conflicts
8. Push

View File

@ -1,4 +1,5 @@
using Bit.Core.Exceptions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -8,13 +9,36 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository)
public CreateAccessPoliciesCommand(
IAccessPolicyRepository accessPolicyRepository,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_accessPolicyRepository = accessPolicyRepository;
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
}
public async Task<List<BaseAccessPolicy>> CreateAsync(List<BaseAccessPolicy> accessPolicies)
private static IEnumerable<Guid?> GetDistinctGrantedProjectIds(List<BaseAccessPolicy> accessPolicies)
{
var userGrantedIds = accessPolicies.OfType<UserProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
var groupGrantedIds = accessPolicies.OfType<GroupProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
var saGrantedIds = accessPolicies.OfType<ServiceAccountProjectAccessPolicy>().Select(ap => ap.GrantedProjectId);
return userGrantedIds.Concat(groupGrantedIds).Concat(saGrantedIds).Distinct();
}
private static IEnumerable<Guid?> GetDistinctGrantedServiceAccountIds(List<BaseAccessPolicy> accessPolicies)
{
var userGrantedIds = accessPolicies.OfType<UserServiceAccountAccessPolicy>().Select(ap => ap.GrantedServiceAccountId);
var groupGrantedIds = accessPolicies.OfType<GroupServiceAccountAccessPolicy>()
.Select(ap => ap.GrantedServiceAccountId);
return userGrantedIds.Concat(groupGrantedIds).Distinct();
}
private static void CheckForDistinctAccessPolicies(IReadOnlyCollection<BaseAccessPolicy> accessPolicies)
{
var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy =>
{
@ -22,8 +46,12 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{
UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),
GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId, ap.GrantedProjectId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy))
ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,
ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId,
ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)),
};
}).ToList();
@ -31,7 +59,44 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
{
throw new BadRequestException("Resources must be unique");
}
}
public async Task<IEnumerable<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> accessPolicies, Guid userId, AccessClientType accessType)
{
CheckForDistinctAccessPolicies(accessPolicies);
await CheckAccessPoliciesDoNotExistAsync(accessPolicies);
await CheckCanCreateAsync(accessPolicies, userId, accessType);
return await _accessPolicyRepository.CreateManyAsync(accessPolicies);
}
private async Task CheckCanCreateAsync(List<BaseAccessPolicy> accessPolicies, Guid userId, AccessClientType accessType)
{
var projectIds = GetDistinctGrantedProjectIds(accessPolicies).ToList();
var serviceAccountIds = GetDistinctGrantedServiceAccountIds(accessPolicies).ToList();
if (projectIds.Any())
{
foreach (var projectId in projectIds)
{
await CheckPermissionAsync(accessType, userId, projectId);
}
}
if (serviceAccountIds.Any())
{
foreach (var serviceAccountId in serviceAccountIds)
{
await CheckPermissionAsync(accessType, userId, serviceAccountIdToCheck: serviceAccountId);
}
}
if (!projectIds.Any() && !serviceAccountIds.Any())
{
throw new BadRequestException("No granted IDs specified");
}
}
private async Task CheckAccessPoliciesDoNotExistAsync(List<BaseAccessPolicy> accessPolicies)
{
foreach (var accessPolicy in accessPolicies)
{
if (await _accessPolicyRepository.AccessPolicyExists(accessPolicy))
@ -39,7 +104,42 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand
throw new BadRequestException("Resource already exists");
}
}
}
return await _accessPolicyRepository.CreateManyAsync(accessPolicies);
private async Task CheckPermissionAsync(AccessClientType accessClient, Guid userId, Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
if (!hasAccess)
{
throw new NotFoundException();
}
}
}

View File

@ -1,5 +1,8 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
@ -7,14 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository)
public DeleteAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
}
public async Task DeleteAsync(Guid id)
public async Task DeleteAsync(Guid id, Guid userId)
{
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null)
@ -22,6 +34,74 @@ public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand
throw new NotFoundException();
}
if (!await IsAllowedToDeleteAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
await _accessPolicyRepository.DeleteAsync(id);
}
private async Task<bool> IsAllowedToDeleteAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
}

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -8,13 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository)
public UpdateAccessPolicyCommand(
IAccessPolicyRepository accessPolicyRepository,
ICurrentContext currentContext,
IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository)
{
_accessPolicyRepository = accessPolicyRepository;
_currentContext = currentContext;
_projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository;
}
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write)
public async Task<BaseAccessPolicy> UpdateAsync(Guid id, bool read, bool write, Guid userId)
{
var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id);
if (accessPolicy == null)
@ -22,11 +34,78 @@ public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand
throw new NotFoundException();
}
if (!await IsAllowedToUpdateAsync(accessPolicy, userId))
{
throw new NotFoundException();
}
accessPolicy.Read = read;
accessPolicy.Write = write;
accessPolicy.RevisionDate = DateTime.UtcNow;
await _accessPolicyRepository.ReplaceAsync(accessPolicy);
return accessPolicy;
}
private async Task<bool> IsAllowedToUpdateAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) =>
baseAccessPolicy switch
{
UserProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
GroupProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId,
ap.GrantedProjectId),
ServiceAccountProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId,
userId, ap.GrantedProjectId),
UserServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
GroupServiceAccountAccessPolicy ap => await HasPermissionsAsync(
ap.GrantedServiceAccount!.OrganizationId,
userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId),
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
private async Task<bool> HasPermissionsAsync(
Guid organizationId,
Guid userId,
Guid? projectIdToCheck = null,
Guid? serviceAccountIdToCheck = null)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
return false;
}
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
bool hasAccess;
switch (accessClient)
{
case AccessClientType.NoAccessCheck:
hasAccess = true;
break;
case AccessClientType.User:
if (projectIdToCheck.HasValue)
{
hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId);
}
else if (serviceAccountIdToCheck.HasValue)
{
hasAccess =
await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccountIdToCheck.Value, userId);
}
else
{
throw new ArgumentException("No ID to check provided.");
}
break;
default:
hasAccess = false;
break;
}
return hasAccess;
}
}

View File

@ -0,0 +1,101 @@
using Bit.Core.SecretsManager.Commands.Porting;
using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.Porting;
public class ImportCommand : IImportCommand
{
private readonly IProjectRepository _projectRepository;
private readonly ISecretRepository _secretRepository;
public ImportCommand(IProjectRepository projectRepository, ISecretRepository secretRepository)
{
_projectRepository = projectRepository;
_secretRepository = secretRepository;
}
public async Task ImportAsync(Guid organizationId, SMImport import)
{
var importedProjects = new List<Guid>();
var importedSecrets = new List<Guid>();
try
{
import = AssignNewIds(import);
if (import.Projects.Any())
{
importedProjects = (await _projectRepository.ImportAsync(import.Projects.Select(p => new Project
{
Id = p.Id,
OrganizationId = organizationId,
Name = p.Name,
}))).Select(p => p.Id).ToList();
}
if (import.Secrets != null && import.Secrets.Any())
{
importedSecrets = (await _secretRepository.ImportAsync(import.Secrets.Select(s => new Secret
{
Id = s.Id,
OrganizationId = organizationId,
Key = s.Key,
Value = s.Value,
Note = s.Note,
Projects = s.ProjectIds?.Select(id => new Project { Id = id }).ToList(),
}))).Select(s => s.Id).ToList();
}
}
catch (Exception)
{
if (importedProjects.Any())
{
await _projectRepository.DeleteManyByIdAsync(importedProjects);
}
if (importedSecrets.Any())
{
await _secretRepository.HardDeleteManyByIdAsync(importedSecrets);
}
throw new Exception("Error attempting import");
}
}
public SMImport AssignNewIds(SMImport import)
{
var projects = new Dictionary<Guid, SMImport.InnerProject>();
var secrets = new List<SMImport.InnerSecret>();
if (import.Projects != null && import.Projects.Any())
{
projects = import.Projects.ToDictionary(
p => p.Id,
p => new SMImport.InnerProject { Id = Guid.NewGuid(), Name = p.Name }
);
}
if (import.Secrets != null && import.Secrets.Any())
{
foreach (var secret in import.Secrets)
{
secrets.Add(new SMImport.InnerSecret
{
Id = Guid.NewGuid(),
Key = secret.Key,
Value = secret.Value,
Note = secret.Note,
ProjectIds = secret.ProjectIds?.Select(id => projects[id].Id),
});
}
}
return new SMImport
{
Projects = projects.Values,
Secrets = secrets,
};
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -6,15 +7,35 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects;
public class CreateProjectCommand : ICreateProjectCommand
{
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProjectRepository _projectRepository;
public CreateProjectCommand(IProjectRepository projectRepository)
public CreateProjectCommand(
IAccessPolicyRepository accessPolicyRepository,
IOrganizationUserRepository organizationUserRepository,
IProjectRepository projectRepository
)
{
_accessPolicyRepository = accessPolicyRepository;
_organizationUserRepository = organizationUserRepository;
_projectRepository = projectRepository;
}
public async Task<Project> CreateAsync(Project project)
public async Task<Project> CreateAsync(Project project, Guid userId)
{
return await _projectRepository.CreateAsync(project);
var createdProject = await _projectRepository.CreateAsync(project);
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(createdProject.OrganizationId,
userId);
var accessPolicy = new UserProjectAccessPolicy()
{
OrganizationUserId = orgUser.Id,
GrantedProjectId = createdProject.Id,
Read = true,
Write = true,
};
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy });
return createdProject;
}
}

View File

@ -1,4 +1,7 @@
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -7,14 +10,34 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
public class CreateSecretCommand : ICreateSecretCommand
{
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly ICurrentContext _currentContext;
public CreateSecretCommand(ISecretRepository secretRepository)
public CreateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
{
_secretRepository = secretRepository;
_projectRepository = projectRepository;
_currentContext = currentContext;
}
public async Task<Secret> CreateAsync(Secret secret)
public async Task<Secret> CreateAsync(Secret secret, Guid userId)
{
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var project = secret.Projects?.FirstOrDefault();
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
return await _secretRepository.CreateAsync(secret);
}
}

View File

@ -1,4 +1,5 @@
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
@ -10,25 +11,27 @@ public class DeleteSecretCommand : IDeleteSecretCommand
{
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
public DeleteSecretCommand(ICurrentContext currentContext, ISecretRepository secretRepository)
public DeleteSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
{
_currentContext = currentContext;
_secretRepository = secretRepository;
_projectRepository = projectRepository;
}
public async Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids)
public async Task<List<Tuple<Secret, string>>> DeleteSecrets(List<Guid> ids, Guid userId)
{
var secrets = await _secretRepository.GetManyByIds(ids);
var secrets = (await _secretRepository.GetManyByIds(ids)).ToList();
if (secrets?.Any() != true)
if (secrets.Any() != true)
{
throw new NotFoundException();
}
// Ensure all secrets belongs to the same organization
var organizationId = secrets.First().OrganizationId;
if (secrets.Any(p => p.OrganizationId != organizationId))
if (secrets.Any(secret => secret.OrganizationId != organizationId))
{
throw new BadRequestException();
}
@ -38,21 +41,46 @@ public class DeleteSecretCommand : IDeleteSecretCommand
throw new NotFoundException();
}
var results = ids.Select(id =>
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var results = new List<Tuple<Secret, string>>();
var deleteIds = new List<Guid>();
foreach (var secret in secrets)
{
var secret = secrets.FirstOrDefault(secret => secret.Id == id);
if (secret == null)
var hasAccess = orgAdmin;
if (secret.Projects != null && secret.Projects?.Count > 0)
{
throw new NotFoundException();
var projectId = secret.Projects.First().Id;
hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(projectId, userId),
_ => false,
};
}
if (!hasAccess)
{
results.Add(new Tuple<Secret, string>(secret, "access denied"));
}
// TODO Once permissions are implemented add check for each secret here.
else
{
return new Tuple<Secret, string>(secret, "");
deleteIds.Add(secret.Id);
results.Add(new Tuple<Secret, string>(secret, ""));
}
}).ToList();
}
if (deleteIds.Count > 0)
{
await _secretRepository.SoftDeleteManyByIdAsync(deleteIds);
}
await _secretRepository.SoftDeleteManyByIdAsync(ids);
return results;
}
}

View File

@ -1,4 +1,6 @@
using Bit.Core.Exceptions;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -8,23 +10,45 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
public class UpdateSecretCommand : IUpdateSecretCommand
{
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly ICurrentContext _currentContext;
public UpdateSecretCommand(ISecretRepository secretRepository)
public UpdateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext)
{
_secretRepository = secretRepository;
_projectRepository = projectRepository;
_currentContext = currentContext;
}
public async Task<Secret> UpdateAsync(Secret secret)
public async Task<Secret> UpdateAsync(Secret updatedSecret, Guid userId)
{
var existingSecret = await _secretRepository.GetByIdAsync(secret.Id);
if (existingSecret == null)
var secret = await _secretRepository.GetByIdAsync(updatedSecret.Id);
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
{
throw new NotFoundException();
}
secret.OrganizationId = existingSecret.OrganizationId;
secret.CreationDate = existingSecret.CreationDate;
secret.DeletedDate = existingSecret.DeletedDate;
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var project = updatedSecret.Projects?.FirstOrDefault();
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
secret.Key = updatedSecret.Key;
secret.Value = updatedSecret.Value;
secret.Note = updatedSecret.Note;
secret.Projects = updatedSecret.Projects;
secret.RevisionDate = DateTime.UtcNow;
await _secretRepository.UpdateAsync(secret);

View File

@ -0,0 +1,24 @@
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
public class RevokeAccessTokensCommand : IRevokeAccessTokensCommand
{
private readonly IApiKeyRepository _apiKeyRepository;
public RevokeAccessTokensCommand(IApiKeyRepository apiKeyRepository)
{
_apiKeyRepository = apiKeyRepository;
}
public async Task RevokeAsync(ServiceAccount serviceAccount, IEnumerable<Guid> Ids)
{
var accessTokens = await _apiKeyRepository.GetManyByServiceAccountIdAsync(serviceAccount.Id);
var tokensToDelete = accessTokens.Where(at => Ids.Contains(at.Id));
await _apiKeyRepository.DeleteManyAsync(tokensToDelete);
}
}

View File

@ -0,0 +1,28 @@
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.Trash;
public class EmptyTrashCommand : IEmptyTrashCommand
{
private readonly ISecretRepository _secretRepository;
public EmptyTrashCommand(ISecretRepository secretRepository)
{
_secretRepository = secretRepository;
}
public async Task EmptyTrash(Guid organizationId, List<Guid> ids)
{
var secrets = await _secretRepository.GetManyByOrganizationIdInTrashByIdsAsync(organizationId, ids);
var missingId = ids.Except(secrets.Select(_ => _.Id)).Any();
if (missingId)
{
throw new NotFoundException();
}
await _secretRepository.HardDeleteManyByIdAsync(ids);
}
}

View File

@ -0,0 +1,28 @@
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Repositories;
namespace Bit.Commercial.Core.SecretsManager.Commands.Trash;
public class RestoreTrashCommand : IRestoreTrashCommand
{
private readonly ISecretRepository _secretRepository;
public RestoreTrashCommand(ISecretRepository secretRepository)
{
_secretRepository = secretRepository;
}
public async Task RestoreTrash(Guid organizationId, List<Guid> ids)
{
var secrets = await _secretRepository.GetManyByOrganizationIdInTrashByIdsAsync(organizationId, ids);
var missingId = ids.Except(secrets.Select(_ => _.Id)).Any();
if (missingId)
{
throw new NotFoundException();
}
await _secretRepository.RestoreManyByIdAsync(ids);
}
}

View File

@ -1,13 +1,17 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens;
using Bit.Commercial.Core.SecretsManager.Commands.Porting;
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
using Bit.Commercial.Core.SecretsManager.Commands.Trash;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces;
using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Commercial.Core.SecretsManager;
@ -24,9 +28,13 @@ public static class SecretsManagerCollectionExtensions
services.AddScoped<IDeleteProjectCommand, DeleteProjectCommand>();
services.AddScoped<ICreateServiceAccountCommand, CreateServiceAccountCommand>();
services.AddScoped<IUpdateServiceAccountCommand, UpdateServiceAccountCommand>();
services.AddScoped<IRevokeAccessTokensCommand, RevokeAccessTokensCommand>();
services.AddScoped<ICreateAccessTokenCommand, CreateAccessTokenCommand>();
services.AddScoped<ICreateAccessPoliciesCommand, CreateAccessPoliciesCommand>();
services.AddScoped<IUpdateAccessPolicyCommand, UpdateAccessPolicyCommand>();
services.AddScoped<IDeleteAccessPolicyCommand, DeleteAccessPolicyCommand>();
services.AddScoped<IImportCommand, ImportCommand>();
services.AddScoped<IEmptyTrashCommand, EmptyTrashCommand>();
services.AddScoped<IRestoreTrashCommand, RestoreTrashCommand>();
}
}

View File

@ -45,8 +45,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -83,28 +83,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -126,6 +126,14 @@
"System.Xml.XPath.XmlDocument": "4.3.0"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2549,10 +2557,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -1,10 +1,13 @@
using AutoMapper;
using System.Linq.Expressions;
using AutoMapper;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories;
public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPolicyRepository
@ -14,147 +17,160 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
{
}
private static Expression<Func<ServiceAccountProjectAccessPolicy, bool>> UserHasWriteAccessToProject(Guid userId) =>
policy =>
policy.GrantedProject.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) ||
policy.GrantedProject.GroupAccessPolicies.Any(ap =>
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write));
public async Task<List<Core.SecretsManager.Entities.BaseAccessPolicy>> CreateManyAsync(List<Core.SecretsManager.Entities.BaseAccessPolicy> baseAccessPolicies)
{
using (var scope = ServiceScopeFactory.CreateScope())
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
foreach (var baseAccessPolicy in baseAccessPolicies)
{
var dbContext = GetDatabaseContext(scope);
foreach (var baseAccessPolicy in baseAccessPolicies)
{
baseAccessPolicy.SetNewId();
switch (baseAccessPolicy)
{
case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy:
{
var entity =
Mapper.Map<UserProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
{
var entity =
Mapper.Map<UserServiceAccountAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy:
{
var entity = Mapper.Map<ServiceAccountProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
}
}
await dbContext.SaveChangesAsync();
return baseAccessPolicies;
}
}
public async Task<bool> AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
baseAccessPolicy.SetNewId();
switch (baseAccessPolicy)
{
case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.UserProjectAccessPolicy
.Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
var entity =
Mapper.Map<UserProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
{
var entity =
Mapper.Map<UserServiceAccountAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.GroupProjectAccessPolicy
.Where(c => c.GroupId == accessPolicy.GroupId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
var entity = Mapper.Map<GroupProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
{
var entity = Mapper.Map<GroupServiceAccountAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.ServiceAccountProjectAccessPolicy
.Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
var entity = Mapper.Map<ServiceAccountProjectAccessPolicy>(accessPolicy);
await dbContext.AddAsync(entity);
break;
}
default:
throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy));
}
}
await dbContext.SaveChangesAsync();
return baseAccessPolicies;
}
public async Task<bool> AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
switch (baseAccessPolicy)
{
case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.UserProjectAccessPolicy
.Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
}
case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.GroupProjectAccessPolicy
.Where(c => c.GroupId == accessPolicy.GroupId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
}
case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy:
{
var policy = await dbContext.ServiceAccountProjectAccessPolicy
.Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId &&
c.GrantedProjectId == accessPolicy.GrantedProjectId)
.FirstOrDefaultAsync();
return policy != null;
}
case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy:
{
var policy = await dbContext.UserServiceAccountAccessPolicy
.Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId &&
c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId)
.FirstOrDefaultAsync();
return policy != null;
}
case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy:
{
var policy = await dbContext.GroupServiceAccountAccessPolicy
.Where(c => c.GroupId == accessPolicy.GroupId &&
c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId)
.FirstOrDefaultAsync();
return policy != null;
}
default:
throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy));
}
}
public async Task<Core.SecretsManager.Entities.BaseAccessPolicy?> GetByIdAsync(Guid id)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id)
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupProjectAccessPolicy)ap).Group)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount)
.FirstOrDefaultAsync();
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id)
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject)
.Include(ap => ((GroupProjectAccessPolicy)ap).Group)
.Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject)
.Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount)
.Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group)
.Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount)
.FirstOrDefaultAsync();
if (entity == null)
{
return null;
}
return MapToCore(entity);
}
return entity == null ? null : MapToCore(entity);
}
public async Task ReplaceAsync(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy)
{
using (var scope = ServiceScopeFactory.CreateScope())
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id);
if (entity != null)
{
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id);
if (entity != null)
{
dbContext.AccessPolicies.Attach(entity);
entity.Write = baseAccessPolicy.Write;
entity.Read = baseAccessPolicy.Read;
entity.RevisionDate = baseAccessPolicy.RevisionDate;
await dbContext.SaveChangesAsync();
}
dbContext.AccessPolicies.Attach(entity);
entity.Write = baseAccessPolicy.Write;
entity.Read = baseAccessPolicy.Read;
entity.RevisionDate = baseAccessPolicy.RevisionDate;
await dbContext.SaveChangesAsync();
}
}
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserProjectAccessPolicy)ap).GrantedProjectId == id ||
((GroupProjectAccessPolicy)ap).GrantedProjectId == id ||
((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id)
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupProjectAccessPolicy)ap).Group)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount)
.ToListAsync();
return entities.Select(MapToCore);
}
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserProjectAccessPolicy)ap).GrantedProjectId == id ||
((GroupProjectAccessPolicy)ap).GrantedProjectId == id ||
((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id)
.Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupProjectAccessPolicy)ap).Group)
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount)
.ToListAsync();
return entities.Select(MapToCore);
}
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id)
@ -174,28 +190,51 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
public async Task DeleteAsync(Guid id)
{
using (var scope = ServiceScopeFactory.CreateScope())
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.FindAsync(id);
if (entity != null)
{
var dbContext = GetDatabaseContext(scope);
var entity = await dbContext.AccessPolicies.FindAsync(id);
if (entity != null)
{
dbContext.Remove(entity);
await dbContext.SaveChangesAsync();
}
dbContext.Remove(entity);
await dbContext.SaveChangesAsync();
}
}
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(BaseAccessPolicy baseAccessPolicyEntity)
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByServiceAccountIdAsync(Guid id, Guid userId,
AccessClientType accessType)
{
return baseAccessPolicyEntity switch
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.ServiceAccountProjectAccessPolicy.Where(ap =>
ap.ServiceAccountId == id);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var entities = await query
.Include(ap => ap.ServiceAccount)
.Include(ap => ap.GrantedProject)
.ToListAsync();
return entities.Select(MapToCore);
}
private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(
BaseAccessPolicy baseAccessPolicyEntity) =>
baseAccessPolicyEntity switch
{
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type")
ServiceAccountProjectAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap =>
Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper
.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type"),
};
}
}

View File

@ -45,6 +45,24 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> GetManyByOrganizationIdWriteAccessAsync(
Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Project.Where(p => p.OrganizationId == organizationId && p.DeletedDate == null);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var projects = await query.OrderBy(p => p.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Project>>(projects);
}
private static Expression<Func<Project, bool>> UserHasReadAccessToProject(Guid userId) => p =>
p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
p.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));
@ -56,6 +74,9 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
private static Expression<Func<Project, bool>> ServiceAccountHasReadAccessToProject(Guid serviceAccountId) => p =>
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read);
private static Expression<Func<Project, bool>> ServiceAccountHasWriteAccessToProject(Guid serviceAccountId) => p =>
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Write);
public async Task DeleteManyByIdAsync(IEnumerable<Guid> ids)
{
using (var scope = ServiceScopeFactory.CreateScope())
@ -82,6 +103,28 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
}
}
public async Task<bool> ServiceAccountHasReadAccessToProject(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Project
.Where(p => p.Id == id)
.Where(ServiceAccountHasReadAccessToProject(userId));
return await query.AnyAsync();
}
public async Task<bool> ServiceAccountHasWriteAccessToProject(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Project
.Where(p => p.Id == id)
.Where(ServiceAccountHasWriteAccessToProject(userId));
return await query.AnyAsync();
}
public async Task<bool> UserHasReadAccessToProject(Guid id, Guid userId)
{
using var scope = ServiceScopeFactory.CreateScope();
@ -103,4 +146,14 @@ public class ProjectRepository : Repository<Core.SecretsManager.Entities.Project
return await query.AnyAsync();
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Project>> ImportAsync(IEnumerable<Core.SecretsManager.Entities.Project> projects)
{
using var scope = ServiceScopeFactory.CreateScope();
var entities = projects.Select(p => Mapper.Map<Project>(p));
var dbContext = GetDatabaseContext(scope);
await GetDbSet(dbContext).AddRangeAsync(entities);
await dbContext.SaveChangesAsync();
return projects;
}
}

View File

@ -1,4 +1,6 @@
using AutoMapper;
using System.Linq.Expressions;
using AutoMapper;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Repositories;
using Bit.Infrastructure.EntityFramework;
using Bit.Infrastructure.EntityFramework.Repositories;
@ -34,18 +36,48 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
var dbContext = GetDatabaseContext(scope);
var secrets = await dbContext.Secret
.Where(c => ids.Contains(c.Id) && c.DeletedDate == null)
.Include(c => c.Projects)
.ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
}
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdAsync(Guid organizationId)
private static Expression<Func<Secret, bool>> ServiceAccountHasReadAccessToSecret(Guid serviceAccountId) => s =>
s.Projects.Any(p =>
p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read));
private static Expression<Func<Secret, bool>> UserHasReadAccessToSecret(Guid userId) => s =>
s.Projects.Any(p =>
p.UserAccessPolicies.Any(ap => ap.OrganizationUser.UserId == userId && ap.Read) ||
p.GroupAccessPolicies.Any(ap =>
ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.UserId == userId && ap.Read)));
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Secret.Include(c => c.Projects).Where(c => c.OrganizationId == organizationId && c.DeletedDate == null);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)),
AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var secrets = await query.OrderBy(c => c.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdInTrashByIdsAsync(Guid organizationId, IEnumerable<Guid> ids)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var secrets = await dbContext.Secret
.Where(c => c.OrganizationId == organizationId && c.DeletedDate == null)
.Where(s => ids.Contains(s.Id) && s.OrganizationId == organizationId && s.DeletedDate != null)
.Include("Projects")
.OrderBy(c => c.RevisionDate)
.ToListAsync();
@ -54,19 +86,42 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
}
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByProjectIdAsync(Guid projectId)
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByOrganizationIdInTrashAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var secrets = await dbContext.Secret
.Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null).Include("Projects")
.OrderBy(s => s.RevisionDate).ToListAsync();
.Where(c => c.OrganizationId == organizationId && c.DeletedDate != null)
.Include("Projects")
.OrderBy(c => c.RevisionDate)
.ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
}
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Secret.Include(s => s.Projects)
.Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)),
AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var secrets = await query.OrderBy(s => s.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.Secret>>(secrets);
}
}
public override async Task<Core.SecretsManager.Entities.Secret> CreateAsync(Core.SecretsManager.Entities.Secret secret)
{
using (var scope = ServiceScopeFactory.CreateScope())
@ -92,11 +147,11 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
public async Task<Core.SecretsManager.Entities.Secret> UpdateAsync(Core.SecretsManager.Entities.Secret secret)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var mappedEntity = Mapper.Map<Secret>(secret);
var entity = await dbContext.Secret
.Include("Projects")
.FirstAsync(s => s.Id == secret.Id);
@ -136,4 +191,65 @@ public class SecretRepository : Repository<Core.SecretsManager.Entities.Secret,
await dbContext.SaveChangesAsync();
}
}
public async Task HardDeleteManyByIdAsync(IEnumerable<Guid> ids)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id));
await secrets.ForEachAsync(secret =>
{
dbContext.Attach(secret);
dbContext.Remove(secret);
});
await dbContext.SaveChangesAsync();
}
}
public async Task RestoreManyByIdAsync(IEnumerable<Guid> ids)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id));
await secrets.ForEachAsync(secret =>
{
dbContext.Attach(secret);
secret.DeletedDate = null;
});
await dbContext.SaveChangesAsync();
}
}
public async Task<IEnumerable<Core.SecretsManager.Entities.Secret>> ImportAsync(IEnumerable<Core.SecretsManager.Entities.Secret> secrets)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var entities = new List<Secret>();
var projects = secrets
.SelectMany(s => s.Projects ?? Enumerable.Empty<Core.SecretsManager.Entities.Project>())
.DistinctBy(p => p.Id)
.Select(p => Mapper.Map<Project>(p))
.ToDictionary(p => p.Id, p => p);
dbContext.AttachRange(projects.Values);
foreach (var s in secrets)
{
var entity = Mapper.Map<Secret>(s);
if (s.Projects?.Count > 0)
{
entity.Projects = s.Projects.Select(p => projects[p.Id]).ToList();
}
entities.Add(entity);
}
await GetDbSet(dbContext).AddRangeAsync(entities);
await dbContext.SaveChangesAsync();
}
return secrets;
}
}

View File

@ -54,6 +54,23 @@ public class ServiceAccountRepository : Repository<Core.SecretsManager.Entities.
return await query.AnyAsync();
}
public async Task<IEnumerable<Core.SecretsManager.Entities.ServiceAccount>> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var query = dbContext.ServiceAccount.Where(c => c.OrganizationId == organizationId);
query = accessType switch
{
AccessClientType.NoAccessCheck => query,
AccessClientType.User => query.Where(UserHasWriteAccessToServiceAccount(userId)),
_ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null),
};
var serviceAccounts = await query.OrderBy(c => c.RevisionDate).ToListAsync();
return Mapper.Map<List<Core.SecretsManager.Entities.ServiceAccount>>(serviceAccounts);
}
private static Expression<Func<ServiceAccount, bool>> UserHasReadAccessToServiceAccount(Guid userId) => sa =>
sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) ||
sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read));

View File

@ -63,8 +63,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -101,28 +101,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -144,6 +144,14 @@
"System.Xml.XPath.XmlDocument": "4.3.0"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2722,10 +2730,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -97,11 +97,11 @@ public class UsersController : Controller
if (model.Active && orgUser.Status == OrganizationUserStatusType.Revoked)
{
await _organizationService.RestoreUserAsync(orgUser, null, _userService);
await _organizationService.RestoreUserAsync(orgUser, EventSystemUser.SCIM, _userService);
}
else if (!model.Active && orgUser.Status != OrganizationUserStatusType.Revoked)
{
await _organizationService.RevokeUserAsync(orgUser, null);
await _organizationService.RevokeUserAsync(orgUser, EventSystemUser.SCIM);
}
// Have to get full details object for response model

View File

@ -71,8 +71,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -109,28 +109,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -157,6 +157,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2998,10 +3006,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -483,7 +483,7 @@ public class AccountController : Controller
// Before any user creation - if Org User doesn't exist at this point - make sure there are enough seats to add one
if (orgUser == null && organization.Seats.HasValue)
{
var occupiedSeats = await _organizationService.GetOccupiedSeatCount(organization);
var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
var initialSeatCount = organization.Seats.Value;
var availableSeats = initialSeatCount - occupiedSeats;
var prorationDate = DateTime.UtcNow;

View File

@ -74,8 +74,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -112,28 +112,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -160,6 +160,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2868,10 +2876,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -1,7 +1,10 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
@ -11,21 +14,233 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize]
[ProjectCustomize]
public class CreateAccessPoliciesCommandTests
{
[Theory]
[BitAutoData]
public async Task CreateAsync_CallsCreate(List<UserProjectAccessPolicy> userProjectAccessPolicies,
private static List<BaseAccessPolicy> MakeGrantedProjectAccessPolicies(Guid grantedProjectId, List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies)
{
var data = new List<BaseAccessPolicy>();
foreach (var ap in userProjectAccessPolicies)
{
ap.GrantedProjectId = grantedProjectId;
}
foreach (var ap in groupProjectAccessPolicies)
{
ap.GrantedProjectId = grantedProjectId;
}
foreach (var ap in serviceAccountProjectAccessPolicies)
{
ap.GrantedProjectId = grantedProjectId;
}
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
return data;
}
await sutProvider.Sut.CreateAsync(data);
private static List<BaseAccessPolicy> MakeGrantedServiceAccountAccessPolicies(Guid grantedServiceAccountId, List<UserServiceAccountAccessPolicy> userServiceAccountAccessPolicies,
List<GroupServiceAccountAccessPolicy> groupServiceAccountAccessPolicies)
{
var data = new List<BaseAccessPolicy>();
foreach (var ap in userServiceAccountAccessPolicies)
{
ap.GrantedServiceAccountId = grantedServiceAccountId;
}
foreach (var ap in groupServiceAccountAccessPolicies)
{
ap.GrantedServiceAccountId = grantedServiceAccountId;
}
data.AddRange(userServiceAccountAccessPolicies);
data.AddRange(groupServiceAccountAccessPolicies);
return data;
}
private static List<BaseAccessPolicy> MakeDuplicate(List<BaseAccessPolicy> data, AccessPolicyType accessPolicyType)
{
switch (accessPolicyType)
{
case AccessPolicyType.UserProjectAccessPolicy:
{
var mockAccessPolicy = new UserProjectAccessPolicy
{
OrganizationUserId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid(),
};
data.Add(mockAccessPolicy);
// Add a duplicate policy
data.Add(mockAccessPolicy);
break;
}
case AccessPolicyType.GroupProjectAccessPolicy:
{
var mockAccessPolicy = new GroupProjectAccessPolicy
{
GroupId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid(),
};
data.Add(mockAccessPolicy);
// Add a duplicate policy
data.Add(mockAccessPolicy);
break;
}
case AccessPolicyType.ServiceAccountProjectAccessPolicy:
{
var mockAccessPolicy = new ServiceAccountProjectAccessPolicy
{
ServiceAccountId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid(),
};
data.Add(mockAccessPolicy);
// Add a duplicate policy
data.Add(mockAccessPolicy);
break;
}
case AccessPolicyType.UserServiceAccountAccessPolicy:
{
var mockAccessPolicy = new UserServiceAccountAccessPolicy
{
OrganizationUserId = Guid.NewGuid(),
GrantedServiceAccountId = Guid.NewGuid(),
};
data.Add(mockAccessPolicy);
// Add a duplicate policy
data.Add(mockAccessPolicy);
break;
}
case AccessPolicyType.GroupServiceAccountAccessPolicy:
{
var mockAccessPolicy = new GroupServiceAccountAccessPolicy
{
GroupId = Guid.NewGuid(),
GrantedServiceAccountId = Guid.NewGuid(),
};
data.Add(mockAccessPolicy);
// Add a duplicate policy
data.Add(mockAccessPolicy);
break;
}
}
return data;
}
private static void SetupPermission(SutProvider<CreateAccessPoliciesCommand> sutProvider,
PermissionType permissionType, Project project, Guid userId)
{
if (permissionType == PermissionType.RunAsUserWithPermission)
{
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId)
.Returns(true);
}
}
private static void SetupPermission(SutProvider<CreateAccessPoliciesCommand> sutProvider,
PermissionType permissionType, ServiceAccount serviceAccount, Guid userId)
{
if (permissionType == PermissionType.RunAsUserWithPermission)
{
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true);
}
}
[Theory]
[BitAutoData]
public async Task CreateMany_AlreadyExists_Throws_BadRequestException(
Guid userId,
Project project,
ServiceAccount serviceAccount,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
List<UserServiceAccountAccessPolicy> userServiceAccountAccessPolicies,
List<GroupServiceAccountAccessPolicy> groupServiceAccountAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies,
serviceAccountProjectAccessPolicies);
var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies);
data = data.Concat(saData).ToList();
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>())
.Returns(true);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default!);
}
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)]
public async Task CreateMany_NotUnique_ThrowsException(
AccessPolicyType accessPolicyType,
Guid userId,
Project project,
ServiceAccount serviceAccount,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
List<UserServiceAccountAccessPolicy> userServiceAccountAccessPolicies,
List<GroupServiceAccountAccessPolicy> groupServiceAccountAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider
)
{
var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies,
serviceAccountProjectAccessPolicies);
var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies);
data = data.Concat(saData).ToList();
data = MakeDuplicate(data, accessPolicyType);
await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
[Theory]
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task CreateMany_Success(
PermissionType permissionType,
Guid userId,
Project project,
ServiceAccount serviceAccount,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
List<UserServiceAccountAccessPolicy> userServiceAccountAccessPolicies,
List<GroupServiceAccountAccessPolicy> groupServiceAccountAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies,
serviceAccountProjectAccessPolicies);
var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies);
data = data.Concat(saData).ToList();
SetupPermission(sutProvider, permissionType, serviceAccount, userId);
SetupPermission(sutProvider, permissionType, project, userId);
if (permissionType == PermissionType.RunAsAdmin)
{
await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck);
}
else if (permissionType == PermissionType.RunAsUserWithPermission)
{
await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User);
}
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
@ -33,94 +248,26 @@ public class CreateAccessPoliciesCommandTests
[Theory]
[BitAutoData]
public async Task CreateAsync_AlreadyExists_Throws_BadRequestException(
public async Task CreateMany_UserWithoutPermission_Throws(
Guid userId,
Project project,
ServiceAccount serviceAccount,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
List<UserServiceAccountAccessPolicy> userServiceAccountAccessPolicies,
List<GroupServiceAccountAccessPolicy> groupServiceAccountAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies,
serviceAccountProjectAccessPolicies);
var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies);
data = data.Concat(saData).ToList();
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>())
.Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User));
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
}
[Theory]
[BitAutoData(true, false, false)]
[BitAutoData(false, true, false)]
[BitAutoData(true, true, false)]
[BitAutoData(false, false, true)]
[BitAutoData(true, false, true)]
[BitAutoData(false, true, true)]
[BitAutoData(true, true, true)]
public async Task CreateAsync_NotUnique_ThrowsException(
bool testUserPolicies,
bool testGroupPolicies,
bool testServiceAccountPolicies,
List<UserProjectAccessPolicy> userProjectAccessPolicies,
List<GroupProjectAccessPolicy> groupProjectAccessPolicies,
List<ServiceAccountProjectAccessPolicy> serviceAccountProjectAccessPolicies,
SutProvider<CreateAccessPoliciesCommand> sutProvider
)
{
var data = new List<BaseAccessPolicy>();
data.AddRange(userProjectAccessPolicies);
data.AddRange(groupProjectAccessPolicies);
data.AddRange(serviceAccountProjectAccessPolicies);
if (testUserPolicies)
{
var mockUserPolicy = new UserProjectAccessPolicy
{
OrganizationUserId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid()
};
data.Add(mockUserPolicy);
// Add a duplicate policy
data.Add(mockUserPolicy);
}
if (testGroupPolicies)
{
var mockGroupPolicy = new GroupProjectAccessPolicy
{
GroupId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid()
};
data.Add(mockGroupPolicy);
// Add a duplicate policy
data.Add(mockGroupPolicy);
}
if (testServiceAccountPolicies)
{
var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy
{
ServiceAccountId = Guid.NewGuid(),
GrantedProjectId = Guid.NewGuid()
};
data.Add(mockServiceAccountPolicy);
// Add a duplicate policy
data.Add(mockServiceAccountPolicy);
}
sutProvider.GetDependency<IAccessPolicyRepository>().AccessPolicyExists(Arg.Any<BaseAccessPolicy>())
.Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.CreateAsync(data));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().CreateManyAsync(default);
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs()
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
}

View File

@ -1,7 +1,11 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
@ -11,28 +15,216 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize]
[ProjectCustomize]
public class DeleteAccessPolicyCommandTests
{
private static void SetupPermission(SutProvider<DeleteAccessPolicyCommand> sutProvider,
PermissionType permissionType, Project grantedProject, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedProject.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(grantedProject.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static void SetupPermission(SutProvider<DeleteAccessPolicyCommand> sutProvider,
PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedServiceAccount.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) =>
accessPolicyType switch
{
AccessPolicyType.UserProjectAccessPolicy => new UserProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
GrantedProject = grantedProject,
},
AccessPolicyType.GroupProjectAccessPolicy => new GroupProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Group = mockGroup,
GrantedProject = grantedProject,
},
AccessPolicyType.ServiceAccountProjectAccessPolicy => new ServiceAccountProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
ServiceAccount = mockServiceAccount,
GrantedProject = grantedProject,
},
_ => null,
};
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
ServiceAccount grantedServiceAccount, Group mockGroup) =>
accessPolicyType switch
{
AccessPolicyType.UserServiceAccountAccessPolicy => new UserServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
},
AccessPolicyType.GroupServiceAccountAccessPolicy => new GroupServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
Group = mockGroup,
GrantedServiceAccount = grantedServiceAccount,
},
_ => null,
};
[Theory]
[BitAutoData]
public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data,
public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, Guid userId,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull();
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data));
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
[Theory]
[BitAutoData]
public async Task DeleteAccessPolicy_Success(Guid data,
public async Task DeleteAccessPolicy_SmNotEnabled_Throws_NotFoundException(Guid data, Guid userId,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(new UserProjectAccessPolicy { Id = data });
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(false);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).ReturnsNull();
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
await sutProvider.Sut.DeleteAsync(data);
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy_ProjectGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
SetupPermission(sutProvider, permissionType, grantedProject, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await sutProvider.Sut.DeleteAsync(data, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data));
}
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)]
public async Task DeleteAccessPolicy_UserProjectAccessPolicy_PermissionsCheck_ThrowsNotAuthorized(
AccessPolicyType accessPolicyType,
Guid data,
Guid userId,
Group mockGroup,
ServiceAccount mockServiceAccount,
Project grantedProject,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup);
SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await sutProvider.Sut.DeleteAsync(data, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).DeleteAsync(Arg.Is(data));
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)]
public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<DeleteAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data)
.Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.DeleteAsync(data, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().DeleteAsync(default);
}
}

View File

@ -1,7 +1,11 @@
using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
@ -11,30 +15,246 @@ using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies;
[SutProviderCustomize]
[ProjectCustomize]
public class UpdateAccessPolicyCommandTests
{
private static void SetupPermission(SutProvider<UpdateAccessPolicyCommand> sutProvider,
PermissionType permissionType, Project grantedProject, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedProject.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(grantedProject.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static void SetupPermission(SutProvider<UpdateAccessPolicyCommand> sutProvider,
PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId)
{
switch (permissionType)
{
case PermissionType.RunAsAdmin:
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(grantedServiceAccount.OrganizationId)
.Returns(true);
break;
case PermissionType.RunAsUserWithPermission:
sutProvider.GetDependency<IServiceAccountRepository>()
.UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId)
.Returns(true);
break;
default:
throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType,
ServiceAccount grantedServiceAccount, Guid data, Group mockGroup)
{
switch (accessPolicyType)
{
case AccessPolicyType.UserServiceAccountAccessPolicy:
return
new UserServiceAccountAccessPolicy
{
Id = data,
Read = true,
Write = true,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
};
case AccessPolicyType.GroupServiceAccountAccessPolicy:
mockGroup.OrganizationId = grantedServiceAccount.OrganizationId;
return new GroupServiceAccountAccessPolicy
{
Id = data,
GrantedServiceAccountId = grantedServiceAccount.Id,
GrantedServiceAccount = grantedServiceAccount,
Read = true,
Write = true,
Group = mockGroup,
};
default:
throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null);
}
}
private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data,
Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount)
{
switch (accessPolicyType)
{
case AccessPolicyType.UserProjectAccessPolicy:
return
new UserProjectAccessPolicy
{
Id = data,
Read = true,
Write = true,
GrantedProjectId = grantedProject.Id,
GrantedProject = grantedProject,
};
case AccessPolicyType.GroupProjectAccessPolicy:
mockGroup.OrganizationId = grantedProject.OrganizationId;
return
new GroupProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Read = true,
Write = true,
Group = mockGroup,
GrantedProject = grantedProject,
};
case AccessPolicyType.ServiceAccountProjectAccessPolicy:
mockServiceAccount.OrganizationId = grantedProject.OrganizationId;
return new ServiceAccountProjectAccessPolicy
{
Id = data,
GrantedProjectId = grantedProject.Id,
Read = true,
Write = true,
ServiceAccount = mockServiceAccount,
GrantedProject = grantedProject,
};
default:
throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null);
}
}
[Theory]
[BitAutoData]
public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write,
public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
var exception =
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write));
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory]
[BitAutoData]
public async Task UpdateAsync_Calls_Replace(Guid data, bool read, bool write,
public async Task UpdateAsync_SmNotEnabled_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
var existingPolicy = new UserProjectAccessPolicy { Id = data, Read = true, Write = true };
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(existingPolicy);
var result = await sutProvider.Sut.UpdateAsync(data, read, write);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(existingPolicy);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
bool read,
bool write,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
SetupPermission(sutProvider, permissionType, grantedProject, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(policyToReturn);
AssertHelper.AssertRecent(result.RevisionDate);
Assert.Equal(read, result.Read);
Assert.Equal(write, result.Write);
}
[Theory]
[BitAutoData(AccessPolicyType.UserProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)]
[BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)]
public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
bool read,
bool write,
Guid userId,
Project grantedProject,
Group mockGroup,
ServiceAccount mockServiceAccount,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn =
CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Success(
AccessPolicyType accessPolicyType,
PermissionType permissionType,
Guid data,
bool read,
bool write,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup);
SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId);
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1).ReplaceAsync(policyToReturn);
AssertHelper.AssertRecent(result.RevisionDate);
Assert.Equal(read, result.Read);
Assert.Equal(write, result.Write);
}
[Theory]
[BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)]
[BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)]
public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Throws(
AccessPolicyType accessPolicyType,
Guid data,
bool read,
bool write,
Guid userId,
ServiceAccount grantedServiceAccount,
Group mockGroup,
SutProvider<UpdateAccessPolicyCommand> sutProvider)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(Arg.Any<Guid>()).Returns(true);
var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup);
sutProvider.GetDependency<IAccessPolicyRepository>().GetByIdAsync(data).Returns(policyToReturn);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.UpdateAsync(data, read, write, userId));
await sutProvider.GetDependency<IAccessPolicyRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
}
}

View File

@ -0,0 +1,11 @@
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
public enum AccessPolicyType
{
UserProjectAccessPolicy,
GroupProjectAccessPolicy,
ServiceAccountProjectAccessPolicy,
UserServiceAccountAccessPolicy,
GroupServiceAccountAccessPolicy,
}

View File

@ -0,0 +1,7 @@
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
public enum PermissionType
{
RunAsAdmin,
RunAsUserWithPermission,
}

View File

@ -0,0 +1,40 @@
using Bit.Commercial.Core.SecretsManager.Commands.Projects;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.Projects;
[SutProviderCustomize]
[ProjectCustomize]
public class CreateProjectCommandTests
{
[Theory]
[BitAutoData]
public async Task CreateAsync_CallsCreate(Project data,
Guid userId,
SutProvider<CreateProjectCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(Arg.Any<Guid>(), Arg.Any<Guid>())
.Returns(new OrganizationUser() { Id = userId });
sutProvider.GetDependency<IProjectRepository>()
.CreateAsync(Arg.Any<Project>())
.Returns(data);
await sutProvider.Sut.CreateAsync(data, userId);
await sutProvider.GetDependency<IProjectRepository>().Received(1)
.CreateAsync(Arg.Is(data));
await sutProvider.GetDependency<IAccessPolicyRepository>().Received(1)
.CreateManyAsync(Arg.Any<List<BaseAccessPolicy>>());
}
}

View File

@ -1,4 +1,7 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
@ -14,14 +17,54 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
public class CreateSecretCommandTests
{
[Theory]
[BitAutoData]
public async Task CreateAsync_CallsCreate(Secret data,
SutProvider<CreateSecretCommand> sutProvider)
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task CreateAsync_Success(PermissionType permissionType, Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
{
await sutProvider.Sut.CreateAsync(data);
data.Projects = new List<Project>() { mockProject };
if (permissionType == PermissionType.RunAsAdmin)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
}
else
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true);
}
await sutProvider.Sut.CreateAsync(data, userId);
await sutProvider.GetDependency<ISecretRepository>().Received(1)
.CreateAsync(data);
}
[Theory]
[BitAutoData]
public async Task CreateAsync_UserWithoutPermission_ThrowsNotFound(Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId, Project mockProject)
{
data.Projects = new List<Project>() { mockProject };
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateAsync(data, userId));
}
[Theory]
[BitAutoData]
public async Task CreateAsync_NoProjects_User_ThrowsNotFound(Secret data,
SutProvider<CreateSecretCommand> sutProvider, Guid userId)
{
data.Projects = null;
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() =>
sutProvider.Sut.CreateAsync(data, userId));
}
}

View File

@ -1,16 +1,20 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
[SutProviderCustomize]
[ProjectCustomize]
public class DeleteSecretCommandTests
{
[Theory]
@ -20,7 +24,7 @@ public class DeleteSecretCommandTests
{
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(new List<Secret>());
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data));
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data, default));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default);
}
@ -36,22 +40,39 @@ public class DeleteSecretCommandTests
};
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(new List<Secret>() { secret });
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data));
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteSecrets(data, default));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default);
}
[Theory]
[BitAutoData]
public async Task DeleteSecrets_Success(List<Guid> data,
SutProvider<DeleteSecretCommand> sutProvider)
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task DeleteSecrets_Success(PermissionType permissionType, List<Guid> data,
SutProvider<DeleteSecretCommand> sutProvider, Guid userId, Guid organizationId, Project mockProject)
{
List<Project> projects = null;
if (permissionType == PermissionType.RunAsAdmin)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
}
else
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(mockProject.Id, userId).Returns(true);
projects = new List<Project>() { mockProject };
}
var secrets = new List<Secret>();
foreach (Guid id in data)
{
var secret = new Secret()
{
Id = id
Id = id,
OrganizationId = organizationId,
Projects = projects
};
secrets.Add(secret);
}
@ -59,9 +80,9 @@ public class DeleteSecretCommandTests
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(secrets);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
var results = await sutProvider.Sut.DeleteSecrets(data);
var results = await sutProvider.Sut.DeleteSecrets(data, userId);
await sutProvider.GetDependency<ISecretRepository>().Received(1).SoftDeleteManyByIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));
await sutProvider.GetDependency<ISecretRepository>().Received(1).SoftDeleteManyByIdAsync(Arg.Is(data));
foreach (var result in results)
{
Assert.Equal("", result.Item2);

View File

@ -1,7 +1,10 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
@ -13,23 +16,38 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets;
[SutProviderCustomize]
[SecretCustomize]
[ProjectCustomize]
public class UpdateSecretCommandTests
{
[Theory]
[BitAutoData]
public async Task UpdateAsync_SecretDoesNotExist_ThrowsNotFound(Secret data, SutProvider<UpdateSecretCommand> sutProvider)
{
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data));
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, default));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().UpdateAsync(default);
}
[Theory]
[BitAutoData]
public async Task UpdateAsync_CallsReplaceAsync(Secret data, SutProvider<UpdateSecretCommand> sutProvider)
[BitAutoData(PermissionType.RunAsAdmin)]
[BitAutoData(PermissionType.RunAsUserWithPermission)]
public async Task UpdateAsync_Success(PermissionType permissionType, Secret data, SutProvider<UpdateSecretCommand> sutProvider, Guid userId, Project mockProject)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
if (permissionType == PermissionType.RunAsAdmin)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
}
else
{
data.Projects = new List<Project>() { mockProject };
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(false);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true);
}
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(data.Id).Returns(data);
await sutProvider.Sut.UpdateAsync(data);
await sutProvider.Sut.UpdateAsync(data, userId);
await sutProvider.GetDependency<ISecretRepository>().Received(1)
.UpdateAsync(data);
@ -37,11 +55,14 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
{
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
var updatedOrgId = Guid.NewGuid();
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(updatedOrgId).Returns(true);
var secretUpdate = new Secret()
{
OrganizationId = updatedOrgId,
@ -49,7 +70,7 @@ public class UpdateSecretCommandTests
Key = existingSecret.Key,
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
Assert.Equal(existingSecret.OrganizationId, result.OrganizationId);
Assert.NotEqual(existingSecret.OrganizationId, updatedOrgId);
@ -57,9 +78,11 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedCreationDate = DateTime.UtcNow;
var secretUpdate = new Secret()
@ -67,9 +90,10 @@ public class UpdateSecretCommandTests
CreationDate = updatedCreationDate,
Id = existingSecret.Id,
Key = existingSecret.Key,
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
Assert.Equal(existingSecret.CreationDate, result.CreationDate);
Assert.NotEqual(existingSecret.CreationDate, updatedCreationDate);
@ -77,9 +101,11 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedDeletionDate = DateTime.UtcNow;
var secretUpdate = new Secret()
@ -87,9 +113,10 @@ public class UpdateSecretCommandTests
DeletedDate = updatedDeletionDate,
Id = existingSecret.Id,
Key = existingSecret.Key,
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
Assert.Equal(existingSecret.DeletedDate, result.DeletedDate);
Assert.NotEqual(existingSecret.DeletedDate, updatedDeletionDate);
@ -98,9 +125,12 @@ public class UpdateSecretCommandTests
[Theory]
[BitAutoData]
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider)
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider<UpdateSecretCommand> sutProvider, Guid userId)
{
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingSecret.OrganizationId).Returns(true);
sutProvider.GetDependency<ISecretRepository>().GetByIdAsync(existingSecret.Id).Returns(existingSecret);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(existingSecret.OrganizationId).Returns(true);
var updatedRevisionDate = DateTime.UtcNow.AddDays(10);
var secretUpdate = new Secret()
@ -108,11 +138,12 @@ public class UpdateSecretCommandTests
RevisionDate = updatedRevisionDate,
Id = existingSecret.Id,
Key = existingSecret.Key,
OrganizationId = existingSecret.OrganizationId
};
var result = await sutProvider.Sut.UpdateAsync(secretUpdate);
var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId);
Assert.NotEqual(existingSecret.RevisionDate, result.RevisionDate);
Assert.NotEqual(secretUpdate.RevisionDate, result.RevisionDate);
AssertHelper.AssertRecent(result.RevisionDate);
}
}

View File

@ -0,0 +1,39 @@
using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts;
[SutProviderCustomize]
public class RevokeAccessTokenCommandTests
{
[Theory]
[BitAutoData]
public async Task RevokeAsyncAsync_Success(ServiceAccount serviceAccount, SutProvider<RevokeAccessTokensCommand> sutProvider)
{
var apiKey1 = new ApiKey
{
Id = Guid.NewGuid(),
ServiceAccountId = serviceAccount.Id
};
var apiKey2 = new ApiKey
{
Id = Guid.NewGuid(),
ServiceAccountId = serviceAccount.Id
};
sutProvider.GetDependency<IApiKeyRepository>()
.GetManyByServiceAccountIdAsync(serviceAccount.Id)
.Returns(new List<ApiKey> { apiKey1, apiKey2 });
await sutProvider.Sut.RevokeAsync(serviceAccount, new List<Guid> { apiKey1.Id });
await sutProvider.GetDependency<IApiKeyRepository>().Received(1)
.DeleteManyAsync(Arg.Is<IEnumerable<ApiKey>>(arg => arg.SequenceEqual(new List<ApiKey> { apiKey1 })));
}
}

View File

@ -0,0 +1,48 @@
using Bit.Commercial.Core.SecretsManager.Commands.Trash;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
[SutProviderCustomize]
[ProjectCustomize]
public class EmptyTrashCommandTests
{
[Theory]
[BitAutoData]
public async Task EmptyTrash_Throws_NotFoundException(Guid orgId, Secret s1, Secret s2, SutProvider<EmptyTrashCommand> sutProvider)
{
s1.DeletedDate = DateTime.Now;
var ids = new List<Guid> { s1.Id, s2.Id };
sutProvider.GetDependency<ISecretRepository>()
.GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids)
.Returns(new List<Secret> { s1 });
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.EmptyTrash(orgId, ids));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().RestoreManyByIdAsync(default);
}
[Theory]
[BitAutoData]
public async Task EmptyTrash_Success(Guid orgId, Secret s1, Secret s2, SutProvider<EmptyTrashCommand> sutProvider)
{
s1.DeletedDate = DateTime.Now;
var ids = new List<Guid> { s1.Id, s2.Id };
sutProvider.GetDependency<ISecretRepository>()
.GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids)
.Returns(new List<Secret> { s1, s2 });
await sutProvider.Sut.EmptyTrash(orgId, ids);
await sutProvider.GetDependency<ISecretRepository>().Received(1).HardDeleteManyByIdAsync(ids);
}
}

View File

@ -0,0 +1,48 @@
using Bit.Commercial.Core.SecretsManager.Commands.Trash;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Commercial.Core.Test.SecretsManager.Trash;
[SutProviderCustomize]
[ProjectCustomize]
public class RestoreTrashCommandTests
{
[Theory]
[BitAutoData]
public async Task RestoreTrash_Throws_NotFoundException(Guid orgId, Secret s1, Secret s2, SutProvider<RestoreTrashCommand> sutProvider)
{
s1.DeletedDate = DateTime.Now;
var ids = new List<Guid> { s1.Id, s2.Id };
sutProvider.GetDependency<ISecretRepository>()
.GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids)
.Returns(new List<Secret> { s1 });
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RestoreTrash(orgId, ids));
await sutProvider.GetDependency<ISecretRepository>().DidNotReceiveWithAnyArgs().RestoreManyByIdAsync(default);
}
[Theory]
[BitAutoData]
public async Task RestoreTrash_Success(Guid orgId, Secret s1, Secret s2, SutProvider<RestoreTrashCommand> sutProvider)
{
s1.DeletedDate = DateTime.Now;
var ids = new List<Guid> { s1.Id, s2.Id };
sutProvider.GetDependency<ISecretRepository>()
.GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids)
.Returns(new List<Secret> { s1, s2 });
await sutProvider.Sut.RestoreTrash(orgId, ids);
await sutProvider.GetDependency<ISecretRepository>().Received(1).RestoreManyByIdAsync(ids);
}
}

View File

@ -105,8 +105,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -143,28 +143,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -203,6 +203,14 @@
"System.Xml.XmlDocument": "4.3.0"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fare": {
"type": "Transitive",
"resolved": "2.1.1",
@ -2818,10 +2826,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -143,8 +143,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -181,28 +181,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -246,6 +246,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fare": {
"type": "Transitive",
"resolved": "2.1.1",
@ -3402,10 +3410,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -131,8 +131,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -169,28 +169,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -234,6 +234,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fare": {
"type": "Transitive",
"resolved": "2.1.1",
@ -3247,10 +3255,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -4,6 +4,13 @@ param (
$Name
)
# DB service provider name
$service = "mysql"
Write-Output "--- Attempting to start $service service ---"
docker-compose --profile $service up -d --no-recreate
dotnet tool restore
$providers = @{

View File

@ -1,3 +1,4 @@
# syntax = docker/dockerfile:1.2
###############################################
# Build stage #
###############################################
@ -13,7 +14,12 @@ RUN apt-get update && apt-get install -y \
WORKDIR /tmp
# Download tags from 'clients' repository
RUN curl https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json
RUN --mount=type=secret,id=GH_PAT,target=/etc/secrets/GH_PAT if [ -e "/etc/secrets/GH_PAT" ]; then \
curl --header "Authorization: token $(cat /etc/secrets/GH_PAT)" \
https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; else \
curl https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; fi
RUN cat tags.json
# Grab last tag/release of the 'web' client
RUN cat tags.json | jq -r 'last(.[] | select(.ref|test("refs/tags/web-v[0-9]{4}.[0-9]{1,2}.[0-9]+"))) | .ref | split("/")[2]' > tag.txt

View File

@ -65,8 +65,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -103,28 +103,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -156,6 +156,14 @@
"resolved": "2.4.3",
"contentHash": "U2FC9Y8NyIxxU6MpFFdWWu1xwiqz/61v/Doou7kmVjpeIEMLWyiNNkzNlSE84kyJ0O1LKApuEj5z48Ow0Hi4OQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2656,10 +2664,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -0,0 +1,31 @@
using Bit.Core;
using Bit.Core.Jobs;
using Bit.Core.Services;
using Quartz;
namespace Bit.Admin.Jobs;
public class DeleteUnverifiedOrganizationDomainsJob : BaseJob
{
private readonly IServiceProvider _serviceProvider;
public DeleteUnverifiedOrganizationDomainsJob(
IServiceProvider serviceProvider,
ILogger<DeleteUnverifiedOrganizationDomainsJob> logger)
: base(logger)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: Start");
using (var serviceScope = _serviceProvider.CreateScope())
{
var organizationDomainService =
serviceScope.ServiceProvider.GetRequiredService<IOrganizationDomainService>();
await organizationDomainService.OrganizationDomainMaintenanceAsync();
}
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: End");
}
}

View File

@ -64,6 +64,11 @@ public class JobsHostedService : BaseJobsHostedService
.StartNow()
.WithCronSchedule("0 */15 * ? * *")
.Build();
var everyDayAtTwoAmUtcTrigger = TriggerBuilder.Create()
.WithIdentity("EveryDayAtTwoAmUtcTrigger")
.StartNow()
.WithCronSchedule("0 0 2 ? * * *")
.Build();
var jobs = new List<Tuple<Type, ITrigger>>
{
@ -74,6 +79,7 @@ public class JobsHostedService : BaseJobsHostedService
new Tuple<Type, ITrigger>(typeof(DeleteCiphersJob), everyDayAtMidnightUtc),
new Tuple<Type, ITrigger>(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger),
new Tuple<Type, ITrigger>(typeof(DeleteAuthRequestsJob), everyFifteenMinutesTrigger),
new Tuple<Type, ITrigger>(typeof(DeleteUnverifiedOrganizationDomainsJob), everyDayAtTwoAmUtcTrigger),
};
if (!_globalSettings.SelfHosted)
@ -98,5 +104,6 @@ public class JobsHostedService : BaseJobsHostedService
services.AddTransient<DeleteSendsJob>();
services.AddTransient<DeleteCiphersJob>();
services.AddTransient<DeleteAuthRequestsJob>();
services.AddTransient<DeleteUnverifiedOrganizationDomainsJob>();
}
}

View File

@ -20,7 +20,7 @@ public class OrganizationViewModel
UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited);
UserAcceptedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Accepted);
UserConfirmedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Confirmed);
OccupiedSeatCount = orgUsers.Count(u => u.OccupiesOrganizationSeat);
OccupiedSeatCount = UserInvitedCount + UserAcceptedCount + UserConfirmedCount;
CipherCount = ciphers.Count();
CollectionCount = collections.Count();
GroupCount = groups?.Count() ?? 0;

View File

@ -94,8 +94,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -132,28 +132,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -182,21 +182,29 @@
},
"dbup-core": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "CR00QMAtHjfeMhwxFC5haoA0q4KZ5s6Y/AdZaT6oFjySik2eFEqVasuLgWSPKSiR7ti3z01BtiR7aD3nVckAsg==",
"resolved": "5.0.8",
"contentHash": "d+3RxJDftcarp1Y7jI78HRdRWRC7VFjM+rB2CFHWDmao6OixuLrqiyEo1DeuMNrWLTR5mmE8p1YTpFOvozI9ZQ==",
"dependencies": {
"Microsoft.CSharp": "4.4.0",
"Microsoft.CSharp": "4.7.0",
"System.Diagnostics.TraceSource": "4.3.0"
}
},
"dbup-sqlserver": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "/4hy4qmbWmtbLJGq8XCH3mtlgMld2G8rbXcjNDhqkq5y6dGZDW03OI4UsnQRxBiTQD5aYOcLuycK1dCJYhkdSw==",
"resolved": "5.0.8",
"contentHash": "b954l5Zgj9qgHtm16SLq2qGLJ0gIZtrWdh6JHoUsCLMHYW+0K2Oevabquw447At4U6X2t4CNuy7ZLHYf/Z/8yg==",
"dependencies": {
"Microsoft.Azure.Services.AppAuthentication": "1.3.1",
"System.Data.SqlClient": "4.6.0",
"dbup-core": "4.5.0"
"Microsoft.Azure.Services.AppAuthentication": "1.6.2",
"Microsoft.Data.SqlClient": "5.0.1",
"dbup-core": "5.0.8"
}
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
@ -494,10 +502,10 @@
},
"Microsoft.Azure.Services.AppAuthentication": {
"type": "Transitive",
"resolved": "1.3.1",
"contentHash": "59CEcmUSlg5nYOzcyhdoUu+EQH4wrjCKj7dNuuPMeIjDCikAON9/KQXTQLfzfWTjDwqHIRptAAj0DTBp25lFcg==",
"resolved": "1.6.2",
"contentHash": "rSQhTv43ionr9rWvE4vxIe/i73XR5hoBYfh7UUgdaVOGW1MZeikR9RmgaJhonTylimCcCuJvrU0zXsSIFOsTGw==",
"dependencies": {
"Microsoft.IdentityModel.Clients.ActiveDirectory": "4.3.0",
"Microsoft.IdentityModel.Clients.ActiveDirectory": "5.2.9",
"System.Diagnostics.Process": "4.3.0"
}
},
@ -1081,15 +1089,22 @@
},
"Microsoft.IdentityModel.Clients.ActiveDirectory": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "IRXnTCHxwnpnGBHVnTWd8RBJk7nsBsNZVl8j20kh234bP+oBILkt+6Iw5vQg5Q+sZmALt3Oq6X3Kx7qY71XyVw==",
"resolved": "5.2.9",
"contentHash": "WhBAG/9hWiMHIXve4ZgwXP3spRwf7kFFfejf76QA5BvumgnPp8iDkDCiJugzAcpW1YaHB526z1UVxHhVT1E5qw==",
"dependencies": {
"Microsoft.CSharp": "4.3.0",
"NETStandard.Library": "1.6.1",
"System.ComponentModel.TypeConverter": "4.3.0",
"System.Dynamic.Runtime": "4.3.0",
"System.Net.Http": "4.3.4",
"System.Private.Uri": "4.3.2",
"System.Runtime.Serialization.Formatters": "4.3.0",
"System.Runtime.Serialization.Json": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0",
"System.Security.Cryptography.X509Certificates": "4.3.0",
"System.Security.SecureString": "4.3.0",
"System.Xml.XDocument": "4.3.0"
"System.Xml.XDocument": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
}
},
"Microsoft.IdentityModel.JsonWebTokens": {
@ -1513,16 +1528,6 @@
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "AJfX7owAAkMjWQYhoml5IBfXh8UyYPjktn8pK0BFGAdKgBS7HqMz1fw5vdzfZUWfhtTPDGCjgNttt46ZyEmSjg==",
"dependencies": {
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
}
},
"runtime.native.System.IO.Compression": {
"type": "Transitive",
"resolved": "4.3.0",
@ -1615,21 +1620,6 @@
"resolved": "4.3.2",
"contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg=="
},
"runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
},
"runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
},
"runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
},
"SendGrid": {
"type": "Transitive",
"resolved": "9.27.0",
@ -1904,29 +1894,69 @@
},
"System.Collections.NonGeneric": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==",
"resolved": "4.3.0",
"contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==",
"dependencies": {
"System.Diagnostics.Debug": "4.0.11",
"System.Globalization": "4.0.11",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Threading": "4.0.11"
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Collections.Specialized": {
"type": "Transitive",
"resolved": "4.0.1",
"contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==",
"resolved": "4.3.0",
"contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==",
"dependencies": {
"System.Collections.NonGeneric": "4.0.1",
"System.Globalization": "4.0.11",
"System.Globalization.Extensions": "4.0.1",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Threading": "4.0.11"
"System.Collections.NonGeneric": "4.3.0",
"System.Globalization": "4.3.0",
"System.Globalization.Extensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.ComponentModel": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.Primitives": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==",
"dependencies": {
"System.ComponentModel": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.ComponentModel.TypeConverter": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Collections.NonGeneric": "4.3.0",
"System.Collections.Specialized": "4.3.0",
"System.ComponentModel": "4.3.0",
"System.ComponentModel.Primitives": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Composition": {
@ -2046,17 +2076,6 @@
"System.Text.Encoding": "4.3.0"
}
},
"System.Data.SqlClient": {
"type": "Transitive",
"resolved": "4.6.0",
"contentHash": "gwItUWW1BMCckicFO85c8frFaMK8SGqYn5IeA3GSX4Lmid+CjXETfoHz7Uv+Vx6L0No7iRc/7cBL8gd6o9k9/g==",
"dependencies": {
"Microsoft.Win32.Registry": "4.5.0",
"System.Security.Principal.Windows": "4.5.0",
"System.Text.Encoding.CodePages": "4.5.0",
"runtime.native.System.Data.SqlClient.sni": "4.5.0"
}
},
"System.Diagnostics.Debug": {
"type": "Transitive",
"resolved": "4.3.0",
@ -2160,24 +2179,23 @@
},
"System.Dynamic.Runtime": {
"type": "Transitive",
"resolved": "4.0.11",
"contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==",
"resolved": "4.3.0",
"contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==",
"dependencies": {
"System.Collections": "4.0.11",
"System.Diagnostics.Debug": "4.0.11",
"System.Globalization": "4.0.11",
"System.Linq": "4.1.0",
"System.Linq.Expressions": "4.1.0",
"System.ObjectModel": "4.0.12",
"System.Reflection": "4.1.0",
"System.Reflection.Emit": "4.0.1",
"System.Reflection.Emit.ILGeneration": "4.0.1",
"System.Reflection.Primitives": "4.0.1",
"System.Reflection.TypeExtensions": "4.1.0",
"System.Resources.ResourceManager": "4.0.1",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Threading": "4.0.11"
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Threading": "4.3.0"
}
},
"System.Formats.Asn1": {
@ -2808,6 +2826,18 @@
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Runtime.Serialization.Formatters": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==",
"dependencies": {
"System.Collections": "4.3.0",
"System.Reflection": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Serialization.Primitives": "4.3.0"
}
},
"System.Runtime.Serialization.Json": {
"type": "Transitive",
"resolved": "4.3.0",
@ -3299,7 +3329,7 @@
"commercial.core": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )"
"Core": "[2023.2.0, )"
}
},
"core": {
@ -3310,10 +3340,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",
@ -3345,7 +3376,7 @@
"infrastructure.dapper": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Core": "[2023.2.0, )",
"Dapper": "[2.0.123, )"
}
},
@ -3353,7 +3384,7 @@
"type": "Project",
"dependencies": {
"AutoMapper.Extensions.Microsoft.DependencyInjection": "[11.0.0, )",
"Core": "[2023.1.0, )",
"Core": "[2023.2.0, )",
"Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )",
"Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )",
"Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )",
@ -3365,38 +3396,38 @@
"migrator": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Core": "[2023.2.0, )",
"Microsoft.Extensions.Logging": "[6.0.0, )",
"dbup-sqlserver": "[4.5.0, )"
"dbup-sqlserver": "[5.0.8, )"
}
},
"mysqlmigrations": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Infrastructure.EntityFramework": "[2023.1.0, )"
"Core": "[2023.2.0, )",
"Infrastructure.EntityFramework": "[2023.2.0, )"
}
},
"postgresmigrations": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Infrastructure.EntityFramework": "[2023.1.0, )"
"Core": "[2023.2.0, )",
"Infrastructure.EntityFramework": "[2023.2.0, )"
}
},
"sharedweb": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Infrastructure.Dapper": "[2023.1.0, )",
"Infrastructure.EntityFramework": "[2023.1.0, )"
"Core": "[2023.2.0, )",
"Infrastructure.Dapper": "[2023.2.0, )",
"Infrastructure.EntityFramework": "[2023.2.0, )"
}
},
"sqlitemigrations": {
"type": "Project",
"dependencies": {
"Core": "[2023.1.0, )",
"Infrastructure.EntityFramework": "[2023.1.0, )"
"Core": "[2023.2.0, )",
"Infrastructure.EntityFramework": "[2023.2.0, )"
}
}
}

View File

@ -0,0 +1,143 @@
using Bit.Api.Models.Request;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces;
using Bit.Core.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
[Route("organizations")]
[Authorize("Application")]
public class OrganizationDomainController : Controller
{
private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand;
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand;
private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery;
private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery;
private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationDomainRepository _organizationDomainRepository;
public OrganizationDomainController(
ICreateOrganizationDomainCommand createOrganizationDomainCommand,
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand,
IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery,
IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery,
ICurrentContext currentContext,
IOrganizationRepository organizationRepository,
IOrganizationDomainRepository organizationDomainRepository)
{
_createOrganizationDomainCommand = createOrganizationDomainCommand;
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
_deleteOrganizationDomainCommand = deleteOrganizationDomainCommand;
_getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery;
_getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery;
_currentContext = currentContext;
_organizationRepository = organizationRepository;
_organizationDomainRepository = organizationDomainRepository;
}
[HttpGet("{orgId}/domain")]
public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(string orgId)
{
var orgIdGuid = new Guid(orgId);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domains = await _getOrganizationDomainByOrganizationIdQuery
.GetDomainsByOrganizationId(orgIdGuid);
var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList();
return new ListResponseModel<OrganizationDomainResponseModel>(response);
}
[HttpGet("{orgId}/domain/{id}")]
public async Task<OrganizationDomainResponseModel> Get(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var IdGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domain = await _getOrganizationDomainByIdQuery.GetOrganizationDomainById(IdGuid);
if (domain is null)
{
throw new NotFoundException();
}
return new OrganizationDomainResponseModel(domain);
}
[HttpPost("{orgId}/domain")]
public async Task<OrganizationDomainResponseModel> Post(string orgId,
[FromBody] OrganizationDomainRequestModel model)
{
var orgIdGuid = new Guid(orgId);
await ValidateOrganizationAccessAsync(orgIdGuid);
var organizationDomain = new OrganizationDomain
{
OrganizationId = orgIdGuid,
Txt = model.Txt,
DomainName = model.DomainName.ToLower()
};
var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain);
return new OrganizationDomainResponseModel(domain);
}
[HttpPost("{orgId}/domain/{id}/verify")]
public async Task<OrganizationDomainResponseModel> Verify(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
return new OrganizationDomainResponseModel(domain);
}
[HttpDelete("{orgId}/domain/{id}")]
[HttpPost("{orgId}/domain/{id}/remove")]
public async Task RemoveDomain(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
await _deleteOrganizationDomainCommand.DeleteAsync(idGuid);
}
[AllowAnonymous]
[HttpPost("domain/sso/details")] // must be post to accept email cleanly
public async Task<OrganizationDomainSsoDetailsResponseModel> GetOrgDomainSsoDetails(
[FromBody] OrganizationDomainSsoDetailsRequestModel model)
{
var ssoResult = await _organizationDomainRepository.GetOrganizationDomainSsoDetailsAsync(model.Email);
if (ssoResult is null)
{
throw new NotFoundException("Claimed org domain not found");
}
return new OrganizationDomainSsoDetailsResponseModel(ssoResult);
}
private async Task ValidateOrganizationAccessAsync(Guid orgIdGuid)
{
if (!await _currentContext.ManageSso(orgIdGuid))
{
throw new UnauthorizedAccessException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
}
}

View File

@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Accounts;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Api.SecretsManager;
using Bit.Api.Utilities;
using Bit.Core.Context;
using Bit.Core.Enums;
@ -38,6 +39,7 @@ public class OrganizationsController : Controller
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly GlobalSettings _globalSettings;
@ -55,6 +57,7 @@ public class OrganizationsController : Controller
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository,
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
GlobalSettings globalSettings)
{
@ -71,6 +74,7 @@ public class OrganizationsController : Controller
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository;
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_globalSettings = globalSettings;
}
@ -135,6 +139,7 @@ public class OrganizationsController : Controller
{
throw new NotFoundException();
}
return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo);
}
else
@ -255,7 +260,7 @@ public class OrganizationsController : Controller
}
var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.BusinessName ||
model.BillingEmail != organization.BillingEmail);
model.BillingEmail != organization.BillingEmail);
var hasRequiredPermissions = updateBilling
? await _currentContext.ManageBilling(orgIdGuid)
@ -304,11 +309,7 @@ public class OrganizationsController : Controller
}
var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade());
return new PaymentResponseModel
{
Success = result.Item1,
PaymentIntentClientSecret = result.Item2
};
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 };
}
[HttpPost("{id}/subscription")]
@ -335,11 +336,7 @@ public class OrganizationsController : Controller
}
var result = await _organizationService.AdjustSeatsAsync(orgIdGuid, model.SeatAdjustment.Value);
return new PaymentResponseModel
{
Success = true,
PaymentIntentClientSecret = result
};
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
}
[HttpPost("{id}/storage")]
@ -353,11 +350,7 @@ public class OrganizationsController : Controller
}
var result = await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value);
return new PaymentResponseModel
{
Success = true,
PaymentIntentClientSecret = result
};
return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result };
}
[HttpPost("{id}/verify-bank")]
@ -471,7 +464,15 @@ public class OrganizationsController : Controller
throw new BadRequestException("Invalid license");
}
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid);
if (selfHostedOrganizationDetails == null)
{
throw new NotFoundException();
}
var existingOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, existingOrganization);
}
[HttpPost("{id}/import")]
@ -548,7 +549,8 @@ public class OrganizationsController : Controller
}
[HttpGet("{id}/api-key-information/{type?}")]
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id, [FromRoute] OrganizationApiKeyType? type)
public async Task<ListResponseModel<OrganizationApiKeyInformation>> ApiKeyInformation(Guid id,
[FromRoute] OrganizationApiKeyType? type)
{
if (!await HasApiKeyAccessAsync(id, type))
{
@ -577,8 +579,8 @@ public class OrganizationsController : Controller
}
var organizationApiKey = await _getOrganizationApiKeyQuery
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
.GetOrganizationApiKeyAsync(organization.Id, model.Type) ??
await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type);
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
@ -679,7 +681,8 @@ public class OrganizationsController : Controller
throw new UnauthorizedAccessException();
}
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey, model.EncryptedPrivateKey);
var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey,
model.EncryptedPrivateKey);
return new OrganizationKeysResponseModel(org);
}
@ -725,4 +728,34 @@ public class OrganizationsController : Controller
return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig);
}
// This is a temporary endpoint to self-enroll in secrets manager
[SecretsManager]
[SelfHosted(NotSelfHostedOnly = true)]
[HttpPost("{id}/enroll-secrets-manager")]
public async Task EnrollSecretsManager(Guid id, [FromBody] OrganizationEnrollSecretsManagerRequestModel model)
{
var userId = _userService.GetProperUserId(User).Value;
if (!await _currentContext.OrganizationAdmin(id))
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(id);
if (organization == null)
{
throw new NotFoundException();
}
organization.UseSecretsManager = model.Enabled;
await _organizationService.UpdateAsync(organization);
// Turn on Secrets Manager for the user
if (model.Enabled)
{
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(id, userId);
orgUser.AccessSecretsManager = true;
await _organizationUserRepository.ReplaceAsync(orgUser);
}
}
}

View File

@ -27,6 +27,7 @@ public class SelfHostedOrganizationLicensesController : Controller
private readonly IOrganizationService _organizationService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserService _userService;
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
public SelfHostedOrganizationLicensesController(
ICurrentContext currentContext,
@ -34,7 +35,8 @@ public class SelfHostedOrganizationLicensesController : Controller
IOrganizationConnectionRepository organizationConnectionRepository,
IOrganizationService organizationService,
IOrganizationRepository organizationRepository,
IUserService userService)
IUserService userService,
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand)
{
_currentContext = currentContext;
_selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery;
@ -42,6 +44,7 @@ public class SelfHostedOrganizationLicensesController : Controller
_organizationService = organizationService;
_organizationRepository = organizationRepository;
_userService = userService;
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
}
[HttpPost("")]
@ -79,25 +82,33 @@ public class SelfHostedOrganizationLicensesController : Controller
throw new BadRequestException("Invalid license");
}
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid);
if (selfHostedOrganizationDetails == null)
{
throw new NotFoundException();
}
var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization);
}
[HttpPost("{id}/sync")]
public async Task SyncLicenseAsync(string id)
{
var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
if (organization == null)
var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(new Guid(id));
if (selfHostedOrganizationDetails == null)
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationOwner(organization.Id))
if (!await _currentContext.OrganizationOwner(selfHostedOrganizationDetails.Id))
{
throw new NotFoundException();
}
var billingSyncConnection =
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(selfHostedOrganizationDetails.Id,
OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
if (billingSyncConnection == null)
{
@ -105,9 +116,10 @@ public class SelfHostedOrganizationLicensesController : Controller
}
var license =
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection);
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(selfHostedOrganizationDetails, billingSyncConnection);
var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey);
await _organizationService.UpdateLicenseAsync(organization.Id, license);
await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization);
var config = billingSyncConnection.GetConfig<BillingSyncConfig>();
config.LastLicenseSync = DateTime.Now;

View File

@ -11,7 +11,6 @@ using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Duo;
using Fido2NetLib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@ -153,7 +152,7 @@ public class TwoFactorController : Controller
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
{
@ -210,7 +209,7 @@ public class TwoFactorController : Controller
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
duoApi.JSONApiCall<object>("GET", "/auth/v2/check");
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
{
@ -295,21 +294,14 @@ public class TwoFactorController : Controller
if (await _verifyAuthRequestCommand
.VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode))
{
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
else
else if (await _userService.VerifySecretAsync(user, model.Secret))
{
if (await _userService.VerifySecretAsync(user, model.Secret))
{
var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model);
await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin);
return;
}
await _userService.SendTwoFactorEmailAsync(user);
return;
}
}
@ -390,41 +382,18 @@ public class TwoFactorController : Controller
}
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpGet("get-device-verification-settings")]
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
public Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (User.Claims.HasSsoIdP())
{
return new DeviceVerificationResponseModel(false, false);
}
var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user);
return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled);
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
[Obsolete("Leaving this for backwards compatibilty on clients")]
[HttpPut("device-verification-settings")]
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
public Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
if (!_userService.CanEditDeviceVerificationSettings(user)
|| User.Claims.HasSsoIdP())
{
throw new InvalidOperationException("Can't update device verification settings");
}
model.ToUser(user);
await _userService.SaveUserAsync(user);
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
}
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
@ -467,17 +436,4 @@ public class TwoFactorController : Controller
await Task.Delay(500);
}
}
private async Task<bool> IsNewDeviceLoginAsync(User user, TwoFactorEmailRequestModel model)
{
if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null
&&
await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null))
{
model.ToUser(user);
return true;
}
return false;
}
}

View File

@ -47,6 +47,12 @@ public class JobsHostedService : BaseJobsHostedService
.WithIntervalInHours(24)
.RepeatForever())
.Build();
var validateOrganizationDomainTrigger = TriggerBuilder.Create()
.WithIdentity("ValidateOrganizationDomainTrigger")
.StartNow()
.WithCronSchedule("0 0 * * * ?")
.Build();
var jobs = new List<Tuple<Type, ITrigger>>
{
@ -54,7 +60,8 @@ public class JobsHostedService : BaseJobsHostedService
new Tuple<Type, ITrigger>(typeof(EmergencyAccessNotificationJob), emergencyAccessNotificationTrigger),
new Tuple<Type, ITrigger>(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger)
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger),
new Tuple<Type, ITrigger>(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger),
};
if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication)
@ -78,5 +85,6 @@ public class JobsHostedService : BaseJobsHostedService
services.AddTransient<EmergencyAccessTimeoutJob>();
services.AddTransient<ValidateUsersJob>();
services.AddTransient<ValidateOrganizationsJob>();
services.AddTransient<ValidateOrganizationDomainJob>();
}
}

View File

@ -0,0 +1,30 @@
using Bit.Core;
using Bit.Core.Jobs;
using Bit.Core.Services;
using Quartz;
namespace Bit.Api.Jobs;
public class ValidateOrganizationDomainJob : BaseJob
{
private readonly IServiceProvider _serviceProvider;
public ValidateOrganizationDomainJob(
IServiceProvider serviceProvider,
ILogger<ValidateOrganizationDomainJob> logger)
: base(logger)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteJobAsync(IJobExecutionContext context)
{
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: Start");
using (var serviceScope = _serviceProvider.CreateScope())
{
var organizationDomainService =
serviceScope.ServiceProvider.GetRequiredService<IOrganizationDomainService>();
await organizationDomainService.ValidateOrganizationsDomainAsync();
}
_logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: End");
}
}

View File

@ -1,16 +1,10 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
namespace Bit.Api.Models.Request;
public class DeviceVerificationRequestModel
{
[Obsolete("Leaving this for backwards compatibilty on clients")]
[Required]
public bool UnknownDeviceVerificationEnabled { get; set; }
public User ToUser(User user)
{
user.UnknownDeviceVerificationEnabled = UnknownDeviceVerificationEnabled;
return user;
}
}

View File

@ -10,8 +10,6 @@ public class GroupRequestModel
public string Name { get; set; }
[Required]
public bool? AccessAll { get; set; }
[StringLength(300)]
public string ExternalId { get; set; }
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
public IEnumerable<Guid> Users { get; set; }
@ -27,7 +25,6 @@ public class GroupRequestModel
{
existingGroup.Name = Name;
existingGroup.AccessAll = AccessAll.Value;
existingGroup.ExternalId = ExternalId;
return existingGroup;
}
}

View File

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Request;
public class OrganizationDomainRequestModel
{
[Required]
public string Txt { get; set; }
[Required]
public string DomainName { get; set; }
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Request.Organizations;
public class OrganizationDomainSsoDetailsRequestModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
}

View File

@ -0,0 +1,6 @@
namespace Bit.Api.Models.Request.Organizations;
public class OrganizationEnrollSecretsManagerRequestModel
{
public bool Enabled { get; set; }
}

View File

@ -12,9 +12,6 @@ public class OrganizationUpdateRequestModel
public string Name { get; set; }
[StringLength(50)]
public string BusinessName { get; set; }
[Obsolete("2022-08-03 Moved to Org SSO request model, left for backwards compatability. Remove with EC-489.")]
[StringLength(50)]
public string Identifier { get; set; }
[EmailAddress]
[Required]
[StringLength(256)]
@ -31,7 +28,6 @@ public class OrganizationUpdateRequestModel
existingOrganization.BusinessName = BusinessName;
existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim();
}
existingOrganization.Identifier = Identifier;
Keys?.ToOrganization(existingOrganization);
return existingOrganization;
}

View File

@ -3,6 +3,7 @@ using Bit.Api.Models.Request.Accounts;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Utilities;
using Fido2NetLib;
namespace Bit.Api.Models.Request;
@ -104,7 +105,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!Core.Utilities.Duo.DuoApi.ValidHost(Host))
if (!DuoApi.ValidHost(Host))
{
yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) });
}
@ -202,8 +203,6 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
[StringLength(256)]
public string Email { get; set; }
public string DeviceIdentifier { get; set; }
public string AuthRequestId { get; set; }
public User ToUser(User extistingUser)

View File

@ -2,6 +2,7 @@
namespace Bit.Api.Models.Response;
[Obsolete("Leaving this for backwards compatibilty on clients")]
public class DeviceVerificationResponseModel : ResponseModel
{
public DeviceVerificationResponseModel(bool isDeviceVerificationSectionEnabled, bool unknownDeviceVerificationEnabled)

View File

@ -31,6 +31,9 @@ public class EventResponseModel : ResponseModel
IpAddress = ev.IpAddress;
InstallationId = ev.InstallationId;
SystemUser = ev.SystemUser;
DomainName = ev.DomainName;
SecretId = ev.SecretId;
ServiceAccountId = ev.ServiceAccountId;
}
public EventType Type { get; set; }
@ -50,4 +53,7 @@ public class EventResponseModel : ResponseModel
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
}

View File

@ -0,0 +1,36 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Organizations;
public class OrganizationDomainResponseModel : ResponseModel
{
public OrganizationDomainResponseModel(OrganizationDomain organizationDomain, string obj = "organizationDomain")
: base(obj)
{
if (organizationDomain == null)
{
throw new ArgumentNullException(nameof(organizationDomain));
}
Id = organizationDomain.Id.ToString();
OrganizationId = organizationDomain.OrganizationId.ToString();
Txt = organizationDomain.Txt;
DomainName = organizationDomain.DomainName;
CreationDate = organizationDomain.CreationDate;
NextRunDate = organizationDomain.NextRunDate;
JobRunCount = organizationDomain.JobRunCount;
VerifiedDate = organizationDomain.VerifiedDate;
LastCheckedDate = organizationDomain.LastCheckedDate;
}
public string Id { get; set; }
public string OrganizationId { get; set; }
public string Txt { get; set; }
public string DomainName { get; set; }
public DateTime CreationDate { get; set; }
public DateTime NextRunDate { get; set; }
public int JobRunCount { get; set; }
public DateTime? VerifiedDate { get; set; }
public DateTime? LastCheckedDate { get; set; }
}

View File

@ -0,0 +1,28 @@
using Bit.Core.Models.Api;
using Bit.Core.Models.Data.Organizations;
namespace Bit.Api.Models.Response.Organizations;
public class OrganizationDomainSsoDetailsResponseModel : ResponseModel
{
public OrganizationDomainSsoDetailsResponseModel(OrganizationDomainSsoDetailsData data, string obj = "organizationDomainSsoDetails")
: base(obj)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
SsoAvailable = data.SsoAvailable;
DomainName = data.DomainName;
OrganizationIdentifier = data.OrganizationIdentifier;
SsoRequired = data.SsoRequired;
VerifiedDate = data.VerifiedDate;
}
public bool SsoAvailable { get; private set; }
public string DomainName { get; private set; }
public string OrganizationIdentifier { get; private set; }
public bool SsoRequired { get; private set; }
public DateTime? VerifiedDate { get; private set; }
}

View File

@ -17,7 +17,6 @@ public class OrganizationResponseModel : ResponseModel
}
Id = organization.Id.ToString();
Identifier = organization.Identifier;
Name = organization.Name;
BusinessName = organization.BusinessName;
BusinessAddress1 = organization.BusinessAddress1;
@ -51,7 +50,6 @@ public class OrganizationResponseModel : ResponseModel
}
public string Id { get; set; }
public string Identifier { get; set; }
public string Name { get; set; }
public string BusinessName { get; set; }
public string BusinessAddress1 { get; set; }

View File

@ -23,6 +23,7 @@ public class OrganizationUserResponseModel : ResponseModel
Type = organizationUser.Type;
Status = organizationUser.Status;
AccessAll = organizationUser.AccessAll;
ExternalId = organizationUser.ExternalId;
AccessSecretsManager = organizationUser.AccessSecretsManager;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
@ -41,6 +42,7 @@ public class OrganizationUserResponseModel : ResponseModel
Type = organizationUser.Type;
Status = organizationUser.Status;
AccessAll = organizationUser.AccessAll;
ExternalId = organizationUser.ExternalId;
AccessSecretsManager = organizationUser.AccessSecretsManager;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
@ -52,6 +54,7 @@ public class OrganizationUserResponseModel : ResponseModel
public OrganizationUserType Type { get; set; }
public OrganizationUserStatusType Status { get; set; }
public bool AccessAll { get; set; }
public string ExternalId { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; }
public bool ResetPasswordEnrolled { get; set; }
@ -86,6 +89,7 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
Name = organizationUser.Name;
Email = organizationUser.Email;
AvatarColor = organizationUser.AvatarColor;
TwoFactorEnabled = twoFactorEnabled;
SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId);
Collections = organizationUser.Collections.Select(c => new SelectionReadOnlyResponseModel(c));
@ -94,8 +98,10 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse
ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector;
}
public string Name { get; set; }
public string Email { get; set; }
public string AvatarColor { get; set; }
public bool TwoFactorEnabled { get; set; }
public bool SsoBound { get; set; }
public IEnumerable<SelectionReadOnlyResponseModel> Collections { get; set; }

View File

@ -1,27 +1,54 @@
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager]
[Authorize("secrets")]
[Route("access-policies")]
public class AccessPoliciesController : Controller
{
private const int _maxBulkCreation = 15;
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand;
private readonly ICurrentContext _currentContext;
private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand;
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand;
private readonly IUserService _userService;
public AccessPoliciesController(
IUserService userService,
ICurrentContext currentContext,
IAccessPolicyRepository accessPolicyRepository,
IServiceAccountRepository serviceAccountRepository,
IGroupRepository groupRepository,
IProjectRepository projectRepository,
IOrganizationUserRepository organizationUserRepository,
ICreateAccessPoliciesCommand createAccessPoliciesCommand,
IDeleteAccessPolicyCommand deleteAccessPolicyCommand,
IUpdateAccessPolicyCommand updateAccessPolicyCommand)
{
_userService = userService;
_currentContext = currentContext;
_serviceAccountRepository = serviceAccountRepository;
_projectRepository = projectRepository;
_groupRepository = groupRepository;
_organizationUserRepository = organizationUserRepository;
_accessPolicyRepository = accessPolicyRepository;
_createAccessPoliciesCommand = createAccessPoliciesCommand;
_deleteAccessPolicyCommand = deleteAccessPolicyCommand;
@ -32,37 +59,244 @@ public class AccessPoliciesController : Controller
public async Task<ProjectAccessPoliciesResponseModel> CreateProjectAccessPoliciesAsync([FromRoute] Guid id,
[FromBody] AccessPoliciesCreateRequest request)
{
if (request.Count() > _maxBulkCreation)
{
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
}
var project = await _projectRepository.GetByIdAsync(id);
if (project == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId);
var policies = request.ToBaseAccessPoliciesForProject(id);
var results = await _createAccessPoliciesCommand.CreateAsync(policies);
await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient);
var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
return new ProjectAccessPoliciesResponseModel(results);
}
[HttpGet("/projects/{id}/access-policies")]
public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id)
{
var project = await _projectRepository.GetByIdAsync(id);
await CheckUserHasWriteAccessToProjectAsync(project);
var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
return new ProjectAccessPoliciesResponseModel(results);
}
[HttpPost("/service-accounts/{id}/access-policies")]
public async Task<ServiceAccountAccessPoliciesResponseModel> CreateServiceAccountAccessPoliciesAsync(
[FromRoute] Guid id,
[FromBody] AccessPoliciesCreateRequest request)
{
if (request.Count() > _maxBulkCreation)
{
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
}
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
var policies = request.ToBaseAccessPoliciesForServiceAccount(id);
await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient);
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id);
return new ServiceAccountAccessPoliciesResponseModel(results);
}
[HttpGet("/service-accounts/{id}/access-policies")]
public async Task<ServiceAccountAccessPoliciesResponseModel> GetServiceAccountAccessPoliciesAsync(
[FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount);
var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id);
return new ServiceAccountAccessPoliciesResponseModel(results);
}
[HttpGet("/service-accounts/{id}/granted-policies")]
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id)
{
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
var results = await _accessPolicyRepository.GetManyByServiceAccountIdAsync(id, userId, accessClient);
var responses = results.Select(ap =>
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
}
[HttpPost("/service-accounts/{id}/granted-policies")]
public async Task<ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>>
CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id,
[FromBody] List<GrantedAccessPolicyRequest> requests)
{
if (requests.Count > _maxBulkCreation)
{
throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once.");
}
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id));
var results =
await _createAccessPoliciesCommand.CreateManyAsync(new List<BaseAccessPolicy>(policies), userId, accessClient);
var responses = results.Select(ap =>
new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap));
return new ListResponseModel<ServiceAccountProjectAccessPolicyResponseModel>(responses);
}
[HttpPut("{id}")]
public async Task<BaseAccessPolicyResponseModel> UpdateAccessPolicyAsync([FromRoute] Guid id,
[FromBody] AccessPolicyUpdateRequest request)
{
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write);
var userId = _userService.GetProperUserId(User).Value;
var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write, userId);
return result switch
{
UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy),
UserServiceAccountAccessPolicy accessPolicy =>
new UserServiceAccountAccessPolicyResponseModel(accessPolicy),
GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy),
GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel(
accessPolicy),
ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel(
accessPolicy),
_ => throw new ArgumentException("Unsupported access policy type provided.")
_ => throw new ArgumentException("Unsupported access policy type provided."),
};
}
[HttpDelete("{id}")]
public async Task DeleteAccessPolicyAsync([FromRoute] Guid id)
{
await _deleteAccessPolicyCommand.DeleteAsync(id);
var userId = _userService.GetProperUserId(User).Value;
await _deleteAccessPolicyCommand.DeleteAsync(id, userId);
}
[HttpGet("/organizations/{id}/access-policies/people/potential-grantees")]
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetPeoplePotentialGranteesAsync(
[FromRoute] Guid id)
{
if (!_currentContext.AccessSecretsManager(id))
{
throw new NotFoundException();
}
var groups = await _groupRepository.GetManyByOrganizationIdAsync(id);
var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g));
var organizationUsers =
await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id);
var userResponses = organizationUsers
.Where(user => user.AccessSecretsManager)
.Select(userDetails => new PotentialGranteeResponseModel(userDetails));
return new ListResponseModel<PotentialGranteeResponseModel>(userResponses.Concat(groupResponses));
}
[HttpGet("/organizations/{id}/access-policies/service-accounts/potential-grantees")]
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetServiceAccountsPotentialGranteesAsync(
[FromRoute] Guid id)
{
var (accessClient, userId) = await GetAccessClientTypeAsync(id);
var serviceAccounts =
await _serviceAccountRepository.GetManyByOrganizationIdWriteAccessAsync(id,
userId,
accessClient);
var serviceAccountResponses =
serviceAccounts.Select(serviceAccount => new PotentialGranteeResponseModel(serviceAccount));
return new ListResponseModel<PotentialGranteeResponseModel>(serviceAccountResponses);
}
[HttpGet("/organizations/{id}/access-policies/projects/potential-grantees")]
public async Task<ListResponseModel<PotentialGranteeResponseModel>> GetProjectPotentialGranteesAsync(
[FromRoute] Guid id)
{
var (accessClient, userId) = await GetAccessClientTypeAsync(id);
var projects =
await _projectRepository.GetManyByOrganizationIdWriteAccessAsync(id,
userId,
accessClient);
var projectResponses =
projects.Select(project => new PotentialGranteeResponseModel(project));
return new ListResponseModel<PotentialGranteeResponseModel>(projectResponses);
}
private async Task CheckUserHasWriteAccessToProjectAsync(Project project)
{
if (project == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
}
private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount)
{
if (serviceAccount == null)
{
throw new NotFoundException();
}
var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(
serviceAccount.Id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
}
private async Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientTypeAsync(Guid organizationId)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
return (accessClient, userId);
}
}

View File

@ -65,7 +65,8 @@ public class ProjectsController : Controller
throw new NotFoundException();
}
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId));
var userId = _userService.GetProperUserId(User).Value;
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId), userId);
return new ProjectResponseModel(result);
}

View File

@ -2,9 +2,13 @@
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -15,23 +19,32 @@ namespace Bit.Api.SecretsManager.Controllers;
public class SecretsController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly IProjectRepository _projectRepository;
private readonly ISecretRepository _secretRepository;
private readonly ICreateSecretCommand _createSecretCommand;
private readonly IUpdateSecretCommand _updateSecretCommand;
private readonly IDeleteSecretCommand _deleteSecretCommand;
private readonly IUserService _userService;
private readonly IEventService _eventService;
public SecretsController(
ICurrentContext currentContext,
IProjectRepository projectRepository,
ISecretRepository secretRepository,
ICreateSecretCommand createSecretCommand,
IUpdateSecretCommand updateSecretCommand,
IDeleteSecretCommand deleteSecretCommand)
IDeleteSecretCommand deleteSecretCommand,
IUserService userService,
IEventService eventService)
{
_currentContext = currentContext;
_projectRepository = projectRepository;
_secretRepository = secretRepository;
_createSecretCommand = createSecretCommand;
_updateSecretCommand = updateSecretCommand;
_deleteSecretCommand = deleteSecretCommand;
_userService = userService;
_eventService = eventService;
}
[HttpGet("organizations/{organizationId}/secrets")]
@ -42,7 +55,12 @@ public class SecretsController : Controller
throw new NotFoundException();
}
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, accessClient);
return new SecretWithProjectsListResponseModel(secrets);
}
@ -54,7 +72,8 @@ public class SecretsController : Controller
throw new NotFoundException();
}
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
var userId = _userService.GetProperUserId(User).Value;
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId), userId);
return new SecretResponseModel(result);
}
@ -62,34 +81,81 @@ public class SecretsController : Controller
public async Task<SecretResponseModel> GetAsync([FromRoute] Guid id)
{
var secret = await _secretRepository.GetByIdAsync(id);
if (secret == null)
if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId))
{
throw new NotFoundException();
}
if (!await UserHasReadAccessToSecret(secret))
{
throw new NotFoundException();
}
if (_currentContext.ClientType == ClientType.ServiceAccount)
{
var userId = _userService.GetProperUserId(User).Value;
await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved);
}
return new SecretResponseModel(secret);
}
[HttpGet("projects/{projectId}/secrets")]
public async Task<SecretWithProjectsListResponseModel> GetSecretsByProjectAsync([FromRoute] Guid projectId)
{
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId);
var responses = secrets.Select(s => new SecretResponseModel(s));
var project = await _projectRepository.GetByIdAsync(projectId);
if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId, userId, accessClient);
return new SecretWithProjectsListResponseModel(secrets);
}
[HttpPut("secrets/{id}")]
public async Task<SecretResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
{
var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id));
var userId = _userService.GetProperUserId(User).Value;
var secret = updateRequest.ToSecret(id);
var result = await _updateSecretCommand.UpdateAsync(secret, userId);
return new SecretResponseModel(result);
}
// TODO Once permissions are setup for Secrets Manager need to enforce them on delete.
[HttpPost("secrets/delete")]
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
{
var results = await _deleteSecretCommand.DeleteSecrets(ids);
var userId = _userService.GetProperUserId(User).Value;
var results = await _deleteSecretCommand.DeleteSecrets(ids, userId);
var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2));
return new ListResponseModel<BulkDeleteResponseModel>(responses);
}
public async Task<bool> UserHasReadAccessToSecret(Secret secret)
{
var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = orgAdmin;
if (secret.Projects?.Count > 0)
{
Guid projectId = secret.Projects.FirstOrDefault().Id;
hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _projectRepository.UserHasReadAccessToProject(projectId, userId),
AccessClientType.ServiceAccount => await _projectRepository.ServiceAccountHasReadAccessToProject(projectId, userId),
_ => false,
};
}
return hasAccess;
}
}

View File

@ -0,0 +1,66 @@
using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager]
public class SecretsManagerPortingController : Controller
{
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly IUserService _userService;
private readonly IImportCommand _importCommand;
private readonly ICurrentContext _currentContext;
public SecretsManagerPortingController(ISecretRepository secretRepository, IProjectRepository projectRepository, IUserService userService, IImportCommand importCommand, ICurrentContext currentContext)
{
_secretRepository = secretRepository;
_projectRepository = projectRepository;
_userService = userService;
_importCommand = importCommand;
_currentContext = currentContext;
}
[HttpGet("sm/{organizationId}/export")]
public async Task<SMExportResponseModel> Export([FromRoute] Guid organizationId, [FromRoute] string format = "json")
{
if (!await _currentContext.OrganizationAdmin(organizationId))
{
throw new UnauthorizedAccessException();
}
var userId = _userService.GetProperUserId(User).Value;
var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
if (projects == null && secrets == null)
{
throw new NotFoundException();
}
return new SMExportResponseModel(projects, secrets);
}
[HttpPost("sm/{organizationId}/import")]
public async Task Import([FromRoute] Guid organizationId, [FromBody] SMImportRequestModel importRequest)
{
if (!await _currentContext.OrganizationAdmin(organizationId))
{
throw new UnauthorizedAccessException();
}
if (importRequest.Projects?.Count() > 1000 || importRequest.Secrets?.Count() > 6000)
{
throw new BadRequestException("You cannot import this much data at once, the limit is 1000 projects and 6000 secrets.");
}
await _importCommand.ImportAsync(organizationId, importRequest.ToSMImport());
}
}

View File

@ -0,0 +1,80 @@
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager]
[Authorize("secrets")]
public class TrashController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository;
private readonly IEmptyTrashCommand _emptyTrashCommand;
private readonly IRestoreTrashCommand _restoreTrashCommand;
public TrashController(
ICurrentContext currentContext,
ISecretRepository secretRepository,
IEmptyTrashCommand emptyTrashCommand,
IRestoreTrashCommand restoreTrashCommand)
{
_currentContext = currentContext;
_secretRepository = secretRepository;
_emptyTrashCommand = emptyTrashCommand;
_restoreTrashCommand = restoreTrashCommand;
}
[HttpGet("secrets/{organizationId}/trash")]
public async Task<SecretWithProjectsListResponseModel> ListByOrganizationAsync(Guid organizationId)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationAdmin(organizationId))
{
throw new UnauthorizedAccessException();
}
var secrets = await _secretRepository.GetManyByOrganizationIdInTrashAsync(organizationId);
return new SecretWithProjectsListResponseModel(secrets);
}
[HttpPost("secrets/{organizationId}/trash/empty")]
public async Task EmptyTrashAsync(Guid organizationId, [FromBody] List<Guid> ids)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationAdmin(organizationId))
{
throw new UnauthorizedAccessException();
}
await _emptyTrashCommand.EmptyTrash(organizationId, ids);
}
[HttpPost("secrets/{organizationId}/trash/restore")]
public async Task RestoreTrashAsync(Guid organizationId, [FromBody] List<Guid> ids)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationAdmin(organizationId))
{
throw new UnauthorizedAccessException();
}
await _restoreTrashCommand.RestoreTrash(organizationId, ids);
}
}

View File

@ -19,20 +19,23 @@ namespace Bit.Api.SecretsManager.Controllers;
public class ServiceAccountsController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IApiKeyRepository _apiKeyRepository;
private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
private readonly ICreateServiceAccountCommand _createServiceAccountCommand;
private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
private readonly IUserService _userService;
private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand;
public ServiceAccountsController(
ICurrentContext currentContext,
IUserService userService,
IServiceAccountRepository serviceAccountRepository,
IApiKeyRepository apiKeyRepository,
ICreateAccessTokenCommand createAccessTokenCommand,
IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand)
ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand,
IRevokeAccessTokensCommand revokeAccessTokensCommand)
{
_currentContext = currentContext;
_userService = userService;
@ -40,6 +43,7 @@ public class ServiceAccountsController : Controller
_apiKeyRepository = apiKeyRepository;
_createServiceAccountCommand = createServiceAccountCommand;
_updateServiceAccountCommand = updateServiceAccountCommand;
_revokeAccessTokensCommand = revokeAccessTokensCommand;
_createAccessTokenCommand = createAccessTokenCommand;
}
@ -129,4 +133,37 @@ public class ServiceAccountsController : Controller
var result = await _createAccessTokenCommand.CreateAsync(request.ToApiKey(id), userId);
return new AccessTokenCreationResponseModel(result);
}
[HttpPost("{id}/access-tokens/revoke")]
public async Task RevokeAccessTokensAsync(Guid id, [FromBody] RevokeAccessTokensRequest request)
{
var userId = _userService.GetProperUserId(User).Value;
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id);
if (serviceAccount == null)
{
throw new NotFoundException();
}
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var hasAccess = accessClient switch
{
AccessClientType.NoAccessCheck => true,
AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(id, userId),
_ => false,
};
if (!hasAccess)
{
throw new NotFoundException();
}
await _revokeAccessTokensCommand.RevokeAsync(serviceAccount, request.Ids);
}
}

View File

@ -13,7 +13,7 @@ public class AccessPoliciesCreateRequest
public IEnumerable<AccessPolicyRequest>? ServiceAccountAccessPolicyRequests { get; set; }
public List<BaseAccessPolicy> ToBaseAccessPoliciesForProject(Guid projectId)
public List<BaseAccessPolicy> ToBaseAccessPoliciesForProject(Guid grantedProjectId)
{
if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null)
{
@ -21,20 +21,77 @@ public class AccessPoliciesCreateRequest
}
var userAccessPolicies = UserAccessPolicyRequests?
.Select(x => x.ToUserProjectAccessPolicy(projectId)).ToList();
.Select(x => x.ToUserProjectAccessPolicy(grantedProjectId)).ToList();
var groupAccessPolicies = GroupAccessPolicyRequests?
.Select(x => x.ToGroupProjectAccessPolicy(projectId)).ToList();
.Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId)).ToList();
var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests?
.Select(x => x.ToServiceAccountProjectAccessPolicy(projectId)).ToList();
.Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId)).ToList();
var policies = new List<BaseAccessPolicy>();
if (userAccessPolicies != null) { policies.AddRange(userAccessPolicies); }
if (groupAccessPolicies != null) { policies.AddRange(groupAccessPolicies); }
if (serviceAccountAccessPolicies != null) { policies.AddRange(serviceAccountAccessPolicies); }
if (userAccessPolicies != null)
{
policies.AddRange(userAccessPolicies);
}
if (groupAccessPolicies != null)
{
policies.AddRange(groupAccessPolicies);
}
if (serviceAccountAccessPolicies != null)
{
policies.AddRange(serviceAccountAccessPolicies);
}
return policies;
}
public List<BaseAccessPolicy> ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId)
{
if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null)
{
throw new BadRequestException("No creation requests provided.");
}
var userAccessPolicies = UserAccessPolicyRequests?
.Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId)).ToList();
var groupAccessPolicies = GroupAccessPolicyRequests?
.Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId)).ToList();
var policies = new List<BaseAccessPolicy>();
if (userAccessPolicies != null)
{
policies.AddRange(userAccessPolicies);
}
if (groupAccessPolicies != null)
{
policies.AddRange(groupAccessPolicies);
}
return policies;
}
public int Count()
{
var total = 0;
if (UserAccessPolicyRequests != null)
{
total += UserAccessPolicyRequests.Count();
}
if (GroupAccessPolicyRequests != null)
{
total += GroupAccessPolicyRequests.Count();
}
if (ServiceAccountAccessPolicyRequests != null)
{
total += ServiceAccountAccessPolicyRequests.Count();
}
return total;
}
}
public class AccessPolicyRequest
@ -74,4 +131,22 @@ public class AccessPolicyRequest
Read = Read,
Write = Write
};
public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id) =>
new()
{
OrganizationUserId = GranteeId,
GrantedServiceAccountId = id,
Read = Read,
Write = Write
};
public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id) =>
new()
{
GroupId = GranteeId,
GrantedServiceAccountId = id,
Read = Read,
Write = Write
};
}

View File

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Request;
public class GrantedAccessPolicyRequest
{
[Required]
public Guid GrantedId { get; set; }
[Required]
public bool Read { get; set; }
[Required]
public bool Write { get; set; }
public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid serviceAccountId) =>
new()
{
ServiceAccountId = serviceAccountId,
GrantedProjectId = GrantedId,
Read = Read,
Write = Write,
};
}

View File

@ -0,0 +1,7 @@
using System.ComponentModel.DataAnnotations;
public class RevokeAccessTokensRequest
{
[Required]
public Guid[] Ids { get; set; }
}

View File

@ -0,0 +1,70 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.SecretsManager.Commands.Porting;
using Bit.Core.Utilities;
namespace Bit.Api.SecretsManager.Models.Request;
public class SMImportRequestModel
{
public IEnumerable<InnerProjectImportRequestModel> Projects { get; set; }
public IEnumerable<InnerSecretImportRequestModel> Secrets { get; set; }
public class InnerProjectImportRequestModel
{
public InnerProjectImportRequestModel() { }
[Required]
public Guid Id { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Name { get; set; }
}
public class InnerSecretImportRequestModel
{
public InnerSecretImportRequestModel() { }
[Required]
public Guid Id { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Key { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Value { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Note { get; set; }
[Required]
public IEnumerable<Guid> ProjectIds { get; set; }
}
public SMImport ToSMImport()
{
return new SMImport
{
Projects = Projects?.Select(p => new SMImport.InnerProject
{
Id = p.Id,
Name = p.Name,
}),
Secrets = Secrets?.Select(s => new SMImport.InnerSecret
{
Id = s.Id,
Key = s.Key,
Value = s.Value,
Note = s.Note,
ProjectIds = s.ProjectIds,
}),
};
}
}

View File

@ -1,4 +1,5 @@
#nullable enable
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
@ -20,6 +21,11 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel
public bool Write { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public string? GetUserDisplayName(User? user)
{
return string.IsNullOrWhiteSpace(user?.Name) ? user?.Email : user?.Name;
}
}
public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
@ -30,7 +36,7 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode
{
OrganizationUserId = accessPolicy.OrganizationUserId;
GrantedProjectId = accessPolicy.GrantedProjectId;
OrganizationUserName = accessPolicy.User?.Name;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
}
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
@ -51,7 +57,7 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo
{
OrganizationUserId = accessPolicy.OrganizationUserId;
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
OrganizationUserName = accessPolicy.User?.Name;
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
}
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
@ -115,6 +121,7 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe
ServiceAccountId = accessPolicy.ServiceAccountId;
GrantedProjectId = accessPolicy.GrantedProjectId;
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
GrantedProjectName = accessPolicy.GrantedProject?.Name;
}
public ServiceAccountProjectAccessPolicyResponseModel()
@ -125,4 +132,5 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe
public Guid? ServiceAccountId { get; set; }
public string? ServiceAccountName { get; set; }
public Guid? GrantedProjectId { get; set; }
public string? GrantedProjectName { get; set; }
}

View File

@ -0,0 +1,75 @@
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class PotentialGranteeResponseModel : ResponseModel
{
private const string _objectName = "potentialGrantee";
public PotentialGranteeResponseModel(Group group)
: base(_objectName)
{
if (group == null)
{
throw new ArgumentNullException(nameof(group));
}
Id = group.Id.ToString();
Name = group.Name;
Type = "group";
}
public PotentialGranteeResponseModel(OrganizationUserUserDetails user)
: base(_objectName)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
Id = user.Id.ToString();
Name = user.Name;
Email = user.Email;
Type = "user";
}
public PotentialGranteeResponseModel(ServiceAccount serviceAccount)
: base(_objectName)
{
if (serviceAccount == null)
{
throw new ArgumentNullException(nameof(serviceAccount));
}
Id = serviceAccount.Id.ToString();
Name = serviceAccount.Name;
Type = "serviceAccount";
}
public PotentialGranteeResponseModel(Project project)
: base(_objectName)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
Id = project.Id.ToString();
Name = project.Name;
Type = "project";
}
public PotentialGranteeResponseModel() : base(_objectName)
{
}
public string Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string? Email { get; set; }
}

View File

@ -11,6 +11,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel
: base(_objectName)
{
foreach (var baseAccessPolicy in baseAccessPolicies)
{
switch (baseAccessPolicy)
{
case UserProjectAccessPolicy accessPolicy:
@ -24,6 +25,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel
new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy));
break;
}
}
}
public ProjectAccessPoliciesResponseModel() : base(_objectName)

View File

@ -0,0 +1,46 @@
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class SMExportResponseModel : ResponseModel
{
public SMExportResponseModel(IEnumerable<Project> projects, IEnumerable<Secret> secrets, string obj = "SecretsManagerExportResponseModel") : base(obj)
{
Secrets = secrets?.Select(s => new InnerSecretExportResponseModel(s));
Projects = projects?.Select(p => new InnerProjectExportResponseModel(p));
}
public IEnumerable<InnerProjectExportResponseModel> Projects { get; set; }
public IEnumerable<InnerSecretExportResponseModel> Secrets { get; set; }
public class InnerProjectExportResponseModel
{
public InnerProjectExportResponseModel(Project project)
{
Id = project.Id;
Name = project.Name;
}
public Guid Id { get; set; }
public string Name { get; set; }
}
public class InnerSecretExportResponseModel
{
public InnerSecretExportResponseModel(Secret secret)
{
Id = secret.Id;
Key = secret.Key;
Value = secret.Value;
Note = secret.Note;
ProjectIds = secret.Projects?.Select(p => p.Id);
}
public Guid Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Note { get; set; }
public IEnumerable<Guid> ProjectIds { get; set; }
}
}

View File

@ -0,0 +1,50 @@
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Commands.Porting;
namespace Bit.Api.SecretsManager.Models.Response;
public class SMImportResponseModel : ResponseModel
{
public SMImportResponseModel(SMImport import, string obj = "SecretsManagerImportResponseModel") : base(obj)
{
Projects = import.Projects?.Select(p => new InnerProjectImportResponseModel(p));
Secrets = import.Secrets?.Select(s => new InnerSecretImportResponseModel(s));
}
public IEnumerable<InnerProjectImportResponseModel> Projects { get; set; }
public IEnumerable<InnerSecretImportResponseModel> Secrets { get; set; }
public class InnerProjectImportResponseModel
{
public InnerProjectImportResponseModel() { }
public InnerProjectImportResponseModel(SMImport.InnerProject project)
{
Id = project.Id;
Name = project.Name;
}
public Guid Id { get; set; }
public string Name { get; set; }
}
public class InnerSecretImportResponseModel
{
public InnerSecretImportResponseModel() { }
public InnerSecretImportResponseModel(SMImport.InnerSecret secret)
{
Id = secret.Id;
Key = secret.Key;
Value = secret.Value;
Note = secret.Note;
ProjectIds = secret.ProjectIds;
}
public Guid Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Note { get; set; }
public IEnumerable<Guid> ProjectIds { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class ServiceAccountAccessPoliciesResponseModel : ResponseModel
{
private const string _objectName = "serviceAccountAccessPolicies";
public ServiceAccountAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies)
: base(_objectName)
{
if (baseAccessPolicies == null)
{
return;
}
foreach (var baseAccessPolicy in baseAccessPolicies)
{
switch (baseAccessPolicy)
{
case UserServiceAccountAccessPolicy accessPolicy:
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy));
break;
case GroupServiceAccountAccessPolicy accessPolicy:
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
break;
}
}
}
public ServiceAccountAccessPoliciesResponseModel() : base(_objectName)
{
}
public List<UserServiceAccountAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
public List<GroupServiceAccountAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
}

View File

@ -92,6 +92,11 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute
errorMessage = "Unauthorized.";
context.HttpContext.Response.StatusCode = 401;
}
else if (exception is ConflictException)
{
errorMessage = exception.Message;
context.HttpContext.Response.StatusCode = 409;
}
else if (exception is AggregateException aggregateException)
{
context.HttpContext.Response.StatusCode = 400;

View File

@ -85,8 +85,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -123,28 +123,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -171,6 +171,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -2794,10 +2802,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -74,8 +74,8 @@
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.24.0",
"contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==",
"resolved": "1.25.0",
"contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.1",
"System.Diagnostics.DiagnosticSource": "4.6.0",
@ -112,28 +112,28 @@
},
"Azure.Storage.Blobs": {
"type": "Transitive",
"resolved": "12.11.0",
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
"resolved": "12.14.1",
"contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Text.Json": "4.7.2"
}
},
"Azure.Storage.Common": {
"type": "Transitive",
"resolved": "12.10.0",
"contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==",
"resolved": "12.13.0",
"contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==",
"dependencies": {
"Azure.Core": "1.22.0",
"Azure.Core": "1.25.0",
"System.IO.Hashing": "6.0.0"
}
},
"Azure.Storage.Queues": {
"type": "Transitive",
"resolved": "12.9.0",
"contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==",
"resolved": "12.12.0",
"contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==",
"dependencies": {
"Azure.Storage.Common": "12.10.0",
"Azure.Storage.Common": "12.13.0",
"System.Memory.Data": "1.0.2",
"System.Text.Json": "4.7.2"
}
@ -160,6 +160,14 @@
"resolved": "2.0.123",
"contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ=="
},
"DnsClient": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==",
"dependencies": {
"Microsoft.Win32.Registry": "5.0.0"
}
},
"Fido2": {
"type": "Transitive",
"resolved": "3.0.1",
@ -3245,10 +3253,11 @@
"AspNetCoreRateLimit": "[4.0.2, )",
"AspNetCoreRateLimit.Redis": "[1.0.1, )",
"Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )",
"Azure.Storage.Blobs": "[12.11.0, )",
"Azure.Storage.Queues": "[12.9.0, )",
"Azure.Storage.Blobs": "[12.14.1, )",
"Azure.Storage.Queues": "[12.12.0, )",
"BitPay.Light": "[1.0.1907, )",
"Braintree": "[5.12.0, )",
"DnsClient": "[1.7.0, )",
"Fido2.AspNet": "[3.0.1, )",
"Handlebars.Net": "[2.1.2, )",
"IdentityServer4": "[4.1.2, )",

View File

@ -35,6 +35,7 @@ public class CurrentContext : ICurrentContext
public virtual string ClientId { get; set; }
public virtual Version ClientVersion { get; set; }
public virtual ClientType ClientType { get; set; }
public virtual Guid? ServiceAccountOrganizationId { get; set; }
public CurrentContext(IProviderUserRepository providerUserRepository)
{
@ -146,6 +147,11 @@ public class CurrentContext : ICurrentContext
ClientType = c;
}
if (ClientType == ClientType.ServiceAccount)
{
ServiceAccountOrganizationId = new Guid(GetClaimValue(claimsDict, Claims.Organization));
}
DeviceIdentifier = GetClaimValue(claimsDict, Claims.Device);
Organizations = GetOrganizations(claimsDict, orgApi);
@ -445,6 +451,11 @@ public class CurrentContext : ICurrentContext
public bool AccessSecretsManager(Guid orgId)
{
if (ServiceAccountOrganizationId.HasValue && ServiceAccountOrganizationId.Value == orgId)
{
return true;
}
return Organizations?.Any(o => o.Id == orgId && o.AccessSecretsManager) ?? false;
}

View File

@ -24,9 +24,10 @@
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.0.150" />
<PackageReference Include="AWSSDK.SQS" Version="3.7.2.47" />
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.2.1" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.11.0" />
<PackageReference Include="Azure.Storage.Queues" Version="12.9.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
<PackageReference Include="BitPay.Light" Version="1.0.1907" />
<PackageReference Include="DnsClient" Version="1.7.0" />
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
<PackageReference Include="Handlebars.Net" Version="2.1.2" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />

View File

@ -28,6 +28,9 @@ public class Event : ITableObject<Guid>, IEvent
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
}
public Guid Id { get; set; }
@ -49,6 +52,9 @@ public class Event : ITableObject<Guid>, IEvent
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public void SetNewId()
{

View File

@ -2,6 +2,7 @@
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
namespace Bit.Core.Entities;
@ -198,4 +199,33 @@ public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorabl
return providers[provider];
}
public void UpdateFromLicense(OrganizationLicense license)
{
Name = license.Name;
BusinessName = license.BusinessName;
BillingEmail = license.BillingEmail;
PlanType = license.PlanType;
Seats = license.Seats;
MaxCollections = license.MaxCollections;
UseGroups = license.UseGroups;
UseDirectory = license.UseDirectory;
UseEvents = license.UseEvents;
UseTotp = license.UseTotp;
Use2fa = license.Use2fa;
UseApi = license.UseApi;
UsePolicies = license.UsePolicies;
UseSso = license.UseSso;
UseKeyConnector = license.UseKeyConnector;
UseScim = license.UseScim;
UseResetPassword = license.UseResetPassword;
SelfHost = license.SelfHost;
UsersGetPremium = license.UsersGetPremium;
UseCustomPermissions = license.UseCustomPermissions;
Plan = license.Plan;
Enabled = license.Enabled;
ExpirationDate = license.Expires;
LicenseKey = license.LicenseKey;
RevisionDate = DateTime.UtcNow;
}
}

View File

@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
namespace Bit.Core.Entities;
public class OrganizationDomain : ITableObject<Guid>
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public string Txt { get; set; }
[MaxLength(255)]
public string DomainName { get; set; }
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
public DateTime? VerifiedDate { get; private set; }
public DateTime NextRunDate { get; private set; }
public DateTime? LastCheckedDate { get; private set; }
public int JobRunCount { get; private set; }
public void SetNewId() => Id = CoreHelpers.GenerateComb();
public void SetNextRunDate(int interval)
{
//verification can take up to 72 hours
//1st job runs after 12hrs, 2nd after 24hrs and 3rd after 36hrs
NextRunDate = JobRunCount == 0
? CreationDate.AddHours(interval)
: NextRunDate.AddHours((JobRunCount + 1) * interval);
}
public void SetJobRunCount()
{
if (JobRunCount == 3)
{
return;
}
JobRunCount++;
}
public void SetVerifiedDate()
{
VerifiedDate = DateTime.UtcNow;
}
public void SetLastCheckedDate()
{
LastCheckedDate = DateTime.UtcNow;
}
}

View File

@ -62,7 +62,6 @@ public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscri
public bool UsesKeyConnector { get; set; }
public int FailedLoginCount { get; set; }
public DateTime? LastFailedLoginDate { get; set; }
public bool UnknownDeviceVerificationEnabled { get; set; }
[MaxLength(7)]
public string AvatarColor { get; set; }
public DateTime? LastPasswordChangeDate { get; set; }

View File

@ -48,4 +48,6 @@ public enum DeviceType : byte
SafariExtension = 20,
[Display(Name = "SDK")]
SDK = 21,
[Display(Name = "Server")]
Server = 22
}

View File

@ -2,5 +2,6 @@
public enum EventSystemUser : byte
{
SCIM = 1
SCIM = 1,
DomainVerification = 2
}

View File

@ -1,5 +1,6 @@
namespace Bit.Core.Enums;
// Increment by 100 for each new set of events
public enum EventType : int
{
User_LoggedIn = 1000,
@ -75,4 +76,11 @@ public enum EventType : int
ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902,
ProviderOrganization_VaultAccessed = 1903,
OrganizationDomain_Added = 2000,
OrganizationDomain_Removed = 2001,
OrganizationDomain_Verified = 2002,
OrganizationDomain_NotVerified = 2003,
Secret_Retrieved = 2100,
}

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