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:
14
.github/workflows/build-migrator-cli.yml
vendored
Normal file
14
.github/workflows/build-migrator-cli.yml
vendored
Normal 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"
|
24
.github/workflows/build-self-host.yml
vendored
24
.github/workflows/build-self-host.yml
vendored
@ -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' }}
|
||||
|
241
.github/workflows/build.yml
vendored
241
.github/workflows/build.yml
vendored
@ -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
|
||||
|
42
.github/workflows/cleanup-after-pr.yml
vendored
42
.github/workflows/cleanup-after-pr.yml
vendored
@ -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
|
||||
|
15
.github/workflows/container-registry-purge.yml
vendored
15
.github/workflows/container-registry-purge.yml
vendored
@ -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: |
|
||||
|
8
.github/workflows/database.yml
vendored
8
.github/workflows/database.yml
vendored
@ -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: |
|
||||
|
64
.github/workflows/release.yml
vendored
64
.github/workflows/release.yml
vendored
@ -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:
|
||||
|
@ -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>
|
||||
|
13
README.md
13
README.md
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
@ -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, )",
|
||||
|
@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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, )",
|
||||
|
@ -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
|
||||
|
@ -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, )",
|
||||
|
@ -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;
|
||||
|
@ -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, )",
|
||||
|
@ -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>>());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||
|
||||
public enum AccessPolicyType
|
||||
{
|
||||
UserProjectAccessPolicy,
|
||||
GroupProjectAccessPolicy,
|
||||
ServiceAccountProjectAccessPolicy,
|
||||
UserServiceAccountAccessPolicy,
|
||||
GroupServiceAccountAccessPolicy,
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace Bit.Commercial.Core.Test.SecretsManager.Enums;
|
||||
|
||||
public enum PermissionType
|
||||
{
|
||||
RunAsAdmin,
|
||||
RunAsUserWithPermission,
|
||||
}
|
@ -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>>());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 })));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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, )",
|
||||
|
@ -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, )",
|
||||
|
@ -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, )",
|
||||
|
@ -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 = @{
|
||||
|
@ -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
|
||||
|
@ -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, )",
|
||||
|
31
src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs
Normal file
31
src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs
Normal 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");
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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, )"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
143
src/Api/Controllers/OrganizationDomainController.cs
Normal file
143
src/Api/Controllers/OrganizationDomainController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
30
src/Api/Jobs/ValidateOrganizationDomainJob.cs
Normal file
30
src/Api/Jobs/ValidateOrganizationDomainJob.cs
Normal 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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
12
src/Api/Models/Request/OrganizationDomainRequestModel.cs
Normal file
12
src/Api/Models/Request/OrganizationDomainRequestModel.cs
Normal 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; }
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Models.Request.Organizations;
|
||||
|
||||
public class OrganizationDomainSsoDetailsRequestModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace Bit.Api.Models.Request.Organizations;
|
||||
|
||||
public class OrganizationEnrollSecretsManagerRequestModel
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
80
src/Api/SecretsManager/Controllers/SecretsTrashController.cs
Normal file
80
src/Api/SecretsManager/Controllers/SecretsTrashController.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class RevokeAccessTokensRequest
|
||||
{
|
||||
[Required]
|
||||
public Guid[] Ids { get; set; }
|
||||
}
|
@ -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,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
@ -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)
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
|
@ -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, )",
|
||||
|
@ -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, )",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
48
src/Core/Entities/OrganizationDomain.cs
Normal file
48
src/Core/Entities/OrganizationDomain.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
@ -48,4 +48,6 @@ public enum DeviceType : byte
|
||||
SafariExtension = 20,
|
||||
[Display(Name = "SDK")]
|
||||
SDK = 21,
|
||||
[Display(Name = "Server")]
|
||||
Server = 22
|
||||
}
|
||||
|
@ -2,5 +2,6 @@
|
||||
|
||||
public enum EventSystemUser : byte
|
||||
{
|
||||
SCIM = 1
|
||||
SCIM = 1,
|
||||
DomainVerification = 2
|
||||
}
|
||||
|
@ -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
Reference in New Issue
Block a user