mirror of
https://github.com/bitwarden/server.git
synced 2024-11-28 13:15:12 +01:00
Merge branch 'main' into PM-11162-assign-to-collection-perm-update
This commit is contained in:
commit
5c672ecdc1
19
.github/renovate.json
vendored
19
.github/renovate.json
vendored
@ -29,7 +29,7 @@
|
|||||||
"commitMessagePrefix": "[deps] DevOps:"
|
"commitMessagePrefix": "[deps] DevOps:"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackageNames": ["DnsClient", "Quartz"],
|
"matchPackageNames": ["DnsClient"],
|
||||||
"description": "Admin Console owned dependencies",
|
"description": "Admin Console owned dependencies",
|
||||||
"commitMessagePrefix": "[deps] AC:",
|
"commitMessagePrefix": "[deps] AC:",
|
||||||
"reviewers": ["team:team-admin-console-dev"]
|
"reviewers": ["team:team-admin-console-dev"]
|
||||||
@ -42,14 +42,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
"AspNetCoreRateLimit",
|
|
||||||
"AspNetCoreRateLimit.Redis",
|
|
||||||
"Azure.Data.Tables",
|
|
||||||
"Azure.Extensions.AspNetCore.DataProtection.Blobs",
|
"Azure.Extensions.AspNetCore.DataProtection.Blobs",
|
||||||
"Azure.Messaging.EventGrid",
|
|
||||||
"Azure.Messaging.ServiceBus",
|
|
||||||
"Azure.Storage.Blobs",
|
|
||||||
"Azure.Storage.Queues",
|
|
||||||
"DuoUniversal",
|
"DuoUniversal",
|
||||||
"Fido2.AspNet",
|
"Fido2.AspNet",
|
||||||
"Duende.IdentityServer",
|
"Duende.IdentityServer",
|
||||||
@ -128,8 +121,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
|
"AspNetCoreRateLimit",
|
||||||
|
"AspNetCoreRateLimit.Redis",
|
||||||
|
"Azure.Data.Tables",
|
||||||
|
"Azure.Messaging.EventGrid",
|
||||||
|
"Azure.Messaging.ServiceBus",
|
||||||
|
"Azure.Storage.Blobs",
|
||||||
|
"Azure.Storage.Queues",
|
||||||
"Microsoft.AspNetCore.Authentication.JwtBearer",
|
"Microsoft.AspNetCore.Authentication.JwtBearer",
|
||||||
"Microsoft.AspNetCore.Http"
|
"Microsoft.AspNetCore.Http",
|
||||||
|
"Quartz"
|
||||||
],
|
],
|
||||||
"description": "Platform owned dependencies",
|
"description": "Platform owned dependencies",
|
||||||
"commitMessagePrefix": "[deps] Platform:",
|
"commitMessagePrefix": "[deps] Platform:",
|
||||||
|
62
.github/workflows/build.yml
vendored
62
.github/workflows/build.yml
vendored
@ -7,18 +7,25 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
- "rc"
|
- "rc"
|
||||||
- "hotfix-rc"
|
- "hotfix-rc"
|
||||||
pull_request:
|
pull_request_target:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check-run:
|
||||||
|
name: Check PR run
|
||||||
|
uses: bitwarden/gh-actions/.github/workflows/check-run.yml@main
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Lint
|
name: Lint
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||||
@ -29,8 +36,7 @@ jobs:
|
|||||||
build-artifacts:
|
build-artifacts:
|
||||||
name: Build artifacts
|
name: Build artifacts
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs: lint
|
||||||
- lint
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -68,6 +74,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||||
@ -120,7 +128,9 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
permissions:
|
permissions:
|
||||||
security-events: write
|
security-events: write
|
||||||
needs: build-artifacts
|
needs:
|
||||||
|
- build-artifacts
|
||||||
|
- check-run
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -173,6 +183,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Check branch to publish
|
- name: Check branch to publish
|
||||||
env:
|
env:
|
||||||
@ -292,6 +304,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||||
@ -305,9 +319,9 @@ jobs:
|
|||||||
run: az acr login -n $_AZ_REGISTRY --only-show-errors
|
run: az acr login -n $_AZ_REGISTRY --only-show-errors
|
||||||
|
|
||||||
- name: Make Docker stubs
|
- name: Make Docker stubs
|
||||||
if: github.ref == 'refs/heads/main' ||
|
if: |
|
||||||
github.ref == 'refs/heads/rc' ||
|
github.event_name != 'pull_request_target'
|
||||||
github.ref == 'refs/heads/hotfix-rc'
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
run: |
|
run: |
|
||||||
# Set proper setup image based on branch
|
# Set proper setup image based on branch
|
||||||
case "$GITHUB_REF" in
|
case "$GITHUB_REF" in
|
||||||
@ -347,13 +361,17 @@ jobs:
|
|||||||
cd docker-stub/EU; zip -r ../../docker-stub-EU.zip *; cd ../..
|
cd docker-stub/EU; zip -r ../../docker-stub-EU.zip *; cd ../..
|
||||||
|
|
||||||
- name: Make Docker stub checksums
|
- name: Make Docker stub checksums
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
if: |
|
||||||
|
github.event_name != 'pull_request_target'
|
||||||
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
run: |
|
run: |
|
||||||
sha256sum docker-stub-US.zip > docker-stub-US-sha256.txt
|
sha256sum docker-stub-US.zip > docker-stub-US-sha256.txt
|
||||||
sha256sum docker-stub-EU.zip > docker-stub-EU-sha256.txt
|
sha256sum docker-stub-EU.zip > docker-stub-EU-sha256.txt
|
||||||
|
|
||||||
- name: Upload Docker stub US artifact
|
- name: Upload Docker stub US artifact
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
if: |
|
||||||
|
github.event_name != 'pull_request_target'
|
||||||
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
name: docker-stub-US.zip
|
name: docker-stub-US.zip
|
||||||
@ -361,7 +379,9 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Docker stub EU artifact
|
- name: Upload Docker stub EU artifact
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
if: |
|
||||||
|
github.event_name != 'pull_request_target'
|
||||||
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
name: docker-stub-EU.zip
|
name: docker-stub-EU.zip
|
||||||
@ -369,7 +389,9 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Docker stub US checksum artifact
|
- name: Upload Docker stub US checksum artifact
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
if: |
|
||||||
|
github.event_name != 'pull_request_target'
|
||||||
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
name: docker-stub-US-sha256.txt
|
name: docker-stub-US-sha256.txt
|
||||||
@ -377,7 +399,9 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Docker stub EU checksum artifact
|
- name: Upload Docker stub EU checksum artifact
|
||||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc'
|
if: |
|
||||||
|
github.event_name != 'pull_request_target'
|
||||||
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
name: docker-stub-EU-sha256.txt
|
name: docker-stub-EU-sha256.txt
|
||||||
@ -467,6 +491,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out repo
|
- name: Check out repo
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Set up .NET
|
- name: Set up .NET
|
||||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||||
@ -501,6 +527,7 @@ jobs:
|
|||||||
|
|
||||||
self-host-build:
|
self-host-build:
|
||||||
name: Trigger self-host build
|
name: Trigger self-host build
|
||||||
|
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: build-docker
|
needs: build-docker
|
||||||
steps:
|
steps:
|
||||||
@ -533,7 +560,7 @@ jobs:
|
|||||||
|
|
||||||
trigger-k8s-deploy:
|
trigger-k8s-deploy:
|
||||||
name: Trigger k8s deploy
|
name: Trigger k8s deploy
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: build-docker
|
needs: build-docker
|
||||||
steps:
|
steps:
|
||||||
@ -567,7 +594,9 @@ jobs:
|
|||||||
|
|
||||||
trigger-ee-updates:
|
trigger-ee-updates:
|
||||||
name: Trigger Ephemeral Environment updates
|
name: Trigger Ephemeral Environment updates
|
||||||
if: github.ref != 'refs/heads/main' && contains(github.event.pull_request.labels.*.name, 'ephemeral-environment')
|
if: |
|
||||||
|
github.event_name == 'pull_request_target'
|
||||||
|
&& contains(github.event.pull_request.labels.*.name, 'ephemeral-environment')
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: build-docker
|
needs: build-docker
|
||||||
steps:
|
steps:
|
||||||
@ -613,9 +642,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check if any job failed
|
- name: Check if any job failed
|
||||||
if: |
|
if: |
|
||||||
(github.ref == 'refs/heads/main'
|
github.event_name != 'pull_request_target'
|
||||||
|| github.ref == 'refs/heads/rc'
|
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
|
||||||
|| github.ref == 'refs/heads/hotfix-rc')
|
|
||||||
&& contains(needs.*.result, 'failure')
|
&& contains(needs.*.result, 'failure')
|
||||||
run: exit 1
|
run: exit 1
|
||||||
|
|
||||||
|
18
.github/workflows/test-database.yml
vendored
18
.github/workflows/test-database.yml
vendored
@ -70,6 +70,11 @@ jobs:
|
|||||||
docker compose --profile mssql --profile postgres --profile mysql up -d
|
docker compose --profile mssql --profile postgres --profile mysql up -d
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Add MariaDB for unified
|
||||||
|
# Use a different port than MySQL
|
||||||
|
run: |
|
||||||
|
docker run --detach --name mariadb --env MARIADB_ROOT_PASSWORD=mariadb-password -p 4306:3306 mariadb:10
|
||||||
|
|
||||||
# I've seen the SQL Server container not be ready for commands right after starting up and just needing a bit longer to be ready
|
# I've seen the SQL Server container not be ready for commands right after starting up and just needing a bit longer to be ready
|
||||||
- name: Sleep
|
- name: Sleep
|
||||||
run: sleep 15s
|
run: sleep 15s
|
||||||
@ -103,6 +108,12 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true"
|
CONN_STR: "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev;Allow User Variables=true"
|
||||||
|
|
||||||
|
- name: Migrate MariaDB
|
||||||
|
working-directory: "util/MySqlMigrations"
|
||||||
|
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:MySql:ConnectionString="$CONN_STR"'
|
||||||
|
env:
|
||||||
|
CONN_STR: "server=localhost;port=4306;uid=root;pwd=mariadb-password;database=vault_dev;Allow User Variables=true"
|
||||||
|
|
||||||
- name: Migrate Postgres
|
- name: Migrate Postgres
|
||||||
working-directory: "util/PostgresMigrations"
|
working-directory: "util/PostgresMigrations"
|
||||||
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:PostgreSql:ConnectionString="$CONN_STR"'
|
run: 'dotnet ef database update --connection "$CONN_STR" -- --GlobalSettings:PostgreSql:ConnectionString="$CONN_STR"'
|
||||||
@ -130,6 +141,9 @@ jobs:
|
|||||||
# Default Sqlite
|
# Default Sqlite
|
||||||
BW_TEST_DATABASES__3__TYPE: "Sqlite"
|
BW_TEST_DATABASES__3__TYPE: "Sqlite"
|
||||||
BW_TEST_DATABASES__3__CONNECTIONSTRING: "Data Source=${{ runner.temp }}/test.db"
|
BW_TEST_DATABASES__3__CONNECTIONSTRING: "Data Source=${{ runner.temp }}/test.db"
|
||||||
|
# Unified MariaDB
|
||||||
|
BW_TEST_DATABASES__4__TYPE: "MySql"
|
||||||
|
BW_TEST_DATABASES__4__CONNECTIONSTRING: "server=localhost;port=4306;uid=root;pwd=mariadb-password;database=vault_dev;Allow User Variables=true"
|
||||||
run: dotnet test --logger "trx;LogFileName=infrastructure-test-results.trx"
|
run: dotnet test --logger "trx;LogFileName=infrastructure-test-results.trx"
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
@ -137,6 +151,10 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
run: 'docker logs $(docker ps --quiet --filter "name=mysql")'
|
run: 'docker logs $(docker ps --quiet --filter "name=mysql")'
|
||||||
|
|
||||||
|
- name: Print MariaDB Logs
|
||||||
|
if: failure()
|
||||||
|
run: 'docker logs $(docker ps --quiet --filter "name=mariadb")'
|
||||||
|
|
||||||
- name: Print Postgres Logs
|
- name: Print Postgres Logs
|
||||||
if: failure()
|
if: failure()
|
||||||
run: 'docker logs $(docker ps --quiet --filter "name=postgres")'
|
run: 'docker logs $(docker ps --quiet --filter "name=postgres")'
|
||||||
|
@ -110,7 +110,6 @@ public static class RolePermissionMapping
|
|||||||
Permission.User_Licensing_View,
|
Permission.User_Licensing_View,
|
||||||
Permission.User_Billing_View,
|
Permission.User_Billing_View,
|
||||||
Permission.User_Billing_LaunchGateway,
|
Permission.User_Billing_LaunchGateway,
|
||||||
Permission.User_Delete,
|
|
||||||
Permission.Org_List_View,
|
Permission.Org_List_View,
|
||||||
Permission.Org_OrgInformation_View,
|
Permission.Org_OrgInformation_View,
|
||||||
Permission.Org_GeneralDetails_View,
|
Permission.Org_GeneralDetails_View,
|
||||||
|
@ -252,6 +252,12 @@ public class OrganizationsController : Controller
|
|||||||
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
|
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
&& (await _userService.GetOrganizationsManagingUserAsync(user.Id)).Any(x => x.Id == id))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Managed user account cannot leave managing organization. Contact your organization administrator for additional details.");
|
||||||
|
}
|
||||||
|
|
||||||
await _removeOrganizationUserCommand.RemoveUserAsync(id, user.Id);
|
await _removeOrganizationUserCommand.RemoveUserAsync(id, user.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,10 @@ public class CiphersController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CipherMiniResponseModel(cipher, _globalSettings, cipher.OrganizationUseTotp);
|
var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(cipher.OrganizationId.Value);
|
||||||
|
var collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
||||||
|
|
||||||
|
return new CipherMiniDetailsResponseModel(cipher, _globalSettings, collectionCiphersGroupDict, cipher.OrganizationUseTotp);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}/full-details")]
|
[HttpGet("{id}/full-details")]
|
||||||
@ -653,10 +656,10 @@ public class CiphersController : Controller
|
|||||||
|
|
||||||
[HttpPut("{id}/collections-admin")]
|
[HttpPut("{id}/collections-admin")]
|
||||||
[HttpPost("{id}/collections-admin")]
|
[HttpPost("{id}/collections-admin")]
|
||||||
public async Task PutCollectionsAdmin(string id, [FromBody] CipherCollectionsRequestModel model)
|
public async Task<CipherMiniDetailsResponseModel> PutCollectionsAdmin(string id, [FromBody] CipherCollectionsRequestModel model)
|
||||||
{
|
{
|
||||||
var userId = _userService.GetProperUserId(User).Value;
|
var userId = _userService.GetProperUserId(User).Value;
|
||||||
var cipher = await _cipherRepository.GetByIdAsync(new Guid(id));
|
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(new Guid(id));
|
||||||
|
|
||||||
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
if (cipher == null || !cipher.OrganizationId.HasValue ||
|
||||||
!await CanEditCipherAsAdminAsync(cipher.OrganizationId.Value, new[] { cipher.Id }))
|
!await CanEditCipherAsAdminAsync(cipher.OrganizationId.Value, new[] { cipher.Id }))
|
||||||
@ -674,6 +677,11 @@ public class CiphersController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _cipherService.SaveCollectionsAsync(cipher, collectionIds, userId, true);
|
await _cipherService.SaveCollectionsAsync(cipher, collectionIds, userId, true);
|
||||||
|
|
||||||
|
var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(cipher.OrganizationId.Value);
|
||||||
|
var collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
||||||
|
|
||||||
|
return new CipherMiniDetailsResponseModel(cipher, _globalSettings, collectionCiphersGroupDict, cipher.OrganizationUseTotp);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("bulk-collections")]
|
[HttpPost("bulk-collections")]
|
||||||
|
@ -151,6 +151,9 @@ public static class FeatureFlagKeys
|
|||||||
public const string GeneratorToolsModernization = "generator-tools-modernization";
|
public const string GeneratorToolsModernization = "generator-tools-modernization";
|
||||||
public const string NewDeviceVerification = "new-device-verification";
|
public const string NewDeviceVerification = "new-device-verification";
|
||||||
public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application";
|
public const string RiskInsightsCriticalApplication = "pm-14466-risk-insights-critical-application";
|
||||||
|
public const string IntegrationPage = "pm-14505-admin-console-integration-page";
|
||||||
|
public const string NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss";
|
||||||
|
public const string NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
{
|
{
|
||||||
|
@ -39,6 +39,7 @@ public class CurrentContext : ICurrentContext
|
|||||||
public virtual int? BotScore { get; set; }
|
public virtual int? BotScore { get; set; }
|
||||||
public virtual string ClientId { get; set; }
|
public virtual string ClientId { get; set; }
|
||||||
public virtual Version ClientVersion { get; set; }
|
public virtual Version ClientVersion { get; set; }
|
||||||
|
public virtual bool ClientVersionIsPrerelease { get; set; }
|
||||||
public virtual IdentityClientType IdentityClientType { get; set; }
|
public virtual IdentityClientType IdentityClientType { get; set; }
|
||||||
public virtual Guid? ServiceAccountOrganizationId { get; set; }
|
public virtual Guid? ServiceAccountOrganizationId { get; set; }
|
||||||
|
|
||||||
@ -97,6 +98,11 @@ public class CurrentContext : ICurrentContext
|
|||||||
{
|
{
|
||||||
ClientVersion = cVersion;
|
ClientVersion = cVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpContext.Request.Headers.TryGetValue("Is-Prerelease", out var clientVersionIsPrerelease))
|
||||||
|
{
|
||||||
|
ClientVersionIsPrerelease = clientVersionIsPrerelease == "1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async virtual Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings)
|
public async virtual Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings)
|
||||||
|
@ -29,12 +29,13 @@ public interface ICurrentContext
|
|||||||
int? BotScore { get; set; }
|
int? BotScore { get; set; }
|
||||||
string ClientId { get; set; }
|
string ClientId { get; set; }
|
||||||
Version ClientVersion { get; set; }
|
Version ClientVersion { get; set; }
|
||||||
|
bool ClientVersionIsPrerelease { get; set; }
|
||||||
|
|
||||||
Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings);
|
Task BuildAsync(HttpContext httpContext, GlobalSettings globalSettings);
|
||||||
Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings);
|
Task BuildAsync(ClaimsPrincipal user, GlobalSettings globalSettings);
|
||||||
|
|
||||||
Task SetContextAsync(ClaimsPrincipal user);
|
Task SetContextAsync(ClaimsPrincipal user);
|
||||||
|
|
||||||
|
|
||||||
Task<bool> OrganizationUser(Guid orgId);
|
Task<bool> OrganizationUser(Guid orgId);
|
||||||
Task<bool> OrganizationAdmin(Guid orgId);
|
Task<bool> OrganizationAdmin(Guid orgId);
|
||||||
Task<bool> OrganizationOwner(Guid orgId);
|
Task<bool> OrganizationOwner(Guid orgId);
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" />
|
<PackageReference Include="AspNetCoreRateLimit.Redis" Version="2.0.0" />
|
||||||
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.401.30" />
|
<PackageReference Include="AWSSDK.SimpleEmail" Version="3.7.401.35" />
|
||||||
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.40" />
|
<PackageReference Include="AWSSDK.SQS" Version="3.7.400.45" />
|
||||||
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
<PackageReference Include="Azure.Data.Tables" Version="12.9.0" />
|
||||||
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
<PackageReference Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.3.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="8.0.10" />
|
||||||
|
@ -20,6 +20,7 @@ public class LaunchDarklyFeatureService : IFeatureService
|
|||||||
private const string _contextKindServiceAccount = "service-account";
|
private const string _contextKindServiceAccount = "service-account";
|
||||||
|
|
||||||
private const string _contextAttributeClientVersion = "client-version";
|
private const string _contextAttributeClientVersion = "client-version";
|
||||||
|
private const string _contextAttributeClientVersionIsPrerelease = "client-version-is-prerelease";
|
||||||
private const string _contextAttributeDeviceType = "device-type";
|
private const string _contextAttributeDeviceType = "device-type";
|
||||||
private const string _contextAttributeClientType = "client-type";
|
private const string _contextAttributeClientType = "client-type";
|
||||||
private const string _contextAttributeOrganizations = "organizations";
|
private const string _contextAttributeOrganizations = "organizations";
|
||||||
@ -145,6 +146,7 @@ public class LaunchDarklyFeatureService : IFeatureService
|
|||||||
if (_currentContext.ClientVersion != null)
|
if (_currentContext.ClientVersion != null)
|
||||||
{
|
{
|
||||||
builder.Set(_contextAttributeClientVersion, _currentContext.ClientVersion.ToString());
|
builder.Set(_contextAttributeClientVersion, _currentContext.ClientVersion.ToString());
|
||||||
|
builder.Set(_contextAttributeClientVersionIsPrerelease, _currentContext.ClientVersionIsPrerelease);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentContext.DeviceType.HasValue)
|
if (_currentContext.DeviceType.HasValue)
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace Bit.Core.Tools.Entities;
|
||||||
|
|
||||||
public class PasswordHealthReportApplication : ITableObject<Guid>, IRevisable
|
public class PasswordHealthReportApplication : ITableObject<Guid>, IRevisable
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid OrganizationId { get; set; }
|
public Guid OrganizationId { get; set; }
|
||||||
public string Uri { get; set; }
|
public string? Uri { get; set; }
|
||||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
|
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Tools.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Tools.Repositories;
|
||||||
|
|
||||||
|
public interface IPasswordHealthReportApplicationRepository : IRepository<PasswordHealthReportApplication, Guid>
|
||||||
|
{
|
||||||
|
Task<ICollection<PasswordHealthReportApplication>> GetByOrganizationIdAsync(Guid organizationId);
|
||||||
|
}
|
@ -121,8 +121,8 @@ public class TwoFactorAuthenticationValidator(
|
|||||||
|
|
||||||
var twoFactorResultDict = new Dictionary<string, object>
|
var twoFactorResultDict = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ "TwoFactorProviders", null },
|
{ "TwoFactorProviders", providers.Keys }, // backwards compatibility
|
||||||
{ "TwoFactorProviders2", providers }, // backwards compatibility
|
{ "TwoFactorProviders2", providers },
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we have email as a 2FA provider, we might need an SsoEmail2fa Session Token
|
// If we have email as a 2FA provider, we might need an SsoEmail2fa Session Token
|
||||||
|
@ -58,6 +58,7 @@ public static class DapperServiceCollectionExtensions
|
|||||||
services.AddSingleton<INotificationStatusRepository, NotificationStatusRepository>();
|
services.AddSingleton<INotificationStatusRepository, NotificationStatusRepository>();
|
||||||
services
|
services
|
||||||
.AddSingleton<IClientOrganizationMigrationRecordRepository, ClientOrganizationMigrationRecordRepository>();
|
.AddSingleton<IClientOrganizationMigrationRecordRepository, ClientOrganizationMigrationRecordRepository>();
|
||||||
|
services.AddSingleton<IPasswordHealthReportApplicationRepository, PasswordHealthReportApplicationRepository>();
|
||||||
|
|
||||||
if (selfHosted)
|
if (selfHosted)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Tools.Repositories;
|
||||||
|
using Bit.Infrastructure.Dapper.Repositories;
|
||||||
|
using Dapper;
|
||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
using ToolsEntities = Bit.Core.Tools.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.Dapper.Tools.Repositories;
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplicationRepository : Repository<ToolsEntities.PasswordHealthReportApplication, Guid>, IPasswordHealthReportApplicationRepository
|
||||||
|
{
|
||||||
|
public PasswordHealthReportApplicationRepository(GlobalSettings globalSettings)
|
||||||
|
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public PasswordHealthReportApplicationRepository(string connectionString, string readOnlyConnectionString)
|
||||||
|
: base(connectionString, readOnlyConnectionString)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async Task<ICollection<ToolsEntities.PasswordHealthReportApplication>> GetByOrganizationIdAsync(Guid organizationId)
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(ReadOnlyConnectionString))
|
||||||
|
{
|
||||||
|
var results = await connection.QueryAsync<ToolsEntities.PasswordHealthReportApplication>(
|
||||||
|
$"[{Schema}].[PasswordHealthReportApplication_ReadByOrganizationId]",
|
||||||
|
new { OrganizationId = organizationId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return results.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using Bit.Infrastructure.EntityFramework.Converters;
|
|||||||
using Bit.Infrastructure.EntityFramework.Models;
|
using Bit.Infrastructure.EntityFramework.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.NotificationCenter.Models;
|
using Bit.Infrastructure.EntityFramework.NotificationCenter.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
using Bit.Infrastructure.EntityFramework.SecretsManager.Models;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
@ -75,6 +76,7 @@ public class DatabaseContext : DbContext
|
|||||||
public DbSet<Notification> Notifications { get; set; }
|
public DbSet<Notification> Notifications { get; set; }
|
||||||
public DbSet<NotificationStatus> NotificationStatuses { get; set; }
|
public DbSet<NotificationStatus> NotificationStatuses { get; set; }
|
||||||
public DbSet<ClientOrganizationMigrationRecord> ClientOrganizationMigrationRecords { get; set; }
|
public DbSet<ClientOrganizationMigrationRecord> ClientOrganizationMigrationRecords { get; set; }
|
||||||
|
public DbSet<PasswordHealthReportApplication> PasswordHealthReportApplications { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
using Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EntityFramework.Tools.Configurations;
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplicationEntityTypeConfiguration : IEntityTypeConfiguration<PasswordHealthReportApplication>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PasswordHealthReportApplication> builder)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.Property(s => s.Id)
|
||||||
|
.ValueGeneratedNever();
|
||||||
|
|
||||||
|
builder.HasIndex(s => s.Id)
|
||||||
|
.IsClustered(true);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasIndex(s => s.OrganizationId)
|
||||||
|
.IsClustered(false);
|
||||||
|
|
||||||
|
builder.ToTable(nameof(PasswordHealthReportApplication));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Bit.Infrastructure.EntityFramework.AdminConsole.Models;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplication : Core.Tools.Entities.PasswordHealthReportApplication
|
||||||
|
{
|
||||||
|
public virtual Organization Organization { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplicationProfile : Profile
|
||||||
|
{
|
||||||
|
public PasswordHealthReportApplicationProfile()
|
||||||
|
{
|
||||||
|
CreateMap<Core.Tools.Entities.PasswordHealthReportApplication, PasswordHealthReportApplication>()
|
||||||
|
.ReverseMap();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using Bit.Core.Tools.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||||
|
using LinqToDB;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using AdminConsoleEntities = Bit.Core.Tools.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplicationRepository :
|
||||||
|
Repository<AdminConsoleEntities.PasswordHealthReportApplication, PasswordHealthReportApplication, Guid>,
|
||||||
|
IPasswordHealthReportApplicationRepository
|
||||||
|
{
|
||||||
|
public PasswordHealthReportApplicationRepository(IServiceScopeFactory serviceScopeFactory,
|
||||||
|
IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.PasswordHealthReportApplications)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async Task<ICollection<AdminConsoleEntities.PasswordHealthReportApplication>> GetByOrganizationIdAsync(Guid organizationId)
|
||||||
|
{
|
||||||
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var results = await dbContext.PasswordHealthReportApplications
|
||||||
|
.Where(p => p.OrganizationId == organizationId)
|
||||||
|
.ToListAsync();
|
||||||
|
return Mapper.Map<ICollection<AdminConsoleEntities.PasswordHealthReportApplication>>(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -51,7 +51,6 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
|
|
||||||
private readonly OrganizationsController _sut;
|
private readonly OrganizationsController _sut;
|
||||||
|
|
||||||
public OrganizationsControllerTests()
|
public OrganizationsControllerTests()
|
||||||
@ -123,7 +122,8 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_currentContext.OrganizationUser(orgId).Returns(true);
|
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||||
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||||
|
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization> { null });
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
|
||||||
|
|
||||||
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
|
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
|
||||||
@ -132,6 +132,36 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, AutoData]
|
||||||
|
public async Task OrganizationsController_UserCannotLeaveOrganizationThatManagesUser(
|
||||||
|
Guid orgId, User user)
|
||||||
|
{
|
||||||
|
var ssoConfig = new SsoConfig
|
||||||
|
{
|
||||||
|
Id = default,
|
||||||
|
Data = new SsoConfigurationData
|
||||||
|
{
|
||||||
|
MemberDecryptionType = MemberDecryptionType.KeyConnector
|
||||||
|
}.Serialize(),
|
||||||
|
Enabled = true,
|
||||||
|
OrganizationId = orgId,
|
||||||
|
};
|
||||||
|
var foundOrg = new Organization();
|
||||||
|
foundOrg.Id = orgId;
|
||||||
|
|
||||||
|
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||||
|
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||||
|
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||||
|
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization> { { foundOrg } });
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
|
||||||
|
|
||||||
|
Assert.Contains("Managed user account cannot leave managing organization. Contact your organization administrator for additional details.",
|
||||||
|
exception.Message);
|
||||||
|
|
||||||
|
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineAutoData(true, false)]
|
[InlineAutoData(true, false)]
|
||||||
[InlineAutoData(false, true)]
|
[InlineAutoData(false, true)]
|
||||||
@ -157,6 +187,8 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_currentContext.OrganizationUser(orgId).Returns(true);
|
_currentContext.OrganizationUser(orgId).Returns(true);
|
||||||
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
|
||||||
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||||
|
_userService.GetOrganizationsManagingUserAsync(user.Id).Returns(new List<Organization>());
|
||||||
|
|
||||||
await _sut.Leave(orgId);
|
await _sut.Leave(orgId);
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ public class LaunchDarklyFeatureServiceTests
|
|||||||
var currentContext = Substitute.For<ICurrentContext>();
|
var currentContext = Substitute.For<ICurrentContext>();
|
||||||
currentContext.UserId.Returns(Guid.NewGuid());
|
currentContext.UserId.Returns(Guid.NewGuid());
|
||||||
currentContext.ClientVersion.Returns(new Version(AssemblyHelpers.GetVersion()));
|
currentContext.ClientVersion.Returns(new Version(AssemblyHelpers.GetVersion()));
|
||||||
|
currentContext.ClientVersionIsPrerelease.Returns(true);
|
||||||
currentContext.DeviceType.Returns(Enums.DeviceType.ChromeBrowser);
|
currentContext.DeviceType.Returns(Enums.DeviceType.ChromeBrowser);
|
||||||
|
|
||||||
var client = Substitute.For<ILdClient>();
|
var client = Substitute.For<ILdClient>();
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.2" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
@ -9,6 +9,7 @@ using Bit.Infrastructure.EntityFramework.AdminConsole.Models.Provider;
|
|||||||
using Bit.Infrastructure.EntityFramework.Auth.Models;
|
using Bit.Infrastructure.EntityFramework.Auth.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.Models;
|
using Bit.Infrastructure.EntityFramework.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Tools.Models;
|
||||||
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
using Bit.Infrastructure.EntityFramework.Vault.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -89,6 +90,7 @@ public class EfRepositoryListBuilder<T> : ISpecimenBuilder where T : BaseEntityF
|
|||||||
cfg.AddProfile<TaxRateMapperProfile>();
|
cfg.AddProfile<TaxRateMapperProfile>();
|
||||||
cfg.AddProfile<TransactionMapperProfile>();
|
cfg.AddProfile<TransactionMapperProfile>();
|
||||||
cfg.AddProfile<UserMapperProfile>();
|
cfg.AddProfile<UserMapperProfile>();
|
||||||
|
cfg.AddProfile<PasswordHealthReportApplicationProfile>();
|
||||||
})
|
})
|
||||||
.CreateMapper()));
|
.CreateMapper()));
|
||||||
|
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
using AutoFixture;
|
||||||
|
using AutoFixture.Kernel;
|
||||||
|
using Bit.Core.Tools.Entities;
|
||||||
|
using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
using Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||||
|
|
||||||
|
internal class PasswordHealthReportApplicationBuilder : ISpecimenBuilder
|
||||||
|
{
|
||||||
|
public object Create(object request, ISpecimenContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = request as Type;
|
||||||
|
if (type == null || type != typeof(PasswordHealthReportApplication))
|
||||||
|
{
|
||||||
|
return new NoSpecimen();
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixture = new Fixture();
|
||||||
|
var obj = fixture.WithAutoNSubstitutions().Create<PasswordHealthReportApplication>();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfPasswordHealthReportApplication : ICustomization
|
||||||
|
{
|
||||||
|
public void Customize(IFixture fixture)
|
||||||
|
{
|
||||||
|
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||||
|
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||||
|
fixture.Customizations.Add(new PasswordHealthReportApplicationBuilder());
|
||||||
|
fixture.Customizations.Add(new OrganizationBuilder());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<PasswordHealthReportApplicationRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfPasswordHealthReportApplicationApplicableToUser : ICustomization
|
||||||
|
{
|
||||||
|
public void Customize(IFixture fixture)
|
||||||
|
{
|
||||||
|
fixture.Customizations.Add(new IgnoreVirtualMembersCustomization());
|
||||||
|
fixture.Customizations.Add(new GlobalSettingsBuilder());
|
||||||
|
fixture.Customizations.Add(new PasswordHealthReportApplicationBuilder());
|
||||||
|
fixture.Customizations.Add(new OrganizationBuilder());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<PasswordHealthReportApplicationRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<UserRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<OrganizationUserRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderUserRepository>());
|
||||||
|
fixture.Customizations.Add(new EfRepositoryListBuilder<ProviderOrganizationRepository>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfPasswordHealthReportApplicationAutoDataAttribute : CustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public EfPasswordHealthReportApplicationAutoDataAttribute() : base(new SutProviderCustomization(), new EfPasswordHealthReportApplication())
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EfPasswordHealthReportApplicationApplicableToUserInlineAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public EfPasswordHealthReportApplicationApplicableToUserInlineAutoDataAttribute(params object[] values) :
|
||||||
|
base(new[] { typeof(SutProviderCustomization), typeof(EfPasswordHealthReportApplicationApplicableToUser) }, values)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InlineEfPasswordHealthReportApplicationAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public InlineEfPasswordHealthReportApplicationAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||||
|
typeof(EfPolicy) }, values)
|
||||||
|
{ }
|
||||||
|
}
|
@ -0,0 +1,269 @@
|
|||||||
|
using AutoFixture;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Test.AutoFixture.Attributes;
|
||||||
|
using Bit.Core.Tools.Entities;
|
||||||
|
using Bit.Core.Tools.Repositories;
|
||||||
|
using Bit.Infrastructure.EFIntegration.Test.AutoFixture;
|
||||||
|
using Xunit;
|
||||||
|
using EfRepo = Bit.Infrastructure.EntityFramework.Repositories;
|
||||||
|
using EfToolsRepo = Bit.Infrastructure.EntityFramework.Tools.Repositories;
|
||||||
|
using SqlAdminConsoleRepo = Bit.Infrastructure.Dapper.Tools.Repositories;
|
||||||
|
using SqlRepo = Bit.Infrastructure.Dapper.Repositories;
|
||||||
|
|
||||||
|
namespace Bit.Infrastructure.EFIntegration.Test.Tools.Repositories;
|
||||||
|
|
||||||
|
public class PasswordHealthReportApplicationRepositoryTests
|
||||||
|
{
|
||||||
|
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||||
|
public async Task CreateAsync_Works_DataMatches(
|
||||||
|
PasswordHealthReportApplication passwordHealthReportApplication,
|
||||||
|
Organization organization,
|
||||||
|
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||||
|
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||||
|
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||||
|
SqlRepo.OrganizationRepository sqlOrganizationRepo
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var passwordHealthReportApplicationRecords = new List<PasswordHealthReportApplication>();
|
||||||
|
foreach (var sut in suts)
|
||||||
|
{
|
||||||
|
var i = suts.IndexOf(sut);
|
||||||
|
|
||||||
|
var efOrganization = await efOrganizationRepos[i].CreateAsync(organization);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
passwordHealthReportApplication.OrganizationId = efOrganization.Id;
|
||||||
|
var postEfPasswordHeathReportApp = await sut.CreateAsync(passwordHealthReportApplication);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
var savedPasswordHealthReportApplication = await sut.GetByIdAsync(postEfPasswordHeathReportApp.Id);
|
||||||
|
passwordHealthReportApplicationRecords.Add(savedPasswordHealthReportApplication);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organization);
|
||||||
|
|
||||||
|
passwordHealthReportApplication.OrganizationId = sqlOrganization.Id;
|
||||||
|
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.CreateAsync(passwordHealthReportApplication);
|
||||||
|
var savedSqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||||
|
passwordHealthReportApplicationRecords.Add(savedSqlPasswordHealthReportApplicationRecord);
|
||||||
|
|
||||||
|
Assert.True(passwordHealthReportApplicationRecords.Count == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||||
|
public async Task RetrieveByOrganisation_Works(
|
||||||
|
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||||
|
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||||
|
{
|
||||||
|
var (firstOrg, firstRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||||
|
var (secondOrg, secondRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||||
|
|
||||||
|
var firstSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(firstOrg.Id);
|
||||||
|
var nextSetOfRecords = await sqlPasswordHealthReportApplicationRepo.GetByOrganizationIdAsync(secondOrg.Id);
|
||||||
|
|
||||||
|
Assert.True(firstSetOfRecords.Count == 1 && firstSetOfRecords.First().OrganizationId == firstOrg.Id);
|
||||||
|
Assert.True(nextSetOfRecords.Count == 1 && nextSetOfRecords.First().OrganizationId == secondOrg.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||||
|
public async Task ReplaceQuery_Works(
|
||||||
|
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||||
|
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||||
|
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||||
|
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||||
|
{
|
||||||
|
var (org, pwdRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||||
|
var exampleUri = "http://www.example.com";
|
||||||
|
var exampleRevisionDate = new DateTime(2021, 1, 1);
|
||||||
|
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||||
|
|
||||||
|
foreach (var sut in suts)
|
||||||
|
{
|
||||||
|
var i = suts.IndexOf(sut);
|
||||||
|
|
||||||
|
// create a new organization for each repository
|
||||||
|
var organization = await efOrganizationRepos[i].CreateAsync(org);
|
||||||
|
|
||||||
|
// map the organization Id and create the PasswordHealthReportApp record
|
||||||
|
pwdRecord.OrganizationId = organization.Id;
|
||||||
|
var passwordHealthReportApplication = await sut.CreateAsync(pwdRecord);
|
||||||
|
|
||||||
|
// update the record with new values
|
||||||
|
passwordHealthReportApplication.Uri = exampleUri;
|
||||||
|
passwordHealthReportApplication.RevisionDate = exampleRevisionDate;
|
||||||
|
|
||||||
|
// apply update to the database
|
||||||
|
await sut.ReplaceAsync(passwordHealthReportApplication);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
// retrieve the data and add to the list for assertions
|
||||||
|
var recordFromDb = await sut.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
dbRecords.Add(recordFromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql - create a new organization and PasswordHealthReportApplication record
|
||||||
|
var (sqlOrg, sqlPwdRecord) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||||
|
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPwdRecord.Id);
|
||||||
|
|
||||||
|
// sql - update the record with new values
|
||||||
|
sqlPasswordHealthReportApplicationRecord.Uri = exampleUri;
|
||||||
|
sqlPasswordHealthReportApplicationRecord.RevisionDate = exampleRevisionDate;
|
||||||
|
await sqlPasswordHealthReportApplicationRepo.ReplaceAsync(sqlPasswordHealthReportApplicationRecord);
|
||||||
|
|
||||||
|
// sql - retrieve the data and add to the list for assertions
|
||||||
|
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||||
|
dbRecords.Add(sqlDbRecord);
|
||||||
|
|
||||||
|
// assertions
|
||||||
|
// the Guids must be distinct across all records
|
||||||
|
Assert.True(dbRecords.Select(_ => _.Id).Distinct().Count() == dbRecords.Count);
|
||||||
|
|
||||||
|
// the Uri and RevisionDate must match the updated values
|
||||||
|
Assert.True(dbRecords.All(_ => _.Uri == exampleUri && _.RevisionDate == exampleRevisionDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||||
|
public async Task Upsert_Works(
|
||||||
|
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||||
|
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||||
|
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||||
|
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||||
|
{
|
||||||
|
var fixture = new Fixture();
|
||||||
|
var rawOrg = fixture.Build<Organization>().Create();
|
||||||
|
var rawPwdRecord = fixture.Build<PasswordHealthReportApplication>()
|
||||||
|
.With(_ => _.OrganizationId, rawOrg.Id)
|
||||||
|
.Without(_ => _.Id)
|
||||||
|
.Create();
|
||||||
|
var exampleUri = "http://www.example.com";
|
||||||
|
var exampleRevisionDate = new DateTime(2021, 1, 1);
|
||||||
|
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||||
|
|
||||||
|
foreach (var sut in suts)
|
||||||
|
{
|
||||||
|
var i = suts.IndexOf(sut);
|
||||||
|
|
||||||
|
// create a new organization for each repository
|
||||||
|
var organization = await efOrganizationRepos[i].CreateAsync(rawOrg);
|
||||||
|
|
||||||
|
// map the organization Id and use Upsert to save new record
|
||||||
|
rawPwdRecord.OrganizationId = organization.Id;
|
||||||
|
rawPwdRecord.Id = default(Guid);
|
||||||
|
await sut.UpsertAsync(rawPwdRecord);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
// retrieve the data and add to the list for assertions
|
||||||
|
var passwordHealthReportApplication = await sut.GetByIdAsync(rawPwdRecord.Id);
|
||||||
|
|
||||||
|
// update the record with new values
|
||||||
|
passwordHealthReportApplication.Uri = exampleUri;
|
||||||
|
passwordHealthReportApplication.RevisionDate = exampleRevisionDate;
|
||||||
|
|
||||||
|
// apply update using Upsert to make changes to db
|
||||||
|
await sut.UpsertAsync(passwordHealthReportApplication);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
// retrieve the data and add to the list for assertions
|
||||||
|
var recordFromDb = await sut.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||||
|
dbRecords.Add(recordFromDb);
|
||||||
|
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql - create new records
|
||||||
|
var organizationForSql = fixture.Create<Organization>();
|
||||||
|
var passwordHealthReportApplicationForSql = fixture.Build<PasswordHealthReportApplication>()
|
||||||
|
.With(_ => _.OrganizationId, organizationForSql.Id)
|
||||||
|
.Without(_ => _.Id)
|
||||||
|
.Create();
|
||||||
|
|
||||||
|
// sql - use Upsert to insert this data
|
||||||
|
var sqlOrganization = await sqlOrganizationRepo.CreateAsync(organizationForSql);
|
||||||
|
await sqlPasswordHealthReportApplicationRepo.UpsertAsync(passwordHealthReportApplicationForSql);
|
||||||
|
var sqlPasswordHealthReportApplicationRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(passwordHealthReportApplicationForSql.Id);
|
||||||
|
|
||||||
|
// sql - update the record with new values
|
||||||
|
sqlPasswordHealthReportApplicationRecord.Uri = exampleUri;
|
||||||
|
sqlPasswordHealthReportApplicationRecord.RevisionDate = exampleRevisionDate;
|
||||||
|
await sqlPasswordHealthReportApplicationRepo.UpsertAsync(sqlPasswordHealthReportApplicationRecord);
|
||||||
|
|
||||||
|
// sql - retrieve the data and add to the list for assertions
|
||||||
|
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(sqlPasswordHealthReportApplicationRecord.Id);
|
||||||
|
dbRecords.Add(sqlDbRecord);
|
||||||
|
|
||||||
|
// assertions
|
||||||
|
// the Guids must be distinct across all records
|
||||||
|
Assert.True(dbRecords.Select(_ => _.Id).Distinct().Count() == dbRecords.Count);
|
||||||
|
|
||||||
|
// the Uri and RevisionDate must match the updated values
|
||||||
|
Assert.True(dbRecords.All(_ => _.Uri == exampleUri && _.RevisionDate == exampleRevisionDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CiSkippedTheory, EfPasswordHealthReportApplicationAutoData]
|
||||||
|
public async Task Delete_Works(
|
||||||
|
List<EfToolsRepo.PasswordHealthReportApplicationRepository> suts,
|
||||||
|
List<EfRepo.OrganizationRepository> efOrganizationRepos,
|
||||||
|
SqlAdminConsoleRepo.PasswordHealthReportApplicationRepository sqlPasswordHealthReportApplicationRepo,
|
||||||
|
SqlRepo.OrganizationRepository sqlOrganizationRepo)
|
||||||
|
{
|
||||||
|
var fixture = new Fixture();
|
||||||
|
var rawOrg = fixture.Build<Organization>().Create();
|
||||||
|
var rawPwdRecord = fixture.Build<PasswordHealthReportApplication>()
|
||||||
|
.With(_ => _.OrganizationId, rawOrg.Id)
|
||||||
|
.Create();
|
||||||
|
var dbRecords = new List<PasswordHealthReportApplication>();
|
||||||
|
|
||||||
|
foreach (var sut in suts)
|
||||||
|
{
|
||||||
|
var i = suts.IndexOf(sut);
|
||||||
|
|
||||||
|
// create a new organization for each repository
|
||||||
|
var organization = await efOrganizationRepos[i].CreateAsync(rawOrg);
|
||||||
|
|
||||||
|
// map the organization Id and use Upsert to save new record
|
||||||
|
rawPwdRecord.OrganizationId = organization.Id;
|
||||||
|
rawPwdRecord = await sut.CreateAsync(rawPwdRecord);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
// apply update using Upsert to make changes to db
|
||||||
|
await sut.DeleteAsync(rawPwdRecord);
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
|
||||||
|
// retrieve the data and add to the list for assertions
|
||||||
|
var recordFromDb = await sut.GetByIdAsync(rawPwdRecord.Id);
|
||||||
|
dbRecords.Add(recordFromDb);
|
||||||
|
|
||||||
|
sut.ClearChangeTracking();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sql - create new records
|
||||||
|
var (org, passwordHealthReportApplication) = await CreateSampleRecord(sqlOrganizationRepo, sqlPasswordHealthReportApplicationRepo);
|
||||||
|
await sqlPasswordHealthReportApplicationRepo.DeleteAsync(passwordHealthReportApplication);
|
||||||
|
var sqlDbRecord = await sqlPasswordHealthReportApplicationRepo.GetByIdAsync(passwordHealthReportApplication.Id);
|
||||||
|
dbRecords.Add(sqlDbRecord);
|
||||||
|
|
||||||
|
// assertions
|
||||||
|
// all records should be null - as they were deleted before querying
|
||||||
|
Assert.True(dbRecords.Where(_ => _ == null).Count() == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(Organization, PasswordHealthReportApplication)> CreateSampleRecord(
|
||||||
|
IOrganizationRepository organizationRepo,
|
||||||
|
IPasswordHealthReportApplicationRepository passwordHealthReportApplicationRepo
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var fixture = new Fixture();
|
||||||
|
var organization = fixture.Create<Organization>();
|
||||||
|
var passwordHealthReportApplication = fixture.Build<PasswordHealthReportApplication>()
|
||||||
|
.With(_ => _.OrganizationId, organization.Id)
|
||||||
|
.Create();
|
||||||
|
|
||||||
|
organization = await organizationRepo.CreateAsync(organization);
|
||||||
|
passwordHealthReportApplication = await passwordHealthReportApplicationRepo.CreateAsync(passwordHealthReportApplication);
|
||||||
|
|
||||||
|
return (organization, passwordHealthReportApplication);
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,9 @@ SET
|
|||||||
U.TwoFactorProviders = JSON_SET(
|
U.TwoFactorProviders = JSON_SET(
|
||||||
JSON_SET(
|
JSON_SET(
|
||||||
U.TwoFactorProviders, '$."2".MetaData.ClientSecret',
|
U.TwoFactorProviders, '$."2".MetaData.ClientSecret',
|
||||||
JSON_UNQUOTE(U.TwoFactorProviders ->'$."2".MetaData.SKey')),
|
JSON_UNQUOTE(JSON_EXTRACT(U.TwoFactorProviders,'$."2".MetaData.SKey'))),
|
||||||
'$."2".MetaData.ClientId',
|
'$."2".MetaData.ClientId',
|
||||||
JSON_UNQUOTE(U.TwoFactorProviders -> '$."2".MetaData.IKey'))
|
JSON_UNQUOTE(JSON_EXTRACT(U.TwoFactorProviders,'$."2".MetaData.IKey')))
|
||||||
WHERE
|
WHERE
|
||||||
JSON_CONTAINS(TwoFactorProviders,
|
JSON_CONTAINS(TwoFactorProviders,
|
||||||
'{"2":{}}')
|
'{"2":{}}')
|
||||||
@ -20,9 +20,9 @@ SET
|
|||||||
o.TwoFactorProviders = JSON_SET(
|
o.TwoFactorProviders = JSON_SET(
|
||||||
JSON_SET(
|
JSON_SET(
|
||||||
o.TwoFactorProviders, '$."6".MetaData.ClientSecret',
|
o.TwoFactorProviders, '$."6".MetaData.ClientSecret',
|
||||||
JSON_UNQUOTE(o.TwoFactorProviders ->'$."6".MetaData.SKey')),
|
JSON_UNQUOTE(JSON_EXTRACT(o.TwoFactorProviders,'$."6".MetaData.SKey'))),
|
||||||
'$."6".MetaData.ClientId',
|
'$."6".MetaData.ClientId',
|
||||||
JSON_UNQUOTE(o.TwoFactorProviders -> '$."6".MetaData.IKey'))
|
JSON_UNQUOTE(JSON_EXTRACT(o.TwoFactorProviders,'$."6".MetaData.IKey')))
|
||||||
WHERE
|
WHERE
|
||||||
JSON_CONTAINS(o.TwoFactorProviders,
|
JSON_CONTAINS(o.TwoFactorProviders,
|
||||||
'{"6":{}}')
|
'{"6":{}}')
|
||||||
|
2888
util/MySqlMigrations/Migrations/20241105195202_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
2888
util/MySqlMigrations/Migrations/20241105195202_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Bit.MySqlMigrations.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixPasswordHealthReportApplication : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// this file is not required, but the designer file is required
|
||||||
|
// in order to keep the database models in sync with the database
|
||||||
|
// without this - the unit tests will fail when run on your local machine
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1887,6 +1887,34 @@ namespace Bit.MySqlMigrations.Migrations
|
|||||||
b.ToTable("ServiceAccount", (string)null);
|
b.ToTable("ServiceAccount", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<Guid>("OrganizationId")
|
||||||
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RevisionDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Id")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", true);
|
||||||
|
|
||||||
|
b.HasIndex("OrganizationId")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", false);
|
||||||
|
|
||||||
|
b.ToTable("PasswordHealthReportApplication", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -2578,6 +2606,17 @@ namespace Bit.MySqlMigrations.Migrations
|
|||||||
b.Navigation("Organization");
|
b.Navigation("Organization");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrganizationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Organization");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
2894
util/PostgresMigrations/Migrations/20241105202053_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
2894
util/PostgresMigrations/Migrations/20241105202053_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Bit.PostgresMigrations.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixPasswordHealthReportApplication : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// this file is not required, but the designer file is required
|
||||||
|
// in order to keep the database models in sync with the database
|
||||||
|
// without this - the unit tests will fail when run on your local machine
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1893,6 +1893,34 @@ namespace Bit.PostgresMigrations.Migrations
|
|||||||
b.ToTable("ServiceAccount", (string)null);
|
b.ToTable("ServiceAccount", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("OrganizationId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RevisionDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Id")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", true);
|
||||||
|
|
||||||
|
b.HasIndex("OrganizationId")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", false);
|
||||||
|
|
||||||
|
b.ToTable("PasswordHealthReportApplication", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -2584,6 +2612,17 @@ namespace Bit.PostgresMigrations.Migrations
|
|||||||
b.Navigation("Organization");
|
b.Navigation("Organization");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrganizationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Organization");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
2877
util/SqliteMigrations/Migrations/20241105202413_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
2877
util/SqliteMigrations/Migrations/20241105202413_FixPasswordHealthReportApplication.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Bit.SqliteMigrations.Migrations;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixPasswordHealthReportApplication : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// this file is not required, but the designer file is required
|
||||||
|
// in order to keep the database models in sync with the database
|
||||||
|
// without this - the unit tests will fail when run on your local machine
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1876,6 +1876,34 @@ namespace Bit.SqliteMigrations.Migrations
|
|||||||
b.ToTable("ServiceAccount", (string)null);
|
b.ToTable("ServiceAccount", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreationDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("OrganizationId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RevisionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Id")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", true);
|
||||||
|
|
||||||
|
b.HasIndex("OrganizationId")
|
||||||
|
.HasAnnotation("SqlServer:Clustered", false);
|
||||||
|
|
||||||
|
b.ToTable("PasswordHealthReportApplication", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -2567,6 +2595,17 @@ namespace Bit.SqliteMigrations.Migrations
|
|||||||
b.Navigation("Organization");
|
b.Navigation("Organization");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Tools.Models.PasswordHealthReportApplication", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OrganizationId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Organization");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
|
||||||
|
Loading…
Reference in New Issue
Block a user