mirror of
https://github.com/bitwarden/server.git
synced 2025-05-29 07:14:50 -05:00
Merge branch 'main' into test-docker-stuff
This commit is contained in:
commit
3c24083961
@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"swashbuckle.aspnetcore.cli": {
|
||||
"version": "6.9.0",
|
||||
"version": "7.2.0",
|
||||
"commands": ["swagger"]
|
||||
},
|
||||
"dotnet-ef": {
|
||||
|
@ -3,6 +3,11 @@
|
||||
"dockerComposeFile": "../../.devcontainer/bitwarden_common/docker-compose.yml",
|
||||
"service": "bitwarden_server",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"mounts": [
|
||||
{
|
||||
"source": "../../dev/.data/keys",
|
||||
@ -13,7 +18,6 @@
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"features": {},
|
||||
"extensions": ["ms-dotnettools.csdevkit"]
|
||||
}
|
||||
},
|
||||
|
@ -6,6 +6,11 @@
|
||||
],
|
||||
"service": "bitwarden_server",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"mounts": [
|
||||
{
|
||||
"source": "../../dev/.data/keys",
|
||||
@ -16,12 +21,11 @@
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {},
|
||||
"features": {},
|
||||
"extensions": ["ms-dotnettools.csdevkit"]
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "bash .devcontainer/internal_dev/postCreateCommand.sh",
|
||||
"forwardPorts": [1080, 1433],
|
||||
"forwardPorts": [1080, 1433, 3306, 5432, 10000, 10001, 10002],
|
||||
"portsAttributes": {
|
||||
"1080": {
|
||||
"label": "Mail Catcher",
|
||||
@ -30,6 +34,26 @@
|
||||
"1433": {
|
||||
"label": "SQL Server",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"3306": {
|
||||
"label": "MySQL",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"5432": {
|
||||
"label": "PostgreSQL",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"10000": {
|
||||
"label": "Azurite Storage Blob",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"10001": {
|
||||
"label": "Azurite Storage Queue ",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"10002": {
|
||||
"label": "Azurite Storage Table",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
.github/CODEOWNERS
vendored
14
.github/CODEOWNERS
vendored
@ -15,11 +15,7 @@
|
||||
|
||||
## These are shared workflows ##
|
||||
.github/workflows/_move_finalization_db_scripts.yml
|
||||
.github/workflows/build.yml
|
||||
.github/workflows/cleanup-after-pr.yml
|
||||
.github/workflows/cleanup-rc-branch.yml
|
||||
.github/workflows/release.yml
|
||||
.github/workflows/repository-management.yml
|
||||
|
||||
# Database Operations for database changes
|
||||
src/Sql/** @bitwarden/dept-dbops
|
||||
@ -38,7 +34,6 @@ src/Identity @bitwarden/team-auth-dev
|
||||
# Key Management team
|
||||
**/KeyManagement @bitwarden/team-key-management-dev
|
||||
|
||||
**/SecretsManager @bitwarden/team-secrets-manager-dev
|
||||
**/Tools @bitwarden/team-tools-dev
|
||||
|
||||
# Vault team
|
||||
@ -69,6 +64,15 @@ src/EventsProcessor @bitwarden/team-admin-console-dev
|
||||
src/Admin/Controllers/ToolsController.cs @bitwarden/team-billing-dev
|
||||
src/Admin/Views/Tools @bitwarden/team-billing-dev
|
||||
|
||||
# Platform team
|
||||
.github/workflows/build.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/cleanup-after-pr.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/cleanup-rc-branch.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/repository-management.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/test-database.yml @bitwarden/team-platform-dev
|
||||
.github/workflows/test.yml @bitwarden/team-platform-dev
|
||||
**/*Platform* @bitwarden/team-platform-dev
|
||||
|
||||
# Multiple owners - DO NOT REMOVE (BRE)
|
||||
**/packages.lock.json
|
||||
Directory.Build.props
|
||||
|
19
.github/renovate.json
vendored
19
.github/renovate.json
vendored
@ -12,21 +12,21 @@
|
||||
{
|
||||
"groupName": "dockerfile minor",
|
||||
"matchManagers": ["dockerfile"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchUpdateTypes": ["minor"]
|
||||
},
|
||||
{
|
||||
"groupName": "docker-compose minor",
|
||||
"matchManagers": ["docker-compose"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchUpdateTypes": ["minor"]
|
||||
},
|
||||
{
|
||||
"groupName": "gh minor",
|
||||
"groupName": "github-action minor",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
"matchUpdateTypes": ["minor"]
|
||||
},
|
||||
{
|
||||
"matchManagers": ["github-actions", "dockerfile", "docker-compose"],
|
||||
"commitMessagePrefix": "[deps] DevOps:"
|
||||
"matchManagers": ["dockerfile", "docker-compose"],
|
||||
"commitMessagePrefix": "[deps] BRE:"
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["DnsClient"],
|
||||
@ -63,7 +63,7 @@
|
||||
"BitPay.Light",
|
||||
"Braintree",
|
||||
"coverlet.collector",
|
||||
"FluentAssertions",
|
||||
"CsvHelper",
|
||||
"Kralizek.AutoFixture.Extensions.MockHttp",
|
||||
"Microsoft.AspNetCore.Mvc.Testing",
|
||||
"Microsoft.Extensions.Logging",
|
||||
@ -104,6 +104,7 @@
|
||||
"Microsoft.EntityFrameworkCore.Relational",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite",
|
||||
"Microsoft.EntityFrameworkCore.SqlServer",
|
||||
"Microsoft.Extensions.Caching.Cosmos",
|
||||
"Microsoft.Extensions.Caching.SqlServer",
|
||||
"Microsoft.Extensions.Caching.StackExchangeRedis",
|
||||
"Npgsql.EntityFrameworkCore.PostgreSQL",
|
||||
@ -116,8 +117,8 @@
|
||||
{
|
||||
"matchPackageNames": ["CommandDotNet", "YamlDotNet"],
|
||||
"description": "DevOps owned dependencies",
|
||||
"commitMessagePrefix": "[deps] DevOps:",
|
||||
"reviewers": ["team:dept-devops"]
|
||||
"commitMessagePrefix": "[deps] BRE:",
|
||||
"reviewers": ["team:dept-bre"]
|
||||
},
|
||||
{
|
||||
"matchPackageNames": [
|
||||
|
68
.github/workflows/build.yml
vendored
68
.github/workflows/build.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Verify format
|
||||
run: dotnet format --verify-no-changes
|
||||
@ -81,7 +81,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||
@ -120,7 +120,7 @@ jobs:
|
||||
ls -atlh ../../../
|
||||
|
||||
- name: Upload project artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: ${{ matrix.project_name }}.zip
|
||||
path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip
|
||||
@ -131,6 +131,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
needs:
|
||||
- build-artifacts
|
||||
strategy:
|
||||
@ -276,7 +277,8 @@ jobs:
|
||||
-d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
||||
id: build-docker
|
||||
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
|
||||
with:
|
||||
context: ${{ matrix.base_path }}/${{ matrix.project_name }}
|
||||
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
|
||||
@ -286,16 +288,33 @@ jobs:
|
||||
secrets: |
|
||||
"GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}"
|
||||
|
||||
- name: Install Cosign
|
||||
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
|
||||
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
|
||||
|
||||
- name: Sign image with Cosign
|
||||
if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
DIGEST: ${{ steps.build-docker.outputs.digest }}
|
||||
TAGS: ${{ steps.image-tags.outputs.tags }}
|
||||
run: |
|
||||
IFS="," read -a tags <<< "${TAGS}"
|
||||
images=""
|
||||
for tag in "${tags[@]}"; do
|
||||
images+="${tag}@${DIGEST} "
|
||||
done
|
||||
cosign sign --yes ${images}
|
||||
|
||||
- name: Scan Docker image
|
||||
id: container-scan
|
||||
uses: anchore/scan-action@5ed195cc06065322983cae4bb31e2a751feb86fd # v5.2.0
|
||||
uses: anchore/scan-action@abae793926ec39a78ab18002bc7fc45bbbd94342 # v6.0.0
|
||||
with:
|
||||
image: ${{ steps.image-tags.outputs.primary_tag }}
|
||||
fail-build: false
|
||||
output-format: sarif
|
||||
|
||||
- name: Upload Grype results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
with:
|
||||
sarif_file: ${{ steps.container-scan.outputs.sarif }}
|
||||
|
||||
@ -310,7 +329,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Log in to Azure - production subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
@ -374,7 +393,7 @@ jobs:
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: docker-stub-US.zip
|
||||
path: docker-stub-US.zip
|
||||
@ -384,7 +403,7 @@ jobs:
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: docker-stub-EU.zip
|
||||
path: docker-stub-EU.zip
|
||||
@ -394,7 +413,7 @@ jobs:
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: docker-stub-US-sha256.txt
|
||||
path: docker-stub-US-sha256.txt
|
||||
@ -404,7 +423,7 @@ jobs:
|
||||
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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: docker-stub-EU-sha256.txt
|
||||
path: docker-stub-EU-sha256.txt
|
||||
@ -428,7 +447,7 @@ jobs:
|
||||
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
||||
|
||||
- name: Upload Public API Swagger artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: swagger.json
|
||||
path: swagger.json
|
||||
@ -462,14 +481,14 @@ jobs:
|
||||
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING: "placeholder"
|
||||
|
||||
- name: Upload Internal API Swagger artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: internal.json
|
||||
path: internal.json
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Identity Swagger artifact
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: identity.json
|
||||
path: identity.json
|
||||
@ -498,7 +517,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@ -514,7 +533,7 @@ jobs:
|
||||
|
||||
- name: Upload project artifact for Windows
|
||||
if: ${{ contains(matrix.target, 'win') == true }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: MsSqlMigratorUtility-${{ matrix.target }}
|
||||
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe
|
||||
@ -522,7 +541,7 @@ jobs:
|
||||
|
||||
- name: Upload project artifact
|
||||
if: ${{ contains(matrix.target, 'win') == false }}
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: MsSqlMigratorUtility-${{ matrix.target }}
|
||||
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility
|
||||
@ -635,6 +654,21 @@ jobs:
|
||||
}
|
||||
})
|
||||
|
||||
trigger-ephemeral-environment-sync:
|
||||
name: Trigger Ephemeral Environment Sync
|
||||
needs: trigger-ee-updates
|
||||
if: |
|
||||
github.event_name == 'pull_request_target'
|
||||
&& contains(github.event.pull_request.labels.*.name, 'ephemeral-environment')
|
||||
uses: bitwarden/gh-actions/.github/workflows/_ephemeral_environment_manager.yml@main
|
||||
with:
|
||||
ephemeral_env_branch: process.env.GITHUB_HEAD_REF
|
||||
project: server
|
||||
sync_environment: true
|
||||
pull_request_number: ${{ github.event.number }}
|
||||
secrets: inherit
|
||||
|
||||
|
||||
check-failures:
|
||||
name: Check for failures
|
||||
if: always()
|
||||
|
2
.github/workflows/code-references.yml
vendored
2
.github/workflows/code-references.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
|
||||
- name: Collect
|
||||
id: collect
|
||||
uses: launchdarkly/find-code-references-in-pull-request@d008aa4f321d8cd35314d9cb095388dcfde84439 # v2.0.0
|
||||
uses: launchdarkly/find-code-references-in-pull-request@30f4c4ab2949bbf258b797ced2fbf6dea34df9ce # v2.1.0
|
||||
with:
|
||||
project-key: default
|
||||
environment-key: dev
|
||||
|
@ -1,33 +1,14 @@
|
||||
name: Ephemeral environment cleanup
|
||||
name: Ephemeral Environment
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [unlabeled]
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
validate-pr:
|
||||
name: Validate PR
|
||||
trigger-ee-updates:
|
||||
name: Trigger Ephemeral Environment updates
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
config-exists: ${{ steps.validate-config.outputs.config-exists }}
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Validate config exists in path
|
||||
id: validate-config
|
||||
run: |
|
||||
if [[ -f "ephemeral-environments/$GITHUB_HEAD_REF.yaml" ]]; then
|
||||
echo "Ephemeral environment config found in path, continuing."
|
||||
echo "config-exists=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
|
||||
cleanup-config:
|
||||
name: Cleanup ephemeral environment
|
||||
runs-on: ubuntu-24.04
|
||||
needs: validate-pr
|
||||
if: ${{ needs.validate-pr.outputs.config-exists }}
|
||||
if: github.event.label.name == 'ephemeral-environment'
|
||||
steps:
|
||||
- name: Log in to Azure - CI subscription
|
||||
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
|
||||
@ -41,7 +22,7 @@ jobs:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Trigger Ephemeral Environment cleanup
|
||||
- name: Trigger Ephemeral Environment update
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
@ -49,11 +30,9 @@ jobs:
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'bitwarden',
|
||||
repo: 'devops',
|
||||
workflow_id: '_ephemeral_environment_pr_manager.yml',
|
||||
workflow_id: '_update_ephemeral_tags.yml',
|
||||
ref: 'main',
|
||||
inputs: {
|
||||
ephemeral_env_branch: process.env.GITHUB_HEAD_REF,
|
||||
cleanup_config: true,
|
||||
project: 'server'
|
||||
ephemeral_env_branch: process.env.GITHUB_HEAD_REF
|
||||
}
|
||||
})
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -85,7 +85,7 @@ jobs:
|
||||
|
||||
- name: Create release
|
||||
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
|
||||
uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0
|
||||
with:
|
||||
artifacts: "docker-stub-US.zip,
|
||||
docker-stub-US-sha256.txt,
|
||||
|
8
.github/workflows/repository-management.yml
vendored
8
.github/workflows/repository-management.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
@ -98,7 +98,7 @@ jobs:
|
||||
version: ${{ inputs.version_number_override }}
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
@ -197,7 +197,7 @@ jobs:
|
||||
- setup
|
||||
steps:
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||
uses: actions/create-github-app-token@c1a285145b9d317df6ced56c09f525b5c2b6f755 # v1.11.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||
@ -206,6 +206,7 @@ jobs:
|
||||
- name: Check out main branch
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
@ -241,6 +242,7 @@ jobs:
|
||||
git cherry-pick --strategy-option=theirs -x $SOURCE_COMMIT
|
||||
git push -u origin $destination_branch
|
||||
fi
|
||||
}
|
||||
|
||||
# If we are cutting 'hotfix-rc':
|
||||
if [[ "$CUT_BRANCH" == "hotfix-rc" ]]; then
|
||||
|
11
.github/workflows/scan.yml
vendored
11
.github/workflows/scan.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Scan with Checkmarx
|
||||
uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36
|
||||
uses: checkmarx/ast-github-action@184bf2f64f55d1c93fd6636d539edf274703e434 # 2.0.41
|
||||
env:
|
||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||
with:
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
--output-path . ${{ env.INCREMENTAL }}
|
||||
|
||||
- name: Upload Checkmarx results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
||||
uses: github/codeql-action/upload-sarif@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
with:
|
||||
sarif_file: cx_result.sarif
|
||||
|
||||
@ -60,7 +60,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
|
||||
uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: "zulu"
|
||||
@ -72,7 +72,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Install SonarCloud scanner
|
||||
run: dotnet tool install dotnet-sonarscanner -g
|
||||
@ -80,12 +80,11 @@ jobs:
|
||||
- name: Scan with SonarCloud
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
dotnet-sonarscanner begin /k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" \
|
||||
/d:sonar.test.inclusions=test/,bitwarden_license/test/ \
|
||||
/d:sonar.exclusions=test/,bitwarden_license/test/ \
|
||||
/o:"${{ github.repository_owner }}" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \
|
||||
/d:sonar.host.url="https://sonarcloud.io"
|
||||
/d:sonar.host.url="https://sonarcloud.io" ${{ contains(github.event_name, 'pull_request') && format('/d:sonar.pullrequest.key={0}', github.event.pull_request.number) || '' }}
|
||||
dotnet build
|
||||
dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
|
||||
|
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check
|
||||
uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
|
||||
with:
|
||||
stale-issue-label: "needs-reply"
|
||||
stale-pr-label: "needs-changes"
|
||||
|
12
.github/workflows/test-database.yml
vendored
12
.github/workflows/test-database.yml
vendored
@ -17,6 +17,7 @@ on:
|
||||
- "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer
|
||||
- "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer
|
||||
- "test/Infrastructure.IntegrationTest/**" # Any changes to the tests
|
||||
- "src/**/Entities/**/*.cs" # Database entity definitions
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-database.yml" # This file
|
||||
@ -28,6 +29,7 @@ on:
|
||||
- "src/Infrastructure.Dapper/**" # Changes to SQL Server Dapper Repository Layer
|
||||
- "src/Infrastructure.EntityFramework/**" # Changes to Entity Framework Repository Layer
|
||||
- "test/Infrastructure.IntegrationTest/**" # Any changes to the tests
|
||||
- "src/**/Entities/**/*.cs" # Database entity definitions
|
||||
|
||||
jobs:
|
||||
check-test-secrets:
|
||||
@ -57,7 +59,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Restore tools
|
||||
run: dotnet tool restore
|
||||
@ -186,7 +188,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@ -200,7 +202,7 @@ jobs:
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload DACPAC
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: sql.dacpac
|
||||
path: Sql.dacpac
|
||||
@ -226,7 +228,7 @@ jobs:
|
||||
shell: pwsh
|
||||
|
||||
- name: Report validation results
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
with:
|
||||
name: report.xml
|
||||
path: |
|
||||
@ -237,7 +239,7 @@ jobs:
|
||||
run: |
|
||||
if grep -q "<Operations>" "report.xml"; then
|
||||
echo
|
||||
echo "Migrations are out of sync with sqlproj!"
|
||||
echo "Migration files are not in sync with the files in the Sql project. Review to make sure that any stored procedures / other db changes match with the stored procedures in the Sql project."
|
||||
exit 1
|
||||
else
|
||||
echo "Report looks good"
|
||||
|
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
|
||||
uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@ -77,7 +77,4 @@ jobs:
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
|
||||
if: ${{ needs.check-test-secrets.outputs.available == 'true' }}
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2
|
||||
|
18
.vscode/extensions.json
vendored
Normal file
18
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"nick-rudenko.back-n-forth",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"MS-vsliveshare.vsliveshare",
|
||||
|
||||
"mhutchie.git-graph",
|
||||
"donjayamanne.githistory",
|
||||
"eamodio.gitlens",
|
||||
|
||||
"jakebathman.mysql-syntax",
|
||||
"ckolkman.vscode-postgres",
|
||||
|
||||
"ms-dotnettools.csharp",
|
||||
"formulahendry.dotnet-test-explorer",
|
||||
"adrianwilczynski.user-secrets"
|
||||
]
|
||||
}
|
@ -3,10 +3,17 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<Version>2024.12.0</Version>
|
||||
<Version>2025.2.0</Version>
|
||||
|
||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<!-- Treat it as a test project if the project hasn't set their own value and it follows our test project conventions -->
|
||||
<IsTestProject Condition="'$(IsTestProject)' == '' and ($(MSBuildProjectName.EndsWith('.Test')) or $(MSBuildProjectName.EndsWith('.IntegrationTest')))">true</IsTestProject>
|
||||
<Nullable Condition="'$(Nullable)' == '' and '$(IsTestProject)' == 'true'">annotations</Nullable>
|
||||
<!-- Uncomment the below line when we are ready to enable nullable repo wide -->
|
||||
<!-- <Nullable Condition="'$(Nullable)' == '' and '$(IsTestProject)' != 'true'">enable</Nullable> -->
|
||||
<TreatWarningsAsErrors Condition="'$(TreatWarningsAsErrors)' == ''">true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
|
@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
.editorconfig = .editorconfig
|
||||
TRADEMARK_GUIDELINES.md = TRADEMARK_GUIDELINES.md
|
||||
SECURITY.md = SECURITY.md
|
||||
NuGet.Config = NuGet.Config
|
||||
LICENSE_FAQ.md = LICENSE_FAQ.md
|
||||
LICENSE_BITWARDEN.txt = LICENSE_BITWARDEN.txt
|
||||
LICENSE_AGPL.txt = LICENSE_AGPL.txt
|
||||
@ -126,6 +125,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notifications.Test", "test\
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Dapper.Test", "test\Infrastructure.Dapper.Test\Infrastructure.Dapper.Test.csproj", "{4A725DB3-BE4F-4C23-9087-82D0610D67AF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.IntegrationTest", "test\Events.IntegrationTest\Events.IntegrationTest.csproj", "{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -314,6 +315,10 @@ Global
|
||||
{4A725DB3-BE4F-4C23-9087-82D0610D67AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4A725DB3-BE4F-4C23-9087-82D0610D67AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4A725DB3-BE4F-4C23-9087-82D0610D67AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -364,6 +369,7 @@ Global
|
||||
{81673EFB-7134-4B4B-A32F-1EA05F0EF3CE} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||
{90D85D8F-5577-4570-A96E-5A2E185F0F6F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||
{4A725DB3-BE4F-4C23-9087-82D0610D67AF} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||
{4F4C63A9-AEE2-48C4-AB86-A5BCD665E401} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F}
|
||||
|
@ -27,7 +27,11 @@ namespace Bit.Commercial.Core.AdminConsole.Services;
|
||||
|
||||
public class ProviderService : IProviderService
|
||||
{
|
||||
public static PlanType[] ProviderDisallowedOrganizationTypes = new[] { PlanType.Free, PlanType.FamiliesAnnually, PlanType.FamiliesAnnually2019 };
|
||||
private static readonly PlanType[] _resellerDisallowedOrganizationTypes = [
|
||||
PlanType.Free,
|
||||
PlanType.FamiliesAnnually,
|
||||
PlanType.FamiliesAnnually2019
|
||||
];
|
||||
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly IMailService _mailService;
|
||||
@ -690,13 +694,14 @@ public class ProviderService : IProviderService
|
||||
throw new BadRequestException($"Multi-organization Enterprise Providers cannot manage organizations with the plan type {requestedType}. Only Enterprise (Monthly) and Enterprise (Annually) are allowed.");
|
||||
}
|
||||
break;
|
||||
case ProviderType.Reseller:
|
||||
if (_resellerDisallowedOrganizationTypes.Contains(requestedType))
|
||||
{
|
||||
throw new BadRequestException($"Providers cannot manage organizations with the requested plan type ({requestedType}). Only Teams and Enterprise accounts are allowed.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException($"Unsupported provider type {providerType}.");
|
||||
}
|
||||
|
||||
if (ProviderDisallowedOrganizationTypes.Contains(requestedType))
|
||||
{
|
||||
throw new BadRequestException($"Providers cannot manage organizations with the requested plan type ({requestedType}). Only Teams and Enterprise accounts are allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
using System.Globalization;
|
||||
using Bit.Commercial.Core.Billing.Models;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Services.Contracts;
|
||||
@ -24,6 +27,7 @@ using Stripe;
|
||||
namespace Bit.Commercial.Core.Billing;
|
||||
|
||||
public class ProviderBillingService(
|
||||
IEventService eventService,
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger<ProviderBillingService> logger,
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -31,9 +35,93 @@ public class ProviderBillingService(
|
||||
IProviderInvoiceItemRepository providerInvoiceItemRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderPlanRepository providerPlanRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IStripeAdapter stripeAdapter,
|
||||
ISubscriberService subscriberService) : IProviderBillingService
|
||||
ISubscriberService subscriberService,
|
||||
ITaxService taxService) : IProviderBillingService
|
||||
{
|
||||
[RequireFeature(FeatureFlagKeys.P15179_AddExistingOrgsFromProviderPortal)]
|
||||
public async Task AddExistingOrganization(
|
||||
Provider provider,
|
||||
Organization organization,
|
||||
string key)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
CancelAtPeriodEnd = false
|
||||
});
|
||||
|
||||
var subscription =
|
||||
await stripeAdapter.SubscriptionCancelAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionCancelOptions
|
||||
{
|
||||
CancellationDetails = new SubscriptionCancellationDetailsOptions
|
||||
{
|
||||
Comment = $"Organization was added to Provider with ID {provider.Id}"
|
||||
},
|
||||
InvoiceNow = true,
|
||||
Prorate = true,
|
||||
Expand = ["latest_invoice", "test_clock"]
|
||||
});
|
||||
|
||||
var now = subscription.TestClock?.FrozenTime ?? DateTime.UtcNow;
|
||||
|
||||
var wasTrialing = subscription.TrialEnd.HasValue && subscription.TrialEnd.Value > now;
|
||||
|
||||
if (!wasTrialing && subscription.LatestInvoice.Status == StripeConstants.InvoiceStatus.Draft)
|
||||
{
|
||||
await stripeAdapter.InvoiceFinalizeInvoiceAsync(subscription.LatestInvoiceId,
|
||||
new InvoiceFinalizeOptions { AutoAdvance = true });
|
||||
}
|
||||
|
||||
var managedPlanType = await GetManagedPlanTypeAsync(provider, organization);
|
||||
|
||||
// TODO: Replace with PricingClient
|
||||
var plan = StaticStore.GetPlan(managedPlanType);
|
||||
organization.Plan = plan.Name;
|
||||
organization.PlanType = plan.Type;
|
||||
organization.MaxCollections = plan.PasswordManager.MaxCollections;
|
||||
organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb;
|
||||
organization.UsePolicies = plan.HasPolicies;
|
||||
organization.UseSso = plan.HasSso;
|
||||
organization.UseGroups = plan.HasGroups;
|
||||
organization.UseEvents = plan.HasEvents;
|
||||
organization.UseDirectory = plan.HasDirectory;
|
||||
organization.UseTotp = plan.HasTotp;
|
||||
organization.Use2fa = plan.Has2fa;
|
||||
organization.UseApi = plan.HasApi;
|
||||
organization.UseResetPassword = plan.HasResetPassword;
|
||||
organization.SelfHost = plan.HasSelfHost;
|
||||
organization.UsersGetPremium = plan.UsersGetPremium;
|
||||
organization.UseCustomPermissions = plan.HasCustomPermissions;
|
||||
organization.UseScim = plan.HasScim;
|
||||
organization.UseKeyConnector = plan.HasKeyConnector;
|
||||
organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb;
|
||||
organization.BillingEmail = provider.BillingEmail!;
|
||||
organization.GatewaySubscriptionId = null;
|
||||
organization.ExpirationDate = null;
|
||||
organization.MaxAutoscaleSeats = null;
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
|
||||
var providerOrganization = new ProviderOrganization
|
||||
{
|
||||
ProviderId = provider.Id,
|
||||
OrganizationId = organization.Id,
|
||||
Key = key
|
||||
};
|
||||
|
||||
await Task.WhenAll(
|
||||
organizationRepository.ReplaceAsync(organization),
|
||||
providerOrganizationRepository.CreateAsync(providerOrganization),
|
||||
ScaleSeats(provider, organization.PlanType, organization.Seats!.Value)
|
||||
);
|
||||
|
||||
await eventService.LogProviderOrganizationEventAsync(
|
||||
providerOrganization,
|
||||
EventType.ProviderOrganization_Added);
|
||||
}
|
||||
|
||||
public async Task ChangePlan(ChangeProviderPlanCommand command)
|
||||
{
|
||||
var plan = await providerPlanRepository.GetByIdAsync(command.ProviderPlanId);
|
||||
@ -205,6 +293,81 @@ public class ProviderBillingService(
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
[RequireFeature(FeatureFlagKeys.P15179_AddExistingOrgsFromProviderPortal)]
|
||||
public async Task<IEnumerable<AddableOrganization>> GetAddableOrganizations(
|
||||
Provider provider,
|
||||
Guid userId)
|
||||
{
|
||||
var providerUser = await providerUserRepository.GetByProviderUserAsync(provider.Id, userId);
|
||||
|
||||
if (providerUser is not { Status: ProviderUserStatusType.Confirmed })
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var candidates = await organizationRepository.GetAddableToProviderByUserIdAsync(userId, provider.Type);
|
||||
|
||||
var active = (await Task.WhenAll(candidates.Select(async organization =>
|
||||
{
|
||||
var subscription = await subscriberService.GetSubscription(organization);
|
||||
return (organization, subscription);
|
||||
})))
|
||||
.Where(pair => pair.subscription is
|
||||
{
|
||||
Status:
|
||||
StripeConstants.SubscriptionStatus.Active or
|
||||
StripeConstants.SubscriptionStatus.Trialing or
|
||||
StripeConstants.SubscriptionStatus.PastDue
|
||||
}).ToList();
|
||||
|
||||
if (active.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return await Task.WhenAll(active.Select(async pair =>
|
||||
{
|
||||
var (organization, _) = pair;
|
||||
|
||||
var planName = DerivePlanName(provider, organization);
|
||||
|
||||
var addable = new AddableOrganization(
|
||||
organization.Id,
|
||||
organization.Name,
|
||||
planName,
|
||||
organization.Seats!.Value);
|
||||
|
||||
if (providerUser.Type != ProviderUserType.ServiceUser)
|
||||
{
|
||||
return addable;
|
||||
}
|
||||
|
||||
var applicablePlanType = await GetManagedPlanTypeAsync(provider, organization);
|
||||
|
||||
var requiresPurchase =
|
||||
await SeatAdjustmentResultsInPurchase(provider, applicablePlanType, organization.Seats!.Value);
|
||||
|
||||
return addable with { Disabled = requiresPurchase };
|
||||
}));
|
||||
|
||||
string DerivePlanName(Provider localProvider, Organization localOrganization)
|
||||
{
|
||||
if (localProvider.Type == ProviderType.Msp)
|
||||
{
|
||||
return localOrganization.PlanType switch
|
||||
{
|
||||
var planType when PlanConstants.EnterprisePlanTypes.Contains(planType) => "Enterprise",
|
||||
var planType when PlanConstants.TeamsPlanTypes.Contains(planType) => "Teams",
|
||||
_ => throw new BillingException()
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Replace with PricingClient
|
||||
var plan = StaticStore.GetPlan(localOrganization.PlanType);
|
||||
return plan.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ScaleSeats(
|
||||
Provider provider,
|
||||
PlanType planType,
|
||||
@ -335,14 +498,28 @@ public class ProviderBillingService(
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "region", globalSettings.BaseServiceUri.CloudRegion }
|
||||
},
|
||||
TaxIdData = taxInfo.HasTaxId ?
|
||||
[
|
||||
new CustomerTaxIdDataOptions { Type = taxInfo.TaxIdType, Value = taxInfo.TaxIdNumber }
|
||||
]
|
||||
: null
|
||||
}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(taxInfo.TaxIdNumber))
|
||||
{
|
||||
var taxIdType = taxService.GetStripeTaxCode(taxInfo.BillingAddressCountry,
|
||||
taxInfo.TaxIdNumber);
|
||||
|
||||
if (taxIdType == null)
|
||||
{
|
||||
logger.LogWarning("Could not infer tax ID type in country '{Country}' with tax ID '{TaxID}'.",
|
||||
taxInfo.BillingAddressCountry,
|
||||
taxInfo.TaxIdNumber);
|
||||
throw new BadRequestException("billingTaxIdTypeInferenceError");
|
||||
}
|
||||
|
||||
customerCreateOptions.TaxIdData =
|
||||
[
|
||||
new CustomerTaxIdDataOptions { Type = taxIdType, Value = taxInfo.TaxIdNumber }
|
||||
];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
@ -567,4 +744,21 @@ public class ProviderBillingService(
|
||||
|
||||
return providerPlan;
|
||||
}
|
||||
|
||||
private async Task<PlanType> GetManagedPlanTypeAsync(
|
||||
Provider provider,
|
||||
Organization organization)
|
||||
{
|
||||
if (provider.Type == ProviderType.MultiOrganizationEnterprise)
|
||||
{
|
||||
return (await providerPlanRepository.GetByProviderId(provider.Id)).First().PlanType;
|
||||
}
|
||||
|
||||
return organization.PlanType switch
|
||||
{
|
||||
var planType when PlanConstants.TeamsPlanTypes.Contains(planType) => PlanType.TeamsMonthly,
|
||||
var planType when PlanConstants.EnterprisePlanTypes.Contains(planType) => PlanType.EnterpriseMonthly,
|
||||
_ => throw new BillingException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,8 +1,10 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Scim.Groups.Interfaces;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
@ -22,9 +24,10 @@ public class GroupsController : Controller
|
||||
private readonly IGetGroupsListQuery _getGroupsListQuery;
|
||||
private readonly IDeleteGroupCommand _deleteGroupCommand;
|
||||
private readonly IPatchGroupCommand _patchGroupCommand;
|
||||
private readonly IPatchGroupCommandvNext _patchGroupCommandvNext;
|
||||
private readonly IPostGroupCommand _postGroupCommand;
|
||||
private readonly IPutGroupCommand _putGroupCommand;
|
||||
private readonly ILogger<GroupsController> _logger;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
public GroupsController(
|
||||
IGroupRepository groupRepository,
|
||||
@ -32,18 +35,21 @@ public class GroupsController : Controller
|
||||
IGetGroupsListQuery getGroupsListQuery,
|
||||
IDeleteGroupCommand deleteGroupCommand,
|
||||
IPatchGroupCommand patchGroupCommand,
|
||||
IPatchGroupCommandvNext patchGroupCommandvNext,
|
||||
IPostGroupCommand postGroupCommand,
|
||||
IPutGroupCommand putGroupCommand,
|
||||
ILogger<GroupsController> logger)
|
||||
IFeatureService featureService
|
||||
)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
_getGroupsListQuery = getGroupsListQuery;
|
||||
_deleteGroupCommand = deleteGroupCommand;
|
||||
_patchGroupCommand = patchGroupCommand;
|
||||
_patchGroupCommandvNext = patchGroupCommandvNext;
|
||||
_postGroupCommand = postGroupCommand;
|
||||
_putGroupCommand = putGroupCommand;
|
||||
_logger = logger;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -97,8 +103,21 @@ public class GroupsController : Controller
|
||||
[HttpPatch("{id}")]
|
||||
public async Task<IActionResult> Patch(Guid organizationId, Guid id, [FromBody] ScimPatchModel model)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.ShortcutDuplicatePatchRequests))
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(id);
|
||||
if (group == null || group.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException("Group not found.");
|
||||
}
|
||||
|
||||
await _patchGroupCommandvNext.PatchGroupAsync(group, model);
|
||||
return new NoContentResult();
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
await _patchGroupCommand.PatchGroupAsync(organization, id, model);
|
||||
|
||||
return new NoContentResult();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Scim.Models;
|
||||
|
||||
namespace Bit.Scim.Groups.Interfaces;
|
||||
|
||||
public interface IPatchGroupCommandvNext
|
||||
{
|
||||
Task PatchGroupAsync(Group group, ScimPatchModel model);
|
||||
}
|
170
bitwarden_license/src/Scim/Groups/PatchGroupCommandvNext.cs
Normal file
170
bitwarden_license/src/Scim/Groups/PatchGroupCommandvNext.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Scim.Groups.Interfaces;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
|
||||
namespace Bit.Scim.Groups;
|
||||
|
||||
public class PatchGroupCommandvNext : IPatchGroupCommandvNext
|
||||
{
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IGroupService _groupService;
|
||||
private readonly IUpdateGroupCommand _updateGroupCommand;
|
||||
private readonly ILogger<PatchGroupCommandvNext> _logger;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
|
||||
public PatchGroupCommandvNext(
|
||||
IGroupRepository groupRepository,
|
||||
IGroupService groupService,
|
||||
IUpdateGroupCommand updateGroupCommand,
|
||||
ILogger<PatchGroupCommandvNext> logger,
|
||||
IOrganizationRepository organizationRepository)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
_groupService = groupService;
|
||||
_updateGroupCommand = updateGroupCommand;
|
||||
_logger = logger;
|
||||
_organizationRepository = organizationRepository;
|
||||
}
|
||||
|
||||
public async Task PatchGroupAsync(Group group, ScimPatchModel model)
|
||||
{
|
||||
foreach (var operation in model.Operations)
|
||||
{
|
||||
await HandleOperationAsync(group, operation);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleOperationAsync(Group group, ScimPatchModel.OperationModel operation)
|
||||
{
|
||||
switch (operation.Op?.ToLowerInvariant())
|
||||
{
|
||||
// Replace a list of members
|
||||
case PatchOps.Replace when operation.Path?.ToLowerInvariant() == PatchPaths.Members:
|
||||
{
|
||||
var ids = GetOperationValueIds(operation.Value);
|
||||
await _groupRepository.UpdateUsersAsync(group.Id, ids);
|
||||
break;
|
||||
}
|
||||
|
||||
// Replace group name from path
|
||||
case PatchOps.Replace when operation.Path?.ToLowerInvariant() == PatchPaths.DisplayName:
|
||||
{
|
||||
group.Name = operation.Value.GetString();
|
||||
var organization = await _organizationRepository.GetByIdAsync(group.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
await _updateGroupCommand.UpdateGroupAsync(group, organization, EventSystemUser.SCIM);
|
||||
break;
|
||||
}
|
||||
|
||||
// Replace group name from value object
|
||||
case PatchOps.Replace when
|
||||
string.IsNullOrWhiteSpace(operation.Path) &&
|
||||
operation.Value.TryGetProperty("displayName", out var displayNameProperty):
|
||||
{
|
||||
group.Name = displayNameProperty.GetString();
|
||||
var organization = await _organizationRepository.GetByIdAsync(group.OrganizationId);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
await _updateGroupCommand.UpdateGroupAsync(group, organization, EventSystemUser.SCIM);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add a single member
|
||||
case PatchOps.Add when
|
||||
!string.IsNullOrWhiteSpace(operation.Path) &&
|
||||
operation.Path.StartsWith("members[value eq ", StringComparison.OrdinalIgnoreCase) &&
|
||||
TryGetOperationPathId(operation.Path, out var addId):
|
||||
{
|
||||
await AddMembersAsync(group, [addId]);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add a list of members
|
||||
case PatchOps.Add when
|
||||
operation.Path?.ToLowerInvariant() == PatchPaths.Members:
|
||||
{
|
||||
await AddMembersAsync(group, GetOperationValueIds(operation.Value));
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove a single member
|
||||
case PatchOps.Remove when
|
||||
!string.IsNullOrWhiteSpace(operation.Path) &&
|
||||
operation.Path.StartsWith("members[value eq ", StringComparison.OrdinalIgnoreCase) &&
|
||||
TryGetOperationPathId(operation.Path, out var removeId):
|
||||
{
|
||||
await _groupService.DeleteUserAsync(group, removeId, EventSystemUser.SCIM);
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove a list of members
|
||||
case PatchOps.Remove when
|
||||
operation.Path?.ToLowerInvariant() == PatchPaths.Members:
|
||||
{
|
||||
var orgUserIds = (await _groupRepository.GetManyUserIdsByIdAsync(group.Id)).ToHashSet();
|
||||
foreach (var v in GetOperationValueIds(operation.Value))
|
||||
{
|
||||
orgUserIds.Remove(v);
|
||||
}
|
||||
await _groupRepository.UpdateUsersAsync(group.Id, orgUserIds);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
_logger.LogWarning("Group patch operation not handled: {OperationOp}:{OperationPath}", operation.Op, operation.Path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddMembersAsync(Group group, HashSet<Guid> usersToAdd)
|
||||
{
|
||||
// Azure Entra ID is known to send redundant "add" requests for each existing member every time any member
|
||||
// is removed. To avoid excessive load on the database, we check against the high availability replica and
|
||||
// return early if they already exist.
|
||||
var groupMembers = await _groupRepository.GetManyUserIdsByIdAsync(group.Id, useReadOnlyReplica: true);
|
||||
if (usersToAdd.IsSubsetOf(groupMembers))
|
||||
{
|
||||
_logger.LogDebug("Ignoring duplicate SCIM request to add members {Members} to group {Group}", usersToAdd, group.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
await _groupRepository.AddGroupUsersByIdAsync(group.Id, usersToAdd);
|
||||
}
|
||||
|
||||
private static HashSet<Guid> GetOperationValueIds(JsonElement objArray)
|
||||
{
|
||||
var ids = new HashSet<Guid>();
|
||||
foreach (var obj in objArray.EnumerateArray())
|
||||
{
|
||||
if (obj.TryGetProperty("value", out var valueProperty))
|
||||
{
|
||||
if (valueProperty.TryGetGuid(out var guid))
|
||||
{
|
||||
ids.Add(guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static bool TryGetOperationPathId(string path, out Guid pathId)
|
||||
{
|
||||
// Parse Guid from string like: members[value eq "{GUID}"}]
|
||||
return Guid.TryParse(path.Substring(18).Replace("\"]", string.Empty), out pathId);
|
||||
}
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Scim.Context;
|
||||
using Bit.Scim.Groups.Interfaces;
|
||||
using Bit.Scim.Models;
|
||||
|
||||
@ -14,17 +11,13 @@ namespace Bit.Scim.Groups;
|
||||
public class PostGroupCommand : IPostGroupCommand
|
||||
{
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IScimContext _scimContext;
|
||||
private readonly ICreateGroupCommand _createGroupCommand;
|
||||
|
||||
public PostGroupCommand(
|
||||
IGroupRepository groupRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IScimContext scimContext,
|
||||
ICreateGroupCommand createGroupCommand)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
_scimContext = scimContext;
|
||||
_createGroupCommand = createGroupCommand;
|
||||
}
|
||||
|
||||
@ -50,11 +43,6 @@ public class PostGroupCommand : IPostGroupCommand
|
||||
|
||||
private async Task UpdateGroupMembersAsync(Group group, ScimGroupRequestModel model)
|
||||
{
|
||||
if (_scimContext.RequestScimProvider != ScimProviderType.Okta)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.Members == null)
|
||||
{
|
||||
return;
|
||||
|
@ -1,10 +1,8 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Scim.Context;
|
||||
using Bit.Scim.Groups.Interfaces;
|
||||
using Bit.Scim.Models;
|
||||
|
||||
@ -13,16 +11,13 @@ namespace Bit.Scim.Groups;
|
||||
public class PutGroupCommand : IPutGroupCommand
|
||||
{
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IScimContext _scimContext;
|
||||
private readonly IUpdateGroupCommand _updateGroupCommand;
|
||||
|
||||
public PutGroupCommand(
|
||||
IGroupRepository groupRepository,
|
||||
IScimContext scimContext,
|
||||
IUpdateGroupCommand updateGroupCommand)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
_scimContext = scimContext;
|
||||
_updateGroupCommand = updateGroupCommand;
|
||||
}
|
||||
|
||||
@ -43,12 +38,6 @@ public class PutGroupCommand : IPutGroupCommand
|
||||
|
||||
private async Task UpdateGroupMembersAsync(Group group, ScimGroupRequestModel model)
|
||||
{
|
||||
if (_scimContext.RequestScimProvider != ScimProviderType.Okta &&
|
||||
_scimContext.RequestScimProvider != ScimProviderType.Ping)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.Members == null)
|
||||
{
|
||||
return;
|
||||
|
@ -7,3 +7,16 @@ public static class ScimConstants
|
||||
public const string Scim2SchemaUser = "urn:ietf:params:scim:schemas:core:2.0:User";
|
||||
public const string Scim2SchemaGroup = "urn:ietf:params:scim:schemas:core:2.0:Group";
|
||||
}
|
||||
|
||||
public static class PatchOps
|
||||
{
|
||||
public const string Replace = "replace";
|
||||
public const string Add = "add";
|
||||
public const string Remove = "remove";
|
||||
}
|
||||
|
||||
public static class PatchPaths
|
||||
{
|
||||
public const string Members = "members";
|
||||
public const string DisplayName = "displayname";
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ public static class ScimServiceCollectionExtensions
|
||||
public static void AddScimGroupCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPatchGroupCommand, PatchGroupCommand>();
|
||||
services.AddScoped<IPatchGroupCommandvNext, PatchGroupCommandvNext>();
|
||||
services.AddScoped<IPostGroupCommand, PostGroupCommand>();
|
||||
services.AddScoped<IPutGroupCommand, PutGroupCommand>();
|
||||
}
|
||||
|
574
bitwarden_license/src/Sso/package-lock.json
generated
574
bitwarden_license/src/Sso/package-lock.json
generated
@ -16,10 +16,10 @@
|
||||
"devDependencies": {
|
||||
"css-loader": "7.1.2",
|
||||
"expose-loader": "5.0.0",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"sass": "1.79.5",
|
||||
"sass-loader": "16.0.2",
|
||||
"webpack": "5.95.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"webpack": "5.97.1",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
},
|
||||
@ -34,9 +34,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -98,10 +98,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz",
|
||||
"integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz",
|
||||
"integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
@ -117,24 +118,25 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-x64": "2.4.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.4.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.4.1",
|
||||
"@parcel/watcher-win32-arm64": "2.4.1",
|
||||
"@parcel/watcher-win32-ia32": "2.4.1",
|
||||
"@parcel/watcher-win32-x64": "2.4.1"
|
||||
"@parcel/watcher-android-arm64": "2.5.0",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.0",
|
||||
"@parcel/watcher-darwin-x64": "2.5.0",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.0",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.0",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.0",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.0",
|
||||
"@parcel/watcher-win32-arm64": "2.5.0",
|
||||
"@parcel/watcher-win32-ia32": "2.5.0",
|
||||
"@parcel/watcher-win32-x64": "2.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -153,9 +155,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -174,9 +176,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -195,9 +197,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -216,9 +218,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -237,9 +260,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -258,9 +281,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -279,9 +302,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -300,9 +323,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -321,9 +344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -342,9 +365,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz",
|
||||
"integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz",
|
||||
"integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -363,9 +386,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -394,6 +417,28 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@ -409,83 +454,83 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
|
||||
"version": "22.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
|
||||
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
||||
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
|
||||
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
|
||||
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
|
||||
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
|
||||
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
|
||||
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
|
||||
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
|
||||
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -493,9 +538,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
|
||||
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
|
||||
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -503,79 +548,79 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
|
||||
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
|
||||
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-section": "1.12.1",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
||||
"@webassemblyjs/wasm-opt": "1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "1.12.1",
|
||||
"@webassemblyjs/wast-printer": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-section": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-opt": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1",
|
||||
"@webassemblyjs/wast-printer": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
|
||||
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
|
||||
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/ieee754": "1.11.6",
|
||||
"@webassemblyjs/leb128": "1.11.6",
|
||||
"@webassemblyjs/utf8": "1.11.6"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
|
||||
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
|
||||
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
|
||||
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
|
||||
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/ieee754": "1.11.6",
|
||||
"@webassemblyjs/leb128": "1.11.6",
|
||||
"@webassemblyjs/utf8": "1.11.6"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
|
||||
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
|
||||
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
@ -641,9 +686,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@ -653,16 +698,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-attributes": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
|
||||
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
@ -725,6 +760,7 @@
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
@ -743,9 +779,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
|
||||
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
|
||||
"version": "4.24.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
|
||||
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -763,10 +799,10 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001663",
|
||||
"electron-to-chromium": "^1.5.28",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
"caniuse-lite": "^1.0.30001688",
|
||||
"electron-to-chromium": "^1.5.73",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@ -783,9 +819,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001668",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz",
|
||||
"integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==",
|
||||
"version": "1.0.30001690",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
|
||||
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -804,9 +840,9 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -859,9 +895,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -936,16 +972,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.36",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz",
|
||||
"integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==",
|
||||
"version": "1.5.75",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz",
|
||||
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1075,11 +1111,11 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz",
|
||||
"integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
|
||||
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastest-levenshtein": {
|
||||
"version": "1.0.16",
|
||||
@ -1235,9 +1271,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1426,9 +1462,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz",
|
||||
"integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz",
|
||||
"integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1447,9 +1483,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1480,9 +1516,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -1553,9 +1589,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -1586,9 +1622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1607,7 +1643,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -1628,14 +1664,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-local-by-default": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz",
|
||||
"integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
|
||||
"integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -1646,13 +1682,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-scope": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz",
|
||||
"integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
|
||||
"integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
"postcss-selector-parser": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
@ -1678,9 +1714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1756,19 +1792,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@ -1837,9 +1876,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-loader": {
|
||||
"version": "16.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.2.tgz",
|
||||
"integrity": "sha512-Ll6iXZ1EYwYT19SqW4mSBb76vSSi8JgzElmzIerhEGgzB5hRjDQIWsPmuk1UrAXkR16KJHqVY0eH+5/uw9Tmfw==",
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1878,9 +1917,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
|
||||
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1890,7 +1929,7 @@
|
||||
"ajv-keywords": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -2027,9 +2066,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.34.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz",
|
||||
"integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==",
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@ -2046,17 +2085,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.10",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
||||
"version": "5.3.11",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
|
||||
"integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.20",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"terser": "^5.26.0"
|
||||
"schema-utils": "^4.3.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"terser": "^5.31.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
@ -2080,59 +2119,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -2147,9 +2133,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -2216,19 +2202,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.95.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
|
||||
"integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
|
||||
"version": "5.97.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
|
||||
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.5",
|
||||
"@webassemblyjs/ast": "^1.12.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.12.1",
|
||||
"acorn": "^8.7.1",
|
||||
"acorn-import-attributes": "^1.9.5",
|
||||
"browserslist": "^4.21.10",
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.14.0",
|
||||
"browserslist": "^4.24.0",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
|
@ -15,10 +15,10 @@
|
||||
"devDependencies": {
|
||||
"css-loader": "7.1.2",
|
||||
"expose-loader": "5.0.0",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"sass": "1.79.5",
|
||||
"sass-loader": "16.0.2",
|
||||
"webpack": "5.95.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"webpack": "5.97.1",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
}
|
||||
|
@ -746,6 +746,12 @@ public class ProviderBillingServiceTests
|
||||
{
|
||||
provider.Name = "MSP";
|
||||
|
||||
sutProvider.GetDependency<ITaxService>()
|
||||
.GetStripeTaxCode(Arg.Is<string>(
|
||||
p => p == taxInfo.BillingAddressCountry),
|
||||
Arg.Is<string>(p => p == taxInfo.TaxIdNumber))
|
||||
.Returns(taxInfo.TaxIdType);
|
||||
|
||||
taxInfo.BillingAddressCountry = "AD";
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
@ -777,6 +783,29 @@ public class ProviderBillingServiceTests
|
||||
Assert.Equivalent(expected, actual);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SetupCustomer_Throws_BadRequestException_WhenTaxIdIsInvalid(
|
||||
SutProvider<ProviderBillingService> sutProvider,
|
||||
Provider provider,
|
||||
TaxInfo taxInfo)
|
||||
{
|
||||
provider.Name = "MSP";
|
||||
|
||||
taxInfo.BillingAddressCountry = "AD";
|
||||
|
||||
sutProvider.GetDependency<ITaxService>()
|
||||
.GetStripeTaxCode(Arg.Is<string>(
|
||||
p => p == taxInfo.BillingAddressCountry),
|
||||
Arg.Is<string>(p => p == taxInfo.TaxIdNumber))
|
||||
.Returns((string)null);
|
||||
|
||||
var actual = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||
await sutProvider.Sut.SetupCustomer(provider, taxInfo));
|
||||
|
||||
Assert.IsType<BadRequestException>(actual);
|
||||
Assert.Equal("billingTaxIdTypeInferenceError", actual.Message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SetupSubscription
|
||||
|
@ -0,0 +1,151 @@
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Commercial.Core.Test.Billing;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class TaxServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData("AD", "A-123456-Z", "ad_nrt")]
|
||||
[BitAutoData("AD", "A123456Z", "ad_nrt")]
|
||||
[BitAutoData("AR", "20-12345678-9", "ar_cuit")]
|
||||
[BitAutoData("AR", "20123456789", "ar_cuit")]
|
||||
[BitAutoData("AU", "01259983598", "au_abn")]
|
||||
[BitAutoData("AU", "123456789123", "au_arn")]
|
||||
[BitAutoData("AT", "ATU12345678", "eu_vat")]
|
||||
[BitAutoData("BH", "123456789012345", "bh_vat")]
|
||||
[BitAutoData("BY", "123456789", "by_tin")]
|
||||
[BitAutoData("BE", "BE0123456789", "eu_vat")]
|
||||
[BitAutoData("BO", "123456789", "bo_tin")]
|
||||
[BitAutoData("BR", "01.234.456/5432-10", "br_cnpj")]
|
||||
[BitAutoData("BR", "01234456543210", "br_cnpj")]
|
||||
[BitAutoData("BR", "123.456.789-87", "br_cpf")]
|
||||
[BitAutoData("BR", "12345678987", "br_cpf")]
|
||||
[BitAutoData("BG", "123456789", "bg_uic")]
|
||||
[BitAutoData("BG", "BG012100705", "eu_vat")]
|
||||
[BitAutoData("CA", "100728494", "ca_bn")]
|
||||
[BitAutoData("CA", "123456789RT0001", "ca_gst_hst")]
|
||||
[BitAutoData("CA", "PST-1234-1234", "ca_pst_bc")]
|
||||
[BitAutoData("CA", "123456-7", "ca_pst_mb")]
|
||||
[BitAutoData("CA", "1234567", "ca_pst_sk")]
|
||||
[BitAutoData("CA", "1234567890TQ1234", "ca_qst")]
|
||||
[BitAutoData("CL", "11.121.326-1", "cl_tin")]
|
||||
[BitAutoData("CL", "11121326-1", "cl_tin")]
|
||||
[BitAutoData("CL", "23.121.326-K", "cl_tin")]
|
||||
[BitAutoData("CL", "43651326-K", "cl_tin")]
|
||||
[BitAutoData("CN", "123456789012345678", "cn_tin")]
|
||||
[BitAutoData("CN", "123456789012345", "cn_tin")]
|
||||
[BitAutoData("CO", "123.456.789-0", "co_nit")]
|
||||
[BitAutoData("CO", "1234567890", "co_nit")]
|
||||
[BitAutoData("CR", "1-234-567890", "cr_tin")]
|
||||
[BitAutoData("CR", "1234567890", "cr_tin")]
|
||||
[BitAutoData("HR", "HR12345678912", "eu_vat")]
|
||||
[BitAutoData("HR", "12345678901", "hr_oib")]
|
||||
[BitAutoData("CY", "CY12345678X", "eu_vat")]
|
||||
[BitAutoData("CZ", "CZ12345678", "eu_vat")]
|
||||
[BitAutoData("DK", "DK12345678", "eu_vat")]
|
||||
[BitAutoData("DO", "123-4567890-1", "do_rcn")]
|
||||
[BitAutoData("DO", "12345678901", "do_rcn")]
|
||||
[BitAutoData("EC", "1234567890001", "ec_ruc")]
|
||||
[BitAutoData("EG", "123456789", "eg_tin")]
|
||||
[BitAutoData("SV", "1234-567890-123-4", "sv_nit")]
|
||||
[BitAutoData("SV", "12345678901234", "sv_nit")]
|
||||
[BitAutoData("EE", "EE123456789", "eu_vat")]
|
||||
[BitAutoData("EU", "EU123456789", "eu_oss_vat")]
|
||||
[BitAutoData("FI", "FI12345678", "eu_vat")]
|
||||
[BitAutoData("FR", "FR12345678901", "eu_vat")]
|
||||
[BitAutoData("GE", "123456789", "ge_vat")]
|
||||
[BitAutoData("DE", "1234567890", "de_stn")]
|
||||
[BitAutoData("DE", "DE123456789", "eu_vat")]
|
||||
[BitAutoData("GR", "EL123456789", "eu_vat")]
|
||||
[BitAutoData("HK", "12345678", "hk_br")]
|
||||
[BitAutoData("HU", "HU12345678", "eu_vat")]
|
||||
[BitAutoData("HU", "12345678-1-23", "hu_tin")]
|
||||
[BitAutoData("HU", "12345678123", "hu_tin")]
|
||||
[BitAutoData("IS", "123456", "is_vat")]
|
||||
[BitAutoData("IN", "12ABCDE1234F1Z5", "in_gst")]
|
||||
[BitAutoData("IN", "12ABCDE3456FGZH", "in_gst")]
|
||||
[BitAutoData("ID", "012.345.678.9-012.345", "id_npwp")]
|
||||
[BitAutoData("ID", "0123456789012345", "id_npwp")]
|
||||
[BitAutoData("IE", "IE1234567A", "eu_vat")]
|
||||
[BitAutoData("IE", "IE1234567AB", "eu_vat")]
|
||||
[BitAutoData("IL", "000012345", "il_vat")]
|
||||
[BitAutoData("IL", "123456789", "il_vat")]
|
||||
[BitAutoData("IT", "IT12345678901", "eu_vat")]
|
||||
[BitAutoData("JP", "1234567890123", "jp_cn")]
|
||||
[BitAutoData("JP", "12345", "jp_rn")]
|
||||
[BitAutoData("KZ", "123456789012", "kz_bin")]
|
||||
[BitAutoData("KE", "P000111111A", "ke_pin")]
|
||||
[BitAutoData("LV", "LV12345678912", "eu_vat")]
|
||||
[BitAutoData("LI", "CHE123456789", "li_uid")]
|
||||
[BitAutoData("LI", "12345", "li_vat")]
|
||||
[BitAutoData("LT", "LT123456789123", "eu_vat")]
|
||||
[BitAutoData("LU", "LU12345678", "eu_vat")]
|
||||
[BitAutoData("MY", "12345678", "my_frp")]
|
||||
[BitAutoData("MY", "C 1234567890", "my_itn")]
|
||||
[BitAutoData("MY", "C1234567890", "my_itn")]
|
||||
[BitAutoData("MY", "A12-3456-78912345", "my_sst")]
|
||||
[BitAutoData("MY", "A12345678912345", "my_sst")]
|
||||
[BitAutoData("MT", "MT12345678", "eu_vat")]
|
||||
[BitAutoData("MX", "ABC010203AB9", "mx_rfc")]
|
||||
[BitAutoData("MD", "1003600", "md_vat")]
|
||||
[BitAutoData("MA", "12345678", "ma_vat")]
|
||||
[BitAutoData("NL", "NL123456789B12", "eu_vat")]
|
||||
[BitAutoData("NZ", "123456789", "nz_gst")]
|
||||
[BitAutoData("NG", "12345678-0001", "ng_tin")]
|
||||
[BitAutoData("NO", "123456789MVA", "no_vat")]
|
||||
[BitAutoData("NO", "1234567", "no_voec")]
|
||||
[BitAutoData("OM", "OM1234567890", "om_vat")]
|
||||
[BitAutoData("PE", "12345678901", "pe_ruc")]
|
||||
[BitAutoData("PH", "123456789012", "ph_tin")]
|
||||
[BitAutoData("PL", "PL1234567890", "eu_vat")]
|
||||
[BitAutoData("PT", "PT123456789", "eu_vat")]
|
||||
[BitAutoData("RO", "RO1234567891", "eu_vat")]
|
||||
[BitAutoData("RO", "1234567890123", "ro_tin")]
|
||||
[BitAutoData("RU", "1234567891", "ru_inn")]
|
||||
[BitAutoData("RU", "123456789", "ru_kpp")]
|
||||
[BitAutoData("SA", "123456789012345", "sa_vat")]
|
||||
[BitAutoData("RS", "123456789", "rs_pib")]
|
||||
[BitAutoData("SG", "M12345678X", "sg_gst")]
|
||||
[BitAutoData("SG", "123456789F", "sg_uen")]
|
||||
[BitAutoData("SK", "SK1234567891", "eu_vat")]
|
||||
[BitAutoData("SI", "SI12345678", "eu_vat")]
|
||||
[BitAutoData("SI", "12345678", "si_tin")]
|
||||
[BitAutoData("ZA", "4123456789", "za_vat")]
|
||||
[BitAutoData("KR", "123-45-67890", "kr_brn")]
|
||||
[BitAutoData("KR", "1234567890", "kr_brn")]
|
||||
[BitAutoData("ES", "A12345678", "es_cif")]
|
||||
[BitAutoData("ES", "ESX1234567X", "eu_vat")]
|
||||
[BitAutoData("SE", "SE123456789012", "eu_vat")]
|
||||
[BitAutoData("CH", "CHE-123.456.789 HR", "ch_uid")]
|
||||
[BitAutoData("CH", "CHE123456789HR", "ch_uid")]
|
||||
[BitAutoData("CH", "CHE-123.456.789 MWST", "ch_vat")]
|
||||
[BitAutoData("CH", "CHE123456789MWST", "ch_vat")]
|
||||
[BitAutoData("TW", "12345678", "tw_vat")]
|
||||
[BitAutoData("TH", "1234567890123", "th_vat")]
|
||||
[BitAutoData("TR", "0123456789", "tr_tin")]
|
||||
[BitAutoData("UA", "123456789", "ua_vat")]
|
||||
[BitAutoData("AE", "123456789012345", "ae_trn")]
|
||||
[BitAutoData("GB", "XI123456789", "eu_vat")]
|
||||
[BitAutoData("GB", "GB123456789", "gb_vat")]
|
||||
[BitAutoData("US", "12-3456789", "us_ein")]
|
||||
[BitAutoData("UY", "123456789012", "uy_ruc")]
|
||||
[BitAutoData("UZ", "123456789", "uz_tin")]
|
||||
[BitAutoData("UZ", "123456789012", "uz_vat")]
|
||||
[BitAutoData("VE", "A-12345678-9", "ve_rif")]
|
||||
[BitAutoData("VE", "A123456789", "ve_rif")]
|
||||
[BitAutoData("VN", "1234567890", "vn_tin")]
|
||||
public void GetStripeTaxCode_WithValidCountryAndTaxId_ReturnsExpectedTaxIdType(
|
||||
string country,
|
||||
string taxId,
|
||||
string expected,
|
||||
SutProvider<TaxService> sutProvider)
|
||||
{
|
||||
var result = sutProvider.Sut.GetStripeTaxCode(country, taxId);
|
||||
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Scim.IntegrationTest.Factories;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Scim.IntegrationTest.Controllers.v2;
|
||||
|
||||
public class GroupsControllerPatchTests : IClassFixture<ScimApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly ScimApplicationFactory _factory;
|
||||
|
||||
public GroupsControllerPatchTests(ScimApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
_factory.ReinitializeDbForTests(databaseContext);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IAsyncLifetime.DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Single(databaseContext.GroupUsers);
|
||||
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
var groupUser = databaseContext.GroupUsers.FirstOrDefault();
|
||||
Assert.Equal(ScimApplicationFactory.TestOrganizationUserId2, groupUser.OrganizationUserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddSingleMember_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId2}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount + 1, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId2;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}},{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId3}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveSingleMember_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId1}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
},
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupCount, databaseContext.Groups.Count());
|
||||
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId1}\"}}, {{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId4}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Empty(databaseContext.GroupUsers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_NotFound()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = Guid.NewGuid();
|
||||
var inputModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>(),
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
var expectedResponse = new ScimErrorResponseModel
|
||||
{
|
||||
Status = StatusCodes.Status404NotFound,
|
||||
Detail = "Group not found.",
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaError }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
|
||||
|
||||
var responseModel = JsonSerializer.Deserialize<ScimErrorResponseModel>(context.Response.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Scim.Groups.Interfaces;
|
||||
using Bit.Scim.IntegrationTest.Factories;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Scim.IntegrationTest.Controllers.v2;
|
||||
|
||||
public class GroupsControllerPatchTestsvNext : IClassFixture<ScimApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private readonly ScimApplicationFactory _factory;
|
||||
|
||||
public GroupsControllerPatchTestsvNext(ScimApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
|
||||
// Enable the feature flag for new PatchGroupsCommand and stub out the old command to be safe
|
||||
_factory.SubstituteService((IFeatureService featureService)
|
||||
=> featureService.IsEnabled(FeatureFlagKeys.ShortcutDuplicatePatchRequests).Returns(true));
|
||||
_factory.SubstituteService((IPatchGroupCommand patchGroupCommand)
|
||||
=> patchGroupCommand.PatchGroupAsync(Arg.Any<Organization>(), Arg.Any<Guid>(), Arg.Any<ScimPatchModel>())
|
||||
.ThrowsAsync(new Exception("This test suite should be testing the vNext command, but the existing command was called.")));
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
_factory.ReinitializeDbForTests(databaseContext);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Task IAsyncLifetime.DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Single(databaseContext.GroupUsers);
|
||||
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
var groupUser = databaseContext.GroupUsers.FirstOrDefault();
|
||||
Assert.Equal(ScimApplicationFactory.TestOrganizationUserId2, groupUser.OrganizationUserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddSingleMember_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId2}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount + 1, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId2;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}},{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId3}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveSingleMember_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId1}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
},
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupCount, databaseContext.Groups.Count());
|
||||
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId1}\"}}, {{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId4}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Empty(databaseContext.GroupUsers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_NotFound()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = Guid.NewGuid();
|
||||
var inputModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>(),
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
var expectedResponse = new ScimErrorResponseModel
|
||||
{
|
||||
Status = StatusCodes.Status404NotFound,
|
||||
Detail = "Group not found.",
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaError }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
|
||||
|
||||
var responseModel = JsonSerializer.Deserialize<ScimErrorResponseModel>(context.Response.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
|
||||
}
|
||||
}
|
@ -9,9 +9,6 @@ namespace Bit.Scim.IntegrationTest.Controllers.v2;
|
||||
|
||||
public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private const int _initialGroupCount = 3;
|
||||
private const int _initialGroupUsersCount = 2;
|
||||
|
||||
private readonly ScimApplicationFactory _factory;
|
||||
|
||||
public GroupsControllerTests(ScimApplicationFactory factory)
|
||||
@ -237,10 +234,10 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
|
||||
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel, "Id");
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(_initialGroupCount + 1, databaseContext.Groups.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupCount + 1, databaseContext.Groups.Count());
|
||||
Assert.True(databaseContext.Groups.Any(g => g.Name == displayName && g.ExternalId == externalId));
|
||||
|
||||
Assert.Equal(_initialGroupUsersCount + 1, databaseContext.GroupUsers.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupUsersCount + 1, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == responseModel.Id && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
}
|
||||
|
||||
@ -248,7 +245,7 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public async Task Post_InvalidDisplayName_BadRequest(string displayName)
|
||||
public async Task Post_InvalidDisplayName_BadRequest(string? displayName)
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var model = new ScimGroupRequestModel
|
||||
@ -281,7 +278,7 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
|
||||
Assert.Equal(StatusCodes.Status409Conflict, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(_initialGroupCount, databaseContext.Groups.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupCount, databaseContext.Groups.Count());
|
||||
Assert.False(databaseContext.Groups.Any(g => g.Name == "New Group"));
|
||||
}
|
||||
|
||||
@ -354,216 +351,6 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
|
||||
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
|
||||
Assert.Equal(_initialGroupUsersCount, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_ReplaceMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Single(databaseContext.GroupUsers);
|
||||
|
||||
Assert.Equal(_initialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
var groupUser = databaseContext.GroupUsers.FirstOrDefault();
|
||||
Assert.Equal(ScimApplicationFactory.TestOrganizationUserId2, groupUser.OrganizationUserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddSingleMember_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId2}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(_initialGroupUsersCount + 1, databaseContext.GroupUsers.Count());
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId1));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_AddListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId2;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "add",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId2}\"}},{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId3}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId2));
|
||||
Assert.True(databaseContext.GroupUsers.Any(gu => gu.GroupId == groupId && gu.OrganizationUserId == ScimApplicationFactory.TestOrganizationUserId3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveSingleMember_ReplaceDisplayName_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var newDisplayName = "Patch Display Name";
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = $"members[value eq \"{ScimApplicationFactory.TestOrganizationUserId1}\"]",
|
||||
Value = JsonDocument.Parse("{}").RootElement
|
||||
},
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{newDisplayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(_initialGroupUsersCount - 1, databaseContext.GroupUsers.Count());
|
||||
Assert.Equal(_initialGroupCount, databaseContext.Groups.Count());
|
||||
|
||||
var group = databaseContext.Groups.FirstOrDefault(g => g.Id == groupId);
|
||||
Assert.Equal(newDisplayName, group.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_RemoveListMembers_Success()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = ScimApplicationFactory.TestGroupId1;
|
||||
var inputModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>()
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse($"[{{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId1}\"}}, {{\"value\":\"{ScimApplicationFactory.TestOrganizationUserId4}\"}}]").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Empty(databaseContext.GroupUsers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Patch_NotFound()
|
||||
{
|
||||
var organizationId = ScimApplicationFactory.TestOrganizationId1;
|
||||
var groupId = Guid.NewGuid();
|
||||
var inputModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>(),
|
||||
Schemas = new List<string>() { ScimConstants.Scim2SchemaGroup }
|
||||
};
|
||||
var expectedResponse = new ScimErrorResponseModel
|
||||
{
|
||||
Status = StatusCodes.Status404NotFound,
|
||||
Detail = "Group not found.",
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaError }
|
||||
};
|
||||
|
||||
var context = await _factory.GroupsPatchAsync(organizationId, groupId, inputModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status404NotFound, context.Response.StatusCode);
|
||||
|
||||
var responseModel = JsonSerializer.Deserialize<ScimErrorResponseModel>(context.Response.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||
AssertHelper.AssertPropertyEqual(expectedResponse, responseModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_Success()
|
||||
{
|
||||
@ -575,7 +362,7 @@ public class GroupsControllerTests : IClassFixture<ScimApplicationFactory>, IAsy
|
||||
Assert.Equal(StatusCodes.Status204NoContent, context.Response.StatusCode);
|
||||
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
Assert.Equal(_initialGroupCount - 1, databaseContext.Groups.Count());
|
||||
Assert.Equal(ScimApplicationFactory.InitialGroupCount - 1, databaseContext.Groups.Count());
|
||||
Assert.True(databaseContext.Groups.FirstOrDefault(g => g.Id == groupId) == null);
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,7 @@ public class UsersControllerTests : IClassFixture<ScimApplicationFactory>, IAsyn
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public async Task Post_InvalidEmail_BadRequest(string email)
|
||||
public async Task Post_InvalidEmail_BadRequest(string? email)
|
||||
{
|
||||
var displayName = "Test User 5";
|
||||
var externalId = "UE";
|
||||
|
@ -9,8 +9,6 @@ using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Scim.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
@ -18,7 +16,8 @@ namespace Bit.Scim.IntegrationTest.Factories;
|
||||
|
||||
public class ScimApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
{
|
||||
public readonly new TestServer Server;
|
||||
public const int InitialGroupCount = 3;
|
||||
public const int InitialGroupUsersCount = 2;
|
||||
|
||||
public static readonly Guid TestUserId1 = Guid.Parse("2e8173db-8e8d-4de1-ac38-91b15c6d8dcb");
|
||||
public static readonly Guid TestUserId2 = Guid.Parse("b57846fc-0e94-4c93-9de5-9d0389eeadfb");
|
||||
@ -33,32 +32,29 @@ public class ScimApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
public static readonly Guid TestOrganizationUserId3 = Guid.Parse("be2f9045-e2b6-4173-ad44-4c69c3ea8140");
|
||||
public static readonly Guid TestOrganizationUserId4 = Guid.Parse("1f5689b7-e96e-4840-b0b1-eb3d5b5fd514");
|
||||
|
||||
public ScimApplicationFactory()
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
WebApplicationFactory<Startup> webApplicationFactory = WithWebHostBuilder(builder =>
|
||||
base.ConfigureWebHost(builder);
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
builder.ConfigureServices(services =>
|
||||
services
|
||||
.AddAuthentication("Test")
|
||||
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
|
||||
|
||||
// Override to bypass SCIM authorization
|
||||
services.AddAuthorization(config =>
|
||||
{
|
||||
services
|
||||
.AddAuthentication("Test")
|
||||
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
|
||||
|
||||
// Override to bypass SCIM authorization
|
||||
services.AddAuthorization(config =>
|
||||
config.AddPolicy("Scim", policy =>
|
||||
{
|
||||
config.AddPolicy("Scim", policy =>
|
||||
{
|
||||
policy.RequireAssertion(a => true);
|
||||
});
|
||||
policy.RequireAssertion(a => true);
|
||||
});
|
||||
|
||||
var mailService = services.First(sd => sd.ServiceType == typeof(IMailService));
|
||||
services.Remove(mailService);
|
||||
services.AddSingleton<IMailService, NoopMailService>();
|
||||
});
|
||||
});
|
||||
|
||||
Server = webApplicationFactory.Server;
|
||||
var mailService = services.First(sd => sd.ServiceType == typeof(IMailService));
|
||||
services.Remove(mailService);
|
||||
services.AddSingleton<IMailService, NoopMailService>();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<HttpContext> GroupsGetAsync(Guid organizationId, Guid id)
|
||||
|
@ -0,0 +1,381 @@
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Scim.Groups;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Scim.Test.Groups;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class PatchGroupCommandvNextTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_ReplaceListMembers_Success(SutProvider<PatchGroupCommandvNext> sutProvider,
|
||||
Organization organization, Group group, IEnumerable<Guid> userIds)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "replace",
|
||||
Path = "members",
|
||||
Value = JsonDocument.Parse(JsonSerializer.Serialize(userIds.Select(uid => new { value = uid }).ToArray())).RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).UpdateUsersAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg =>
|
||||
arg.Count() == userIds.Count() &&
|
||||
arg.ToHashSet().SetEquals(userIds)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_ReplaceDisplayNameFromPath_Success(
|
||||
SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group, string displayName)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "replace",
|
||||
Path = "displayname",
|
||||
Value = JsonDocument.Parse($"\"{displayName}\"").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IUpdateGroupCommand>().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM);
|
||||
Assert.Equal(displayName, group.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_ReplaceDisplayNameFromValueObject_Success(SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group, string displayName)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "replace",
|
||||
Value = JsonDocument.Parse($"{{\"displayName\":\"{displayName}\"}}").RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IUpdateGroupCommand>().Received(1).UpdateGroupAsync(group, organization, EventSystemUser.SCIM);
|
||||
Assert.Equal(displayName, group.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_AddSingleMember_Success(SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group, ICollection<Guid> existingMembers, Guid userId)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id, true)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members[value eq \"{userId}\"]",
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).AddGroupUsersByIdAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg => arg.Single() == userId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_AddSingleMember_ReturnsEarlyIfAlreadyInGroup(
|
||||
SutProvider<PatchGroupCommandvNext> sutProvider,
|
||||
Organization organization,
|
||||
Group group,
|
||||
ICollection<Guid> existingMembers)
|
||||
{
|
||||
// User being added is already in group
|
||||
var userId = existingMembers.First();
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id, true)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members[value eq \"{userId}\"]",
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.AddGroupUsersByIdAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_AddListMembers_Success(SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group, ICollection<Guid> existingMembers, ICollection<Guid> userIds)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id, true)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members",
|
||||
Value = JsonDocument.Parse(JsonSerializer.Serialize(userIds.Select(uid => new { value = uid }).ToArray())).RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).AddGroupUsersByIdAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg =>
|
||||
arg.Count() == userIds.Count &&
|
||||
arg.ToHashSet().SetEquals(userIds)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_AddListMembers_IgnoresDuplicatesInRequest(
|
||||
SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group,
|
||||
ICollection<Guid> existingMembers)
|
||||
{
|
||||
// Create 3 userIds
|
||||
var fixture = new Fixture { RepeatCount = 3 };
|
||||
var userIds = fixture.CreateMany<Guid>().ToList();
|
||||
|
||||
// Copy the list and add a duplicate
|
||||
var userIdsWithDuplicate = userIds.Append(userIds.First()).ToList();
|
||||
Assert.Equal(4, userIdsWithDuplicate.Count);
|
||||
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id, true)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members",
|
||||
Value = JsonDocument.Parse(JsonSerializer
|
||||
.Serialize(userIdsWithDuplicate
|
||||
.Select(uid => new { value = uid })
|
||||
.ToArray())).RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().Received(1).AddGroupUsersByIdAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg =>
|
||||
arg.Count() == 3 &&
|
||||
arg.ToHashSet().SetEquals(userIds)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_AddListMembers_SuccessIfOnlySomeUsersAreInGroup(
|
||||
SutProvider<PatchGroupCommandvNext> sutProvider,
|
||||
Organization organization, Group group,
|
||||
ICollection<Guid> existingMembers,
|
||||
ICollection<Guid> userIds)
|
||||
{
|
||||
// A user is already in the group, but some still need to be added
|
||||
userIds.Add(existingMembers.First());
|
||||
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id, true)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "add",
|
||||
Path = $"members",
|
||||
Value = JsonDocument.Parse(JsonSerializer.Serialize(userIds.Select(uid => new { value = uid }).ToArray())).RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>()
|
||||
.Received(1)
|
||||
.AddGroupUsersByIdAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg =>
|
||||
arg.Count() == userIds.Count &&
|
||||
arg.ToHashSet().SetEquals(userIds)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_RemoveSingleMember_Success(SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group, Guid userId)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
var scimPatchModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new ScimPatchModel.OperationModel
|
||||
{
|
||||
Op = "remove",
|
||||
Path = $"members[value eq \"{userId}\"]",
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupService>().Received(1).DeleteUserAsync(group, userId, EventSystemUser.SCIM);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_RemoveListMembers_Success(SutProvider<PatchGroupCommandvNext> sutProvider,
|
||||
Organization organization, Group group, ICollection<Guid> existingMembers)
|
||||
{
|
||||
List<Guid> usersToRemove = [existingMembers.First(), existingMembers.Skip(1).First()];
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
sutProvider.GetDependency<IGroupRepository>()
|
||||
.GetManyUserIdsByIdAsync(group.Id)
|
||||
.Returns(existingMembers);
|
||||
|
||||
var scimPatchModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Op = "remove",
|
||||
Path = $"members",
|
||||
Value = JsonDocument.Parse(JsonSerializer.Serialize(usersToRemove.Select(uid => new { value = uid }).ToArray())).RootElement
|
||||
}
|
||||
},
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
var expectedRemainingUsers = existingMembers.Skip(2).ToList();
|
||||
await sutProvider.GetDependency<IGroupRepository>()
|
||||
.Received(1)
|
||||
.UpdateUsersAsync(
|
||||
group.Id,
|
||||
Arg.Is<IEnumerable<Guid>>(arg =>
|
||||
arg.Count() == expectedRemainingUsers.Count &&
|
||||
arg.ToHashSet().SetEquals(expectedRemainingUsers)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PatchGroup_NoAction_Success(
|
||||
SutProvider<PatchGroupCommandvNext> sutProvider, Organization organization, Group group)
|
||||
{
|
||||
group.OrganizationId = organization.Id;
|
||||
|
||||
var scimPatchModel = new Models.ScimPatchModel
|
||||
{
|
||||
Operations = new List<ScimPatchModel.OperationModel>(),
|
||||
Schemas = new List<string> { ScimConstants.Scim2SchemaUser }
|
||||
};
|
||||
|
||||
await sutProvider.Sut.PatchGroupAsync(group, scimPatchModel);
|
||||
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().UpdateUsersAsync(default, default);
|
||||
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyUserIdsByIdAsync(default);
|
||||
await sutProvider.GetDependency<IUpdateGroupCommand>().DidNotReceiveWithAnyArgs().UpdateGroupAsync(default, default);
|
||||
await sutProvider.GetDependency<IGroupService>().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Scim.Context;
|
||||
using Bit.Scim.Groups;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
@ -73,10 +71,6 @@ public class PostGroupCommandTests
|
||||
.GetManyByOrganizationIdAsync(organization.Id)
|
||||
.Returns(groups);
|
||||
|
||||
sutProvider.GetDependency<IScimContext>()
|
||||
.RequestScimProvider
|
||||
.Returns(ScimProviderType.Okta);
|
||||
|
||||
var group = await sutProvider.Sut.PostGroupAsync(organization, scimGroupRequestModel);
|
||||
|
||||
await sutProvider.GetDependency<ICreateGroupCommand>().Received(1).CreateGroupAsync(group, organization, EventSystemUser.SCIM, null);
|
||||
|
@ -1,10 +1,8 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Scim.Context;
|
||||
using Bit.Scim.Groups;
|
||||
using Bit.Scim.Models;
|
||||
using Bit.Scim.Utilities;
|
||||
@ -62,10 +60,6 @@ public class PutGroupCommandTests
|
||||
.GetByIdAsync(group.Id)
|
||||
.Returns(group);
|
||||
|
||||
sutProvider.GetDependency<IScimContext>()
|
||||
.RequestScimProvider
|
||||
.Returns(ScimProviderType.Okta);
|
||||
|
||||
var inputModel = new ScimGroupRequestModel
|
||||
{
|
||||
DisplayName = displayName,
|
||||
|
@ -21,3 +21,7 @@ IDP_SP_ACS_URL=http://localhost:51822/saml2/yourOrgIdHere/Acs
|
||||
# Should match server listen ports in reverse-proxy.conf
|
||||
API_PROXY_PORT=4100
|
||||
IDENTITY_PROXY_PORT=33756
|
||||
|
||||
# Optional RabbitMQ configuration
|
||||
RABBITMQ_DEFAULT_USER=bitwarden
|
||||
RABBITMQ_DEFAULT_PASS=SET_A_PASSWORD_HERE_123
|
||||
|
@ -84,6 +84,20 @@ services:
|
||||
profiles:
|
||||
- idp
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:management
|
||||
container_name: rabbitmq
|
||||
ports:
|
||||
- "5672:5672"
|
||||
- "15672:15672"
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq_data
|
||||
profiles:
|
||||
- rabbitmq
|
||||
|
||||
reverse-proxy:
|
||||
image: nginx:alpine
|
||||
container_name: reverse-proxy
|
||||
@ -95,7 +109,23 @@ services:
|
||||
profiles:
|
||||
- proxy
|
||||
|
||||
service-bus:
|
||||
container_name: service-bus
|
||||
image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest
|
||||
pull_policy: always
|
||||
volumes:
|
||||
- "./servicebusemulator_config.json:/ServiceBus_Emulator/ConfigFiles/Config.json"
|
||||
ports:
|
||||
- "5672:5672"
|
||||
environment:
|
||||
SQL_SERVER: mssql
|
||||
MSSQL_SA_PASSWORD: "${MSSQL_PASSWORD}"
|
||||
ACCEPT_EULA: "Y"
|
||||
profiles:
|
||||
- servicebus
|
||||
|
||||
volumes:
|
||||
mssql_dev_data:
|
||||
postgres_dev_data:
|
||||
mysql_dev_data:
|
||||
rabbitmq_data:
|
||||
|
@ -7,11 +7,13 @@ param(
|
||||
[switch]$mysql,
|
||||
[switch]$mssql,
|
||||
[switch]$sqlite,
|
||||
[switch]$selfhost
|
||||
[switch]$selfhost,
|
||||
[switch]$test
|
||||
)
|
||||
|
||||
# Abort on any error
|
||||
$ErrorActionPreference = "Stop"
|
||||
$currentDir = Get-Location
|
||||
|
||||
if (!$all -and !$postgres -and !$mysql -and !$sqlite) {
|
||||
$mssql = $true;
|
||||
@ -25,36 +27,62 @@ if ($all -or $postgres -or $mysql -or $sqlite) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($all -or $mssql) {
|
||||
function Get-UserSecrets {
|
||||
# The dotnet cli command sometimes adds //BEGIN and //END comments to the output, Where-Object removes comments
|
||||
# to ensure a valid json
|
||||
return dotnet user-secrets list --json --project ../src/Api | Where-Object { $_ -notmatch "^//" } | ConvertFrom-Json
|
||||
}
|
||||
|
||||
if ($selfhost) {
|
||||
$msSqlConnectionString = $(Get-UserSecrets).'dev:selfHostOverride:globalSettings:sqlServer:connectionString'
|
||||
$envName = "self-host"
|
||||
} else {
|
||||
$msSqlConnectionString = $(Get-UserSecrets).'globalSettings:sqlServer:connectionString'
|
||||
$envName = "cloud"
|
||||
}
|
||||
|
||||
Write-Host "Starting Microsoft SQL Server Migrations for $envName"
|
||||
|
||||
dotnet run --project ../util/MsSqlMigratorUtility/ "$msSqlConnectionString"
|
||||
function Get-UserSecrets {
|
||||
# The dotnet cli command sometimes adds //BEGIN and //END comments to the output, Where-Object removes comments
|
||||
# to ensure a valid json
|
||||
return dotnet user-secrets list --json --project "$currentDir/../src/Api" | Where-Object { $_ -notmatch "^//" } | ConvertFrom-Json
|
||||
}
|
||||
|
||||
$currentDir = Get-Location
|
||||
if ($all -or $mssql) {
|
||||
if ($all -or !$test) {
|
||||
if ($selfhost) {
|
||||
$msSqlConnectionString = $(Get-UserSecrets).'dev:selfHostOverride:globalSettings:sqlServer:connectionString'
|
||||
$envName = "self-host"
|
||||
} else {
|
||||
$msSqlConnectionString = $(Get-UserSecrets).'globalSettings:sqlServer:connectionString'
|
||||
$envName = "cloud"
|
||||
}
|
||||
|
||||
Foreach ($item in @(@($mysql, "MySQL", "MySqlMigrations"), @($postgres, "PostgreSQL", "PostgresMigrations"), @($sqlite, "SQLite", "SqliteMigrations"))) {
|
||||
Write-Host "Starting Microsoft SQL Server Migrations for $envName"
|
||||
dotnet run --project ../util/MsSqlMigratorUtility/ "$msSqlConnectionString"
|
||||
}
|
||||
|
||||
if ($all -or $test) {
|
||||
$testMsSqlConnectionString = $(Get-UserSecrets).'databases:3:connectionString'
|
||||
if ($testMsSqlConnectionString) {
|
||||
$testEnvName = "test databases"
|
||||
Write-Host "Starting Microsoft SQL Server Migrations for $testEnvName"
|
||||
dotnet run --project ../util/MsSqlMigratorUtility/ "$testMsSqlConnectionString"
|
||||
} else {
|
||||
Write-Host "Connection string for a test MSSQL database not found in secrets.json!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Foreach ($item in @(
|
||||
@($mysql, "MySQL", "MySqlMigrations", "mySql", 2),
|
||||
@($postgres, "PostgreSQL", "PostgresMigrations", "postgreSql", 0),
|
||||
@($sqlite, "SQLite", "SqliteMigrations", "sqlite", 1)
|
||||
)) {
|
||||
if (!$item[0] -and !$all) {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "Starting $($item[1]) Migrations"
|
||||
Set-Location "$currentDir/../util/$($item[2])/"
|
||||
dotnet ef database update
|
||||
if(!$test -or $all) {
|
||||
Write-Host "Starting $($item[1]) Migrations"
|
||||
$connectionString = $(Get-UserSecrets)."globalSettings:$($item[3]):connectionString"
|
||||
dotnet ef database update --connection "$connectionString"
|
||||
}
|
||||
if ($test -or $all) {
|
||||
$testConnectionString = $(Get-UserSecrets)."databases:$($item[4]):connectionString"
|
||||
if ($testConnectionString) {
|
||||
Write-Host "Starting $($item[1]) Migrations for test databases"
|
||||
dotnet ef database update --connection "$testConnectionString"
|
||||
} else {
|
||||
Write-Host "Connection string for a test $($item[1]) database not found in secrets.json!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set-Location "$currentDir"
|
||||
|
@ -21,7 +21,7 @@
|
||||
"connectionString": "server=localhost;uid=root;pwd=SET_A_PASSWORD_HERE_123;database=vault_dev"
|
||||
},
|
||||
"sqlite": {
|
||||
"connectionString": "Data Source=/path/to/bitwardenServer/repository/server/dev/db/bitwarden.sqlite"
|
||||
"connectionString": "Data Source=/path/to/bitwardenServer/repository/server/dev/db/bitwarden.db"
|
||||
},
|
||||
"identityServer": {
|
||||
"certificateThumbprint": "<your Identity certificate thumbprint with no spaces>"
|
||||
|
38
dev/servicebusemulator_config.json
Normal file
38
dev/servicebusemulator_config.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"UserConfig": {
|
||||
"Namespaces": [
|
||||
{
|
||||
"Name": "sbemulatorns",
|
||||
"Queues": [
|
||||
{
|
||||
"Name": "queue.1",
|
||||
"Properties": {
|
||||
"DeadLetteringOnMessageExpiration": false,
|
||||
"DefaultMessageTimeToLive": "PT1H",
|
||||
"DuplicateDetectionHistoryTimeWindow": "PT20S",
|
||||
"ForwardDeadLetteredMessagesTo": "",
|
||||
"ForwardTo": "",
|
||||
"LockDuration": "PT1M",
|
||||
"MaxDeliveryCount": 3,
|
||||
"RequiresDuplicateDetection": false,
|
||||
"RequiresSession": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"Topics": [
|
||||
{
|
||||
"Name": "event-logging",
|
||||
"Subscriptions": [
|
||||
{
|
||||
"Name": "events-write-subscription"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Logging": {
|
||||
"Type": "File"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
Invoke-RestMethod -OutFile $scriptPath -Uri "https://go.btwrdn.co/bw-ps"
|
||||
Write-Output "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Your 'bitwarden.ps1' script has been automatically upgraded. Please run it again."
|
@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cat << "EOF"
|
||||
_ _ _ _
|
||||
| |__ (_) |___ ____ _ _ __ __| | ___ _ __
|
||||
| '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \
|
||||
| |_) | | |_ \ V V / (_| | | | (_| | __/ | | |
|
||||
|_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_|
|
||||
EOF
|
||||
|
||||
cat << EOF
|
||||
Open source password management solutions
|
||||
Copyright 2015-$(date +'%Y'), 8bit Solutions LLC
|
||||
https://bitwarden.com, https://github.com/bitwarden
|
||||
===================================================
|
||||
EOF
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
SCRIPT_PATH="$DIR/$SCRIPT_NAME"
|
||||
BITWARDEN_SCRIPT_URL="https://go.btwrdn.co/bw-sh"
|
||||
|
||||
if curl -L -s -w "http_code %{http_code}" -o $SCRIPT_PATH.1 $BITWARDEN_SCRIPT_URL | grep -q "^http_code 20[0-9]"
|
||||
then
|
||||
mv $SCRIPT_PATH.1 $SCRIPT_PATH
|
||||
chmod u+x $SCRIPT_PATH
|
||||
echo "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Your 'bitwarden.sh' script has been automatically upgraded. Please run it again."
|
||||
else
|
||||
rm -f $SCRIPT_PATH.1
|
||||
fi
|
@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
##############################
|
||||
# Builds a specified service
|
||||
# Arguments:
|
||||
# 1: Project to build
|
||||
# 2: Project path
|
||||
##############################
|
||||
build() {
|
||||
local project=$1
|
||||
local project_dir=$2
|
||||
|
||||
echo "Building $project"
|
||||
echo "Build Path: $project_dir"
|
||||
echo "=================="
|
||||
|
||||
chmod u+x "$project_dir/build.sh"
|
||||
"$project_dir/build.sh"
|
||||
}
|
||||
|
||||
# Get Project
|
||||
PROJECT=$1; shift
|
||||
|
||||
case "$PROJECT" in
|
||||
"admin" | "Admin") build Admin $PWD/src/Admin ;;
|
||||
"api" | "Api") build Api $PWD/src/Api ;;
|
||||
"billing" | "Billing") build Billing $PWD/src/Billing ;;
|
||||
"events" | "Events") build Events $PWD/src/Events ;;
|
||||
"eventsprocessor" | "EventsProcessor") build EventsProcessor $PWD/src/EventsProcessor ;;
|
||||
"icons" | "Icons") build Icons $PWD/src/Icons ;;
|
||||
"identity" | "Identity") build Identity $PWD/src/Identity ;;
|
||||
"notifications" | "Notifications") build Notifications $PWD/src/Notifications ;;
|
||||
"server" | "Server") build Server $PWD/util/Server ;;
|
||||
"sso" | "Sso") build Sso $PWD/bitwarden_license/src/Sso ;;
|
||||
"")
|
||||
build Admin $PWD/src/Admin
|
||||
build Api $PWD/src/Api
|
||||
build Billing $PWD/src/Billing
|
||||
build Events $PWD/src/Events
|
||||
build EventsProcessor $PWD/src/EventsProcessor
|
||||
build Icons $PWD/src/Icons
|
||||
build Identity $PWD/src/Identity
|
||||
build Notifications $PWD/src/Notifications
|
||||
build Server $PWD/util/Server
|
||||
build Sso $PWD/bitwarden_license/src/Sso
|
||||
;;
|
||||
esac
|
@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
##############################
|
||||
# Builds the docker image from a pre-built build directory
|
||||
# Arguments:
|
||||
# 1: Project Name
|
||||
# 2: Project Directory
|
||||
# 3: Docker Tag
|
||||
# 4: Docker push
|
||||
# Outputs:
|
||||
# Output to STDOUT or STDERR.
|
||||
# Returns:
|
||||
# Returned values other than the default exit status of the last command run.
|
||||
##############################
|
||||
docker_build() {
|
||||
local project_name=$1
|
||||
local project_dir=$2
|
||||
local docker_tag=$3
|
||||
local docker_push=$4
|
||||
|
||||
local project_name_lower=$(echo "$project_name" | awk '{print tolower($0)}')
|
||||
|
||||
echo "Building docker image: bitwarden/$project_name_lower:$docker_tag"
|
||||
echo "=============================="
|
||||
docker build -t bitwarden/$project_name_lower:$docker_tag $project_dir
|
||||
|
||||
if [ "$docker_push" == "1" ]; then
|
||||
docker push bitwarden/$project_name_lower:$docker_tag
|
||||
fi
|
||||
}
|
||||
|
||||
# Get Project
|
||||
PROJECT=$1; shift
|
||||
|
||||
# Get Params
|
||||
TAG="latest"
|
||||
PUSH=0
|
||||
|
||||
while [ ! $# -eq 0 ]; do
|
||||
case "$1" in
|
||||
-t | --tag)
|
||||
if [[ $2 ]]; then
|
||||
TAG="$2"
|
||||
shift
|
||||
else
|
||||
exp "--tag requires a value"
|
||||
fi
|
||||
;;
|
||||
--push) PUSH=1 ;;
|
||||
-h | --help ) usage && exit ;;
|
||||
*) usage && exit ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
|
||||
case "$PROJECT" in
|
||||
"admin" | "Admin") docker_build Admin $PWD/src/Admin $TAG $PUSH ;;
|
||||
"api" | "Api") docker_build Api $PWD/src/Api $TAG $PUSH ;;
|
||||
"attachments" | "Attachments") docker_build Attachments $PWD/util/Attachments $TAG $PUSH ;;
|
||||
#"billing" | "Billing") docker_build Billing $PWD/src/Billing $TAG $PUSH ;;
|
||||
"events" | "Events") docker_build Events $PWD/src/Events $TAG $PUSH ;;
|
||||
"eventsprocessor" | "EventsProcessor") docker_build EventsProcessor $PWD/src/EventsProcessor $TAG $PUSH ;;
|
||||
"icons" | "Icons") docker_build Icons $PWD/src/Icons $TAG $PUSH ;;
|
||||
"identity" | "Identity") docker_build Identity $PWD/src/Identity $TAG $PUSH ;;
|
||||
"mssql" | "MsSql" | "Mssql") docker_build MsSql $PWD/util/MsSql $TAG $PUSH ;;
|
||||
"nginx" | "Nginx") docker_build Nginx $PWD/util/Nginx $TAG $PUSH ;;
|
||||
"notifications" | "Notifications") docker_build Notifications $PWD/src/Notifications $TAG $PUSH ;;
|
||||
"server" | "Server") docker_build Server $PWD/util/Server $TAG $PUSH ;;
|
||||
"setup" | "Setup") docker_build Setup $PWD/util/Setup $TAG $PUSH ;;
|
||||
"sso" | "Sso") docker_build Sso $PWD/bitwarden_license/src/Sso $TAG $PUSH ;;
|
||||
"")
|
||||
docker_build Admin $PWD/src/Admin $TAG $PUSH
|
||||
docker_build Api $PWD/src/Api $TAG $PUSH
|
||||
docker_build Attachments $PWD/util/Attachments $TAG $PUSH
|
||||
#docker_build Billing $PWD/src/Billing $TAG $PUSH
|
||||
docker_build Events $PWD/src/Events $TAG $PUSH
|
||||
docker_build EventsProcessor $PWD/src/EventsProcessor $TAG $PUSH
|
||||
docker_build Icons $PWD/src/Icons $TAG $PUSH
|
||||
docker_build Identity $PWD/src/Identity $TAG $PUSH
|
||||
docker_build MsSql $PWD/util/MsSql $TAG $PUSH
|
||||
docker_build Nginx $PWD/util/Nginx $TAG $PUSH
|
||||
docker_build Notifications $PWD/src/Notifications $TAG $PUSH
|
||||
docker_build Server $PWD/util/Server $TAG $PUSH
|
||||
docker_build Setup $PWD/util/Setup $TAG $PUSH
|
||||
docker_build Sso $PWD/bitwarden_license/src/Sso $TAG $PUSH
|
||||
;;
|
||||
esac
|
@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
##############################
|
||||
# Builds the docker image from a pre-built build directory
|
||||
# Arguments:
|
||||
# 1: Project Name
|
||||
# 2: Project Directory
|
||||
# 3: Docker Tag
|
||||
# 4: Docker push
|
||||
##############################
|
||||
deploy_app_service() {
|
||||
local project_name=$1
|
||||
local project_dir=$2
|
||||
|
||||
local project_name_lower=$(echo "$project_name" | awk '{print tolower($0)}')
|
||||
local webapp_name=$(az keyvault secret show --vault-name bitwarden-qa-kv --name appservices-$project_name_lower-webapp-name --query value --output tsv)
|
||||
|
||||
cd $project_dir/obj/build-output/publish
|
||||
zip -r $project_name.zip .
|
||||
mv $project_name.zip ../../../
|
||||
#az webapp deploy --resource-group bw-qa-env --name $webapp_name \
|
||||
# --src-path $project_name.zip --verbose --type zip --restart true --subscription "Bitwarden Test"
|
||||
}
|
||||
|
||||
PROJECT=$1; shift
|
||||
|
||||
case "$PROJECT" in
|
||||
"api" | "Api") deploy_app_service Api $PWD/src/Api ;;
|
||||
"admin" | "Admin") deploy_app_service Admin $PWD/src/Admin ;;
|
||||
"identity" | "Identity") deploy_app_service Identity $PWD/src/Identity ;;
|
||||
"events" | "Events") deploy_app_service Events $PWD/src/Events ;;
|
||||
"billing" | "Billing") deploy_app_service Billing $PWD/src/Billing ;;
|
||||
"sso" | "Sso") deploy_app_service Sso $PWD/bitwarden_license/src/Sso ;;
|
||||
"")
|
||||
deploy_app_service Api $PWD/src/Api
|
||||
deploy_app_service Admin $PWD/src/Admin
|
||||
deploy_app_service Identity $PWD/src/Identity
|
||||
deploy_app_service Events $PWD/src/Events
|
||||
deploy_app_service Billing $PWD/src/Billing
|
||||
deploy_app_service Sso $PWD/bitwarden_license/src/Sso
|
||||
;;
|
||||
esac
|
@ -1,16 +0,0 @@
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
$bitwardenPath = Split-Path $scriptPath | Split-Path | Split-Path
|
||||
$files = Get-ChildItem $bitwardenPath
|
||||
$scriptFound = $false
|
||||
foreach ($file in $files) {
|
||||
if ($file.Name -eq "bitwarden.ps1") {
|
||||
$scriptFound = $true
|
||||
Invoke-RestMethod -OutFile "$($bitwardenPath)/bitwarden.ps1" -Uri "https://go.btwrdn.co/bw-ps"
|
||||
Write-Output "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Your 'bitwarden.ps1' script has been automatically upgraded. Please run it again."
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $scriptFound) {
|
||||
Write-Output "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Please run 'bitwarden.ps1 -updateself' before updating."
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
cat << "EOF"
|
||||
_ _ _ _
|
||||
| |__ (_) |___ ____ _ _ __ __| | ___ _ __
|
||||
| '_ \| | __\ \ /\ / / _` | '__/ _` |/ _ \ '_ \
|
||||
| |_) | | |_ \ V V / (_| | | | (_| | __/ | | |
|
||||
|_.__/|_|\__| \_/\_/ \__,_|_| \__,_|\___|_| |_|
|
||||
EOF
|
||||
|
||||
cat << EOF
|
||||
Open source password management solutions
|
||||
Copyright 2015-$(date +'%Y'), 8bit Solutions LLC
|
||||
https://bitwarden.com, https://github.com/bitwarden
|
||||
===================================================
|
||||
EOF
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
BITWARDEN_SCRIPT_URL="https://go.btwrdn.co/bw-sh"
|
||||
|
||||
cd $DIR
|
||||
cd ../../
|
||||
|
||||
FOUND=false
|
||||
|
||||
for i in *.sh; do
|
||||
if [ $i = "bitwarden.sh" ]
|
||||
then
|
||||
FOUND=true
|
||||
if curl -L -s -w "http_code %{http_code}" -o bitwarden.sh.1 $BITWARDEN_SCRIPT_URL | grep -q "^http_code 20[0-9]"
|
||||
then
|
||||
mv bitwarden.sh.1 bitwarden.sh
|
||||
chmod u+x bitwarden.sh
|
||||
echo "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Your 'bitwarden.sh' script has been automatically upgraded. Please run it again."
|
||||
else
|
||||
rm -f bitwarden.sh.1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $FOUND = false ]
|
||||
then
|
||||
echo "We have moved our self-hosted scripts to their own repository (https://github.com/bitwarden/self-host). Please run 'bitwarden.sh updateself' before updating."
|
||||
fi
|
@ -16,7 +16,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Billing\Controllers\" />
|
||||
<Folder Include="Billing\Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Choose>
|
||||
|
@ -3,9 +3,9 @@ using Bit.Admin.AdminConsole.Models;
|
||||
using Bit.Admin.Enums;
|
||||
using Bit.Admin.Services;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
@ -57,6 +57,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IRemoveOrganizationFromProviderCommand _removeOrganizationFromProviderCommand;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationService organizationService,
|
||||
@ -83,7 +84,8 @@ public class OrganizationsController : Controller
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IRemoveOrganizationFromProviderCommand removeOrganizationFromProviderCommand,
|
||||
IProviderBillingService providerBillingService,
|
||||
IFeatureService featureService)
|
||||
IFeatureService featureService,
|
||||
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -110,6 +112,7 @@ public class OrganizationsController : Controller
|
||||
_removeOrganizationFromProviderCommand = removeOrganizationFromProviderCommand;
|
||||
_providerBillingService = providerBillingService;
|
||||
_featureService = featureService;
|
||||
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Org_List_View)]
|
||||
@ -306,7 +309,7 @@ public class OrganizationsController : Controller
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequirePermission(Permission.Org_Delete)]
|
||||
[RequirePermission(Permission.Org_RequestDelete)]
|
||||
public async Task<IActionResult> DeleteInitiation(Guid id, OrganizationInitiateDeleteModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
@ -320,7 +323,7 @@ public class OrganizationsController : Controller
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization != null)
|
||||
{
|
||||
await _organizationService.InitiateDeleteAsync(organization, model.AdminEmail);
|
||||
await _organizationInitiateDeleteCommand.InitiateDeleteAsync(organization, model.AdminEmail);
|
||||
TempData["Success"] = "The request to initiate deletion of the organization has been sent.";
|
||||
}
|
||||
}
|
||||
@ -418,6 +421,11 @@ public class OrganizationsController : Controller
|
||||
|
||||
private void UpdateOrganization(Organization organization, OrganizationEditModel model)
|
||||
{
|
||||
if (_accessControlService.UserHasPermission(Permission.Org_Name_Edit))
|
||||
{
|
||||
organization.Name = WebUtility.HtmlEncode(model.Name);
|
||||
}
|
||||
|
||||
if (_accessControlService.UserHasPermission(Permission.Org_CheckEnabledBox))
|
||||
{
|
||||
organization.Enabled = model.Enabled;
|
||||
@ -448,6 +456,7 @@ public class OrganizationsController : Controller
|
||||
organization.UseTotp = model.UseTotp;
|
||||
organization.UsersGetPremium = model.UsersGetPremium;
|
||||
organization.UseSecretsManager = model.UseSecretsManager;
|
||||
organization.UseRiskInsights = model.UseRiskInsights;
|
||||
|
||||
//secrets
|
||||
organization.SmSeats = model.SmSeats;
|
||||
@ -475,14 +484,6 @@ public class OrganizationsController : Controller
|
||||
Organization organization,
|
||||
OrganizationEditModel update)
|
||||
{
|
||||
var scaleMSPOnClientOrganizationUpdate =
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM14401_ScaleMSPOnClientOrganizationUpdate);
|
||||
|
||||
if (!scaleMSPOnClientOrganizationUpdate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id);
|
||||
|
||||
// No scaling required
|
||||
|
@ -235,7 +235,8 @@ public class ProvidersController : Controller
|
||||
|
||||
var users = await _providerUserRepository.GetManyDetailsByProviderAsync(id);
|
||||
var providerOrganizations = await _providerOrganizationRepository.GetManyDetailsByProviderAsync(id);
|
||||
return View(new ProviderViewModel(provider, users, providerOrganizations));
|
||||
var providerPlans = await _providerPlanRepository.GetByProviderId(id);
|
||||
return View(new ProviderViewModel(provider, users, providerOrganizations, providerPlans.ToList()));
|
||||
}
|
||||
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
|
@ -80,6 +80,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
Use2fa = org.Use2fa;
|
||||
UseApi = org.UseApi;
|
||||
UseSecretsManager = org.UseSecretsManager;
|
||||
UseRiskInsights = org.UseRiskInsights;
|
||||
UseResetPassword = org.UseResetPassword;
|
||||
SelfHost = org.SelfHost;
|
||||
UsersGetPremium = org.UsersGetPremium;
|
||||
@ -144,6 +145,8 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public bool UseScim { get; set; }
|
||||
[Display(Name = "Secrets Manager")]
|
||||
public new bool UseSecretsManager { get; set; }
|
||||
[Display(Name = "Risk Insights")]
|
||||
public new bool UseRiskInsights { get; set; }
|
||||
[Display(Name = "Self Host")]
|
||||
public bool SelfHost { get; set; }
|
||||
[Display(Name = "Users Get Premium")]
|
||||
@ -284,6 +287,7 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
existingOrganization.Use2fa = Use2fa;
|
||||
existingOrganization.UseApi = UseApi;
|
||||
existingOrganization.UseSecretsManager = UseSecretsManager;
|
||||
existingOrganization.UseRiskInsights = UseRiskInsights;
|
||||
existingOrganization.UseResetPassword = UseResetPassword;
|
||||
existingOrganization.SelfHost = SelfHost;
|
||||
existingOrganization.UsersGetPremium = UsersGetPremium;
|
||||
|
@ -69,4 +69,5 @@ public class OrganizationViewModel
|
||||
public int ServiceAccountsCount { get; set; }
|
||||
public int OccupiedSmSeatsCount { get; set; }
|
||||
public bool UseSecretsManager => Organization.UseSecretsManager;
|
||||
public bool UseRiskInsights => Organization.UseRiskInsights;
|
||||
}
|
||||
|
@ -10,4 +10,6 @@ public class OrganizationsModel : PagedModel<Organization>
|
||||
public bool? Paid { get; set; }
|
||||
public string Action { get; set; }
|
||||
public bool SelfHosted { get; set; }
|
||||
|
||||
public double StorageGB(Organization org) => org.Storage.HasValue ? Math.Round(org.Storage.Value / 1073741824D, 2) : 0;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public class ProviderEditModel : ProviderViewModel, IValidatableObject
|
||||
IEnumerable<ProviderOrganizationOrganizationDetails> organizations,
|
||||
IReadOnlyCollection<ProviderPlan> providerPlans,
|
||||
string gatewayCustomerUrl = null,
|
||||
string gatewaySubscriptionUrl = null) : base(provider, providerUsers, organizations)
|
||||
string gatewaySubscriptionUrl = null) : base(provider, providerUsers, organizations, providerPlans)
|
||||
{
|
||||
Name = provider.DisplayName();
|
||||
BusinessName = provider.DisplayBusinessName();
|
||||
|
@ -1,6 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Admin.Billing.Models;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
using Bit.Core.Billing.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
|
||||
namespace Bit.Admin.AdminConsole.Models;
|
||||
|
||||
@ -8,17 +11,57 @@ public class ProviderViewModel
|
||||
{
|
||||
public ProviderViewModel() { }
|
||||
|
||||
public ProviderViewModel(Provider provider, IEnumerable<ProviderUserUserDetails> providerUsers, IEnumerable<ProviderOrganizationOrganizationDetails> organizations)
|
||||
public ProviderViewModel(
|
||||
Provider provider,
|
||||
IEnumerable<ProviderUserUserDetails> providerUsers,
|
||||
IEnumerable<ProviderOrganizationOrganizationDetails> organizations,
|
||||
IReadOnlyCollection<ProviderPlan> providerPlans)
|
||||
{
|
||||
Provider = provider;
|
||||
UserCount = providerUsers.Count();
|
||||
ProviderAdmins = providerUsers.Where(u => u.Type == ProviderUserType.ProviderAdmin);
|
||||
|
||||
ProviderOrganizations = organizations.Where(o => o.ProviderId == provider.Id);
|
||||
|
||||
if (Provider.Type == ProviderType.Msp)
|
||||
{
|
||||
var usedTeamsSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.TeamsMonthly)
|
||||
.Sum(po => po.OccupiedSeats) ?? 0;
|
||||
var teamsProviderPlan = providerPlans.FirstOrDefault(plan => plan.PlanType == PlanType.TeamsMonthly);
|
||||
if (teamsProviderPlan != null && teamsProviderPlan.IsConfigured())
|
||||
{
|
||||
ProviderPlanViewModels.Add(new ProviderPlanViewModel("Teams (Monthly) Subscription", teamsProviderPlan, usedTeamsSeats));
|
||||
}
|
||||
|
||||
var usedEnterpriseSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.EnterpriseMonthly)
|
||||
.Sum(po => po.OccupiedSeats) ?? 0;
|
||||
var enterpriseProviderPlan = providerPlans.FirstOrDefault(plan => plan.PlanType == PlanType.EnterpriseMonthly);
|
||||
if (enterpriseProviderPlan != null && enterpriseProviderPlan.IsConfigured())
|
||||
{
|
||||
ProviderPlanViewModels.Add(new ProviderPlanViewModel("Enterprise (Monthly) Subscription", enterpriseProviderPlan, usedEnterpriseSeats));
|
||||
}
|
||||
}
|
||||
else if (Provider.Type == ProviderType.MultiOrganizationEnterprise)
|
||||
{
|
||||
var usedEnterpriseSeats = ProviderOrganizations.Where(po => po.PlanType == PlanType.EnterpriseMonthly)
|
||||
.Sum(po => po.OccupiedSeats).GetValueOrDefault(0);
|
||||
var enterpriseProviderPlan = providerPlans.FirstOrDefault();
|
||||
if (enterpriseProviderPlan != null && enterpriseProviderPlan.IsConfigured())
|
||||
{
|
||||
var planLabel = enterpriseProviderPlan.PlanType switch
|
||||
{
|
||||
PlanType.EnterpriseMonthly => "Enterprise (Monthly) Subscription",
|
||||
PlanType.EnterpriseAnnually => "Enterprise (Annually) Subscription",
|
||||
_ => string.Empty
|
||||
};
|
||||
|
||||
ProviderPlanViewModels.Add(new ProviderPlanViewModel(planLabel, enterpriseProviderPlan, usedEnterpriseSeats));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int UserCount { get; set; }
|
||||
public Provider Provider { get; set; }
|
||||
public IEnumerable<ProviderUserUserDetails> ProviderAdmins { get; set; }
|
||||
public IEnumerable<ProviderOrganizationOrganizationDetails> ProviderOrganizations { get; set; }
|
||||
public List<ProviderPlanViewModel> ProviderPlanViewModels { get; set; } = [];
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
var canViewOrganizationInformation = AccessControlService.UserHasPermission(Permission.Org_OrgInformation_View);
|
||||
var canViewBillingInformation = AccessControlService.UserHasPermission(Permission.Org_BillingInformation_View);
|
||||
var canInitiateTrial = AccessControlService.UserHasPermission(Permission.Org_InitiateTrial);
|
||||
var canRequestDelete = AccessControlService.UserHasPermission(Permission.Org_RequestDelete);
|
||||
var canDelete = AccessControlService.UserHasPermission(Permission.Org_Delete);
|
||||
var canUnlinkFromProvider = AccessControlService.UserHasPermission(Permission.Provider_Edit);
|
||||
}
|
||||
@ -120,12 +121,15 @@
|
||||
Unlink provider
|
||||
</button>
|
||||
}
|
||||
@if (canDelete)
|
||||
@if (canRequestDelete)
|
||||
{
|
||||
<form asp-action="DeleteInitiation" asp-route-id="@Model.Organization.Id" id="initiate-delete-form">
|
||||
<input type="hidden" name="AdminEmail" id="AdminEmail" />
|
||||
<button class="btn btn-danger me-2" type="submit">Request Delete</button>
|
||||
</form>
|
||||
}
|
||||
@if (canDelete)
|
||||
{
|
||||
<form asp-action="Delete" asp-route-id="@Model.Organization.Id"
|
||||
onsubmit="return confirm('Are you sure you want to hard delete this organization?')">
|
||||
<button class="btn btn-outline-danger" type="submit">Delete</button>
|
||||
|
@ -81,16 +81,7 @@
|
||||
<i class="fa fa-smile-o fa-lg fa-fw text-body-secondary" title="Freeloader"></i>
|
||||
}
|
||||
}
|
||||
@if(org.MaxStorageGb.HasValue && org.MaxStorageGb > 1)
|
||||
{
|
||||
<i class="fa fa-plus-square fa-lg fa-fw"
|
||||
title="Additional Storage, @(org.MaxStorageGb - 1) GB"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-plus-square-o fa-lg fa-fw text-body-secondary"
|
||||
title="No Additional Storage"></i>
|
||||
}
|
||||
<i class="fa fa-hdd-o fa-lg fa-fw" title="Used Storage, @Model.StorageGB(org) GB"></i>
|
||||
@if(org.Enabled)
|
||||
{
|
||||
<i class="fa fa-check-circle fa-lg fa-fw"
|
||||
|
@ -55,19 +55,11 @@
|
||||
<dt class="col-sm-4 col-lg-3">Administrators manage all collections</dt>
|
||||
<dd id="pm-manage-collections" class="col-sm-8 col-lg-9">@(Model.Organization.AllowAdminAccessToAllCollectionItems ? "On" : "Off")</dd>
|
||||
|
||||
@if (!FeatureService.IsEnabled(Bit.Core.FeatureFlagKeys.LimitCollectionCreationDeletionSplit))
|
||||
{
|
||||
<dt class="col-sm-4 col-lg-3">Limit collection creation to administrators</dt>
|
||||
<dd id="pm-collection-creation" class="col-sm-8 col-lg-9">@(Model.Organization.LimitCollectionCreationDeletion ? "On" : "Off")</dd>
|
||||
}
|
||||
else
|
||||
{
|
||||
<dt class="col-sm-4 col-lg-3">Limit collection creation to administrators</dt>
|
||||
<dd id="pm-collection-creation" class="col-sm-8 col-lg-9">@(Model.Organization.LimitCollectionCreation ? "On" : "Off")</dd>
|
||||
<dt class="col-sm-4 col-lg-3">Limit collection creation to administrators</dt>
|
||||
<dd id="pm-collection-creation" class="col-sm-8 col-lg-9">@(Model.Organization.LimitCollectionCreation ? "On" : "Off")</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Limit collection deletion to administrators</dt>
|
||||
<dd id="pm-collection-deletion" class="col-sm-8 col-lg-9">@(Model.Organization.LimitCollectionDeletion ? "On" : "Off")</dd>
|
||||
}
|
||||
<dt class="col-sm-4 col-lg-3">Limit collection deletion to administrators</dt>
|
||||
<dd id="pm-collection-deletion" class="col-sm-8 col-lg-9">@(Model.Organization.LimitCollectionDeletion ? "On" : "Off")</dd>
|
||||
</dl>
|
||||
|
||||
<h2>Secrets Manager</h2>
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
<h2>Provider Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
@if (Model.ProviderPlanViewModels.Any())
|
||||
{
|
||||
@await Html.PartialAsync("~/Billing/Views/Providers/ProviderPlans.cshtml", Model.ProviderPlanViewModels)
|
||||
}
|
||||
@await Html.PartialAsync("Admins", Model)
|
||||
<form method="post" id="edit-form">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
|
@ -7,5 +7,9 @@
|
||||
|
||||
<h2>Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model)
|
||||
@if (Model.ProviderPlanViewModels.Any())
|
||||
{
|
||||
@await Html.PartialAsync("ProviderPlans", Model.ProviderPlanViewModels)
|
||||
}
|
||||
@await Html.PartialAsync("Admins", Model)
|
||||
@await Html.PartialAsync("Organizations", Model)
|
||||
|
@ -12,6 +12,7 @@
|
||||
var canViewBilling = AccessControlService.UserHasPermission(Permission.Org_Billing_View);
|
||||
var canViewPlan = AccessControlService.UserHasPermission(Permission.Org_Plan_View);
|
||||
var canViewLicensing = AccessControlService.UserHasPermission(Permission.Org_Licensing_View);
|
||||
var canEditName = AccessControlService.UserHasPermission(Permission.Org_Name_Edit);
|
||||
var canCheckEnabled = AccessControlService.UserHasPermission(Permission.Org_CheckEnabledBox);
|
||||
var canEditPlan = AccessControlService.UserHasPermission(Permission.Org_Plan_Edit);
|
||||
var canEditLicensing = AccessControlService.UserHasPermission(Permission.Org_Licensing_Edit);
|
||||
@ -28,7 +29,7 @@
|
||||
<div class="col-sm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" asp-for="Name"></label>
|
||||
<input type="text" class="form-control" asp-for="Name" value="@Model.Name" required>
|
||||
<input type="text" class="form-control" asp-for="Name" value="@Model.Name" required disabled="@(canEditName ? null : "disabled")">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -94,7 +95,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2>Features</h2>
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-4">
|
||||
<div class="col-4">
|
||||
<h3>General</h3>
|
||||
<div class="form-check mb-2">
|
||||
@ -146,7 +147,7 @@
|
||||
<label class="form-check-label" asp-for="UseCustomPermissions"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="col-3">
|
||||
<h3>Password Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseTotp" disabled='@(canEditPlan ? null : "disabled")'>
|
||||
@ -157,13 +158,20 @@
|
||||
<label class="form-check-label" asp-for="UsersGetPremium"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="col-3">
|
||||
<h3>Secrets Manager</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseSecretsManager" disabled='@(canEditPlan ? null : "disabled")'>
|
||||
<label class="form-check-label" asp-for="UseSecretsManager"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<h3>Access Intelligence</h3>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" asp-for="UseRiskInsights" disabled='@(canEditPlan ? null : "disabled")'>
|
||||
<label class="form-check-label" asp-for="UseRiskInsights"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
26
src/Admin/Billing/Models/ProviderPlanViewModel.cs
Normal file
26
src/Admin/Billing/Models/ProviderPlanViewModel.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Bit.Core.Billing.Entities;
|
||||
|
||||
namespace Bit.Admin.Billing.Models;
|
||||
|
||||
public class ProviderPlanViewModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int PurchasedSeats { get; set; }
|
||||
public int AssignedSeats { get; set; }
|
||||
public int UsedSeats { get; set; }
|
||||
public int RemainingSeats { get; set; }
|
||||
|
||||
public ProviderPlanViewModel(
|
||||
string name,
|
||||
ProviderPlan providerPlan,
|
||||
int usedSeats)
|
||||
{
|
||||
var purchasedSeats = (providerPlan.SeatMinimum ?? 0) + (providerPlan.PurchasedSeats ?? 0);
|
||||
|
||||
Name = name;
|
||||
PurchasedSeats = purchasedSeats;
|
||||
AssignedSeats = providerPlan.AllocatedSeats ?? 0;
|
||||
UsedSeats = usedSeats;
|
||||
RemainingSeats = purchasedSeats - AssignedSeats;
|
||||
}
|
||||
}
|
18
src/Admin/Billing/Views/Providers/ProviderPlans.cshtml
Normal file
18
src/Admin/Billing/Views/Providers/ProviderPlans.cshtml
Normal file
@ -0,0 +1,18 @@
|
||||
@model List<Bit.Admin.Billing.Models.ProviderPlanViewModel>
|
||||
@foreach (var plan in Model)
|
||||
{
|
||||
<h2>@plan.Name</h2>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Purchased Seats</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@plan.PurchasedSeats</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Assigned Seats</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@plan.AssignedSeats</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Used Seats</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@plan.UsedSeats</dd>
|
||||
|
||||
<dt class="col-sm-4 col-lg-3">Remaining Seats</dt>
|
||||
<dd class="col-sm-8 col-lg-9">@plan.RemainingSeats</dd>
|
||||
</dl>
|
||||
}
|
@ -4,16 +4,17 @@ using Bit.Admin.Enums;
|
||||
using Bit.Admin.Models;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TaxRate = Bit.Core.Entities.TaxRate;
|
||||
|
||||
namespace Bit.Admin.Controllers;
|
||||
|
||||
@ -28,8 +29,8 @@ public class ToolsController : Controller
|
||||
private readonly ITransactionRepository _transactionRepository;
|
||||
private readonly IInstallationRepository _installationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly ITaxRateRepository _taxRateRepository;
|
||||
private readonly IStripeAdapter _stripeAdapter;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
@ -41,7 +42,7 @@ public class ToolsController : Controller
|
||||
ITransactionRepository transactionRepository,
|
||||
IInstallationRepository installationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ITaxRateRepository taxRateRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripeAdapter stripeAdapter,
|
||||
IWebHostEnvironment environment)
|
||||
@ -53,7 +54,7 @@ public class ToolsController : Controller
|
||||
_transactionRepository = transactionRepository;
|
||||
_installationRepository = installationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_taxRateRepository = taxRateRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_paymentService = paymentService;
|
||||
_stripeAdapter = stripeAdapter;
|
||||
_environment = environment;
|
||||
@ -220,6 +221,44 @@ public class ToolsController : Controller
|
||||
return RedirectToAction("Edit", "Organizations", new { id = model.OrganizationId.Value });
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_PromoteProviderServiceUser)]
|
||||
public IActionResult PromoteProviderServiceUser()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequirePermission(Permission.Tools_PromoteProviderServiceUser)]
|
||||
public async Task<IActionResult> PromoteProviderServiceUser(PromoteProviderServiceUserModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var providerUsers = await _providerUserRepository.GetManyByProviderAsync(
|
||||
model.ProviderId.Value, null);
|
||||
var serviceUser = providerUsers.FirstOrDefault(u => u.UserId == model.UserId.Value);
|
||||
if (serviceUser == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.UserId), "Service User Id not found in this provider.");
|
||||
}
|
||||
else if (serviceUser.Type != Core.AdminConsole.Enums.Provider.ProviderUserType.ServiceUser)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.UserId), "User is not a service user of this provider.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
serviceUser.Type = Core.AdminConsole.Enums.Provider.ProviderUserType.ProviderAdmin;
|
||||
await _providerUserRepository.ReplaceAsync(serviceUser);
|
||||
return RedirectToAction("Edit", "Providers", new { id = model.ProviderId.Value });
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_GenerateLicenseFile)]
|
||||
public IActionResult GenerateLicense()
|
||||
{
|
||||
@ -300,165 +339,6 @@ public class ToolsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_ManageTaxRates)]
|
||||
public async Task<IActionResult> TaxRate(int page = 1, int count = 25)
|
||||
{
|
||||
if (page < 1)
|
||||
{
|
||||
page = 1;
|
||||
}
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
count = 1;
|
||||
}
|
||||
|
||||
var skip = (page - 1) * count;
|
||||
var rates = await _taxRateRepository.SearchAsync(skip, count);
|
||||
return View(new TaxRatesModel
|
||||
{
|
||||
Items = rates.ToList(),
|
||||
Page = page,
|
||||
Count = count
|
||||
});
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_ManageTaxRates)]
|
||||
public async Task<IActionResult> TaxRateAddEdit(string stripeTaxRateId = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(stripeTaxRateId))
|
||||
{
|
||||
return View(new TaxRateAddEditModel());
|
||||
}
|
||||
|
||||
var rate = await _taxRateRepository.GetByIdAsync(stripeTaxRateId);
|
||||
var model = new TaxRateAddEditModel()
|
||||
{
|
||||
StripeTaxRateId = stripeTaxRateId,
|
||||
Country = rate.Country,
|
||||
State = rate.State,
|
||||
PostalCode = rate.PostalCode,
|
||||
Rate = rate.Rate
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequirePermission(Permission.Tools_ManageTaxRates)]
|
||||
public async Task<IActionResult> TaxRateUpload(IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
// Build rates and validate them first before updating DB & Stripe
|
||||
var taxRateUpdates = new List<TaxRate>();
|
||||
var currentTaxRates = await _taxRateRepository.GetAllActiveAsync();
|
||||
using var reader = new StreamReader(file.OpenReadStream());
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = await reader.ReadLineAsync();
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var taxParts = line.Split(',');
|
||||
if (taxParts.Length < 2)
|
||||
{
|
||||
throw new Exception($"This line is not in the format of <postal code>,<rate>,<state code>,<country code>: {line}");
|
||||
}
|
||||
var postalCode = taxParts[0].Trim();
|
||||
if (string.IsNullOrWhiteSpace(postalCode))
|
||||
{
|
||||
throw new Exception($"'{line}' is not valid, the first element must contain a postal code.");
|
||||
}
|
||||
if (!decimal.TryParse(taxParts[1], out var rate) || rate <= 0M || rate > 100)
|
||||
{
|
||||
throw new Exception($"{taxParts[1]} is not a valid rate/decimal for {postalCode}");
|
||||
}
|
||||
var state = taxParts.Length > 2 ? taxParts[2] : null;
|
||||
var country = (taxParts.Length > 3 ? taxParts[3] : null);
|
||||
if (string.IsNullOrWhiteSpace(country))
|
||||
{
|
||||
country = "US";
|
||||
}
|
||||
var taxRate = currentTaxRates.FirstOrDefault(r => r.Country == country && r.PostalCode == postalCode) ??
|
||||
new TaxRate
|
||||
{
|
||||
Country = country,
|
||||
PostalCode = postalCode,
|
||||
Active = true,
|
||||
};
|
||||
taxRate.Rate = rate;
|
||||
taxRate.State = state ?? taxRate.State;
|
||||
taxRateUpdates.Add(taxRate);
|
||||
}
|
||||
|
||||
foreach (var taxRate in taxRateUpdates)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(taxRate.Id))
|
||||
{
|
||||
await _paymentService.UpdateTaxRateAsync(taxRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _paymentService.CreateTaxRateAsync(taxRate);
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction("TaxRate");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequirePermission(Permission.Tools_ManageTaxRates)]
|
||||
public async Task<IActionResult> TaxRateAddEdit(TaxRateAddEditModel model)
|
||||
{
|
||||
var existingRateCheck = await _taxRateRepository.GetByLocationAsync(new TaxRate() { Country = model.Country, PostalCode = model.PostalCode });
|
||||
if (existingRateCheck.Any())
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.PostalCode), "A tax rate already exists for this Country/Postal Code combination.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var taxRate = new TaxRate()
|
||||
{
|
||||
Id = model.StripeTaxRateId,
|
||||
Country = model.Country,
|
||||
State = model.State,
|
||||
PostalCode = model.PostalCode,
|
||||
Rate = model.Rate
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(model.StripeTaxRateId))
|
||||
{
|
||||
await _paymentService.UpdateTaxRateAsync(taxRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _paymentService.CreateTaxRateAsync(taxRate);
|
||||
}
|
||||
|
||||
return RedirectToAction("TaxRate");
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_ManageTaxRates)]
|
||||
public async Task<IActionResult> TaxRateArchive(string stripeTaxRateId)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(stripeTaxRateId))
|
||||
{
|
||||
await _paymentService.ArchiveTaxRateAsync(new TaxRate() { Id = stripeTaxRateId });
|
||||
}
|
||||
|
||||
return RedirectToAction("TaxRate");
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Tools_ManageStripeSubscriptions)]
|
||||
public async Task<IActionResult> StripeSubscriptions(StripeSubscriptionListOptions options)
|
||||
{
|
||||
|
@ -107,7 +107,8 @@ public class UsersController : Controller
|
||||
var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user);
|
||||
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||
var verifiedDomain = await AccountDeprovisioningEnabled(user.Id);
|
||||
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain));
|
||||
var deviceVerificationRequired = await _userService.ActiveNewDeviceVerificationException(user.Id);
|
||||
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain, deviceVerificationRequired));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@ -162,6 +163,22 @@ public class UsersController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RequirePermission(Permission.User_NewDeviceException_Edit)]
|
||||
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||
public async Task<IActionResult> ToggleNewDeviceVerification(Guid id)
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(id);
|
||||
if (user == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
await _userService.ToggleNewDeviceVerificationException(user.Id);
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
// TODO: Feature flag to be removed in PM-14207
|
||||
private async Task<bool?> AccountDeprovisioningEnabled(Guid userId)
|
||||
{
|
||||
|
@ -17,13 +17,16 @@ public enum Permission
|
||||
User_Billing_View,
|
||||
User_Billing_Edit,
|
||||
User_Billing_LaunchGateway,
|
||||
User_NewDeviceException_Edit,
|
||||
|
||||
Org_List_View,
|
||||
Org_OrgInformation_View,
|
||||
Org_GeneralDetails_View,
|
||||
Org_Name_Edit,
|
||||
Org_CheckEnabledBox,
|
||||
Org_BusinessInformation_View,
|
||||
Org_InitiateTrial,
|
||||
Org_RequestDelete,
|
||||
Org_Delete,
|
||||
Org_BillingInformation_View,
|
||||
Org_BillingInformation_DownloadInvoice,
|
||||
@ -44,6 +47,7 @@ public enum Permission
|
||||
|
||||
Tools_ChargeBrainTreeCustomer,
|
||||
Tools_PromoteAdmin,
|
||||
Tools_PromoteProviderServiceUser,
|
||||
Tools_GenerateLicenseFile,
|
||||
Tools_ManageTaxRates,
|
||||
Tools_ManageStripeSubscriptions,
|
||||
|
13
src/Admin/Models/PromoteProviderServiceUserModel.cs
Normal file
13
src/Admin/Models/PromoteProviderServiceUserModel.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class PromoteProviderServiceUserModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Provider Service User Id")]
|
||||
public Guid? UserId { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Provider Id")]
|
||||
public Guid? ProviderId { get; set; }
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class TaxRateAddEditModel
|
||||
{
|
||||
public string StripeTaxRateId { get; set; }
|
||||
public string Country { get; set; }
|
||||
public string State { get; set; }
|
||||
public string PostalCode { get; set; }
|
||||
public decimal Rate { get; set; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Admin.Models;
|
||||
|
||||
public class TaxRatesModel : PagedModel<TaxRate>
|
||||
{
|
||||
public string Message { get; set; }
|
||||
}
|
@ -18,10 +18,13 @@ public class UserEditModel
|
||||
BillingInfo billingInfo,
|
||||
BillingHistoryInfo billingHistoryInfo,
|
||||
GlobalSettings globalSettings,
|
||||
bool? claimedAccount)
|
||||
bool? claimedAccount,
|
||||
bool? activeNewDeviceVerificationException)
|
||||
{
|
||||
User = UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers, claimedAccount);
|
||||
|
||||
ActiveNewDeviceVerificationException = activeNewDeviceVerificationException ?? false;
|
||||
|
||||
BillingInfo = billingInfo;
|
||||
BillingHistoryInfo = billingHistoryInfo;
|
||||
BraintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
@ -44,6 +47,8 @@ public class UserEditModel
|
||||
public string RandomLicenseKey => CoreHelpers.SecureRandomString(20);
|
||||
public string OneYearExpirationDate => DateTime.Now.AddYears(1).ToString("yyyy-MM-ddTHH:mm");
|
||||
public string BraintreeMerchantId { get; init; }
|
||||
public bool ActiveNewDeviceVerificationException { get; init; }
|
||||
|
||||
|
||||
[Display(Name = "Name")]
|
||||
public string Name { get; init; }
|
||||
|
@ -12,7 +12,6 @@ public static class RolePermissionMapping
|
||||
Permission.User_List_View,
|
||||
Permission.User_UserInformation_View,
|
||||
Permission.User_GeneralDetails_View,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.User_Delete,
|
||||
Permission.User_UpgradePremium,
|
||||
Permission.User_BillingInformation_View,
|
||||
@ -24,12 +23,16 @@ public static class RolePermissionMapping
|
||||
Permission.User_Billing_View,
|
||||
Permission.User_Billing_Edit,
|
||||
Permission.User_Billing_LaunchGateway,
|
||||
Permission.User_NewDeviceException_Edit,
|
||||
Permission.Org_Name_Edit,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.Org_List_View,
|
||||
Permission.Org_OrgInformation_View,
|
||||
Permission.Org_GeneralDetails_View,
|
||||
Permission.Org_BusinessInformation_View,
|
||||
Permission.Org_InitiateTrial,
|
||||
Permission.Org_Delete,
|
||||
Permission.Org_RequestDelete,
|
||||
Permission.Org_BillingInformation_View,
|
||||
Permission.Org_BillingInformation_DownloadInvoice,
|
||||
Permission.Org_Plan_View,
|
||||
@ -45,6 +48,7 @@ public static class RolePermissionMapping
|
||||
Permission.Provider_ResendEmailInvite,
|
||||
Permission.Tools_ChargeBrainTreeCustomer,
|
||||
Permission.Tools_PromoteAdmin,
|
||||
Permission.Tools_PromoteProviderServiceUser,
|
||||
Permission.Tools_GenerateLicenseFile,
|
||||
Permission.Tools_ManageTaxRates,
|
||||
Permission.Tools_ManageStripeSubscriptions
|
||||
@ -55,7 +59,6 @@ public static class RolePermissionMapping
|
||||
Permission.User_List_View,
|
||||
Permission.User_UserInformation_View,
|
||||
Permission.User_GeneralDetails_View,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.User_Delete,
|
||||
Permission.User_UpgradePremium,
|
||||
Permission.User_BillingInformation_View,
|
||||
@ -68,11 +71,15 @@ public static class RolePermissionMapping
|
||||
Permission.User_Billing_View,
|
||||
Permission.User_Billing_Edit,
|
||||
Permission.User_Billing_LaunchGateway,
|
||||
Permission.User_NewDeviceException_Edit,
|
||||
Permission.Org_Name_Edit,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.Org_List_View,
|
||||
Permission.Org_OrgInformation_View,
|
||||
Permission.Org_GeneralDetails_View,
|
||||
Permission.Org_BusinessInformation_View,
|
||||
Permission.Org_Delete,
|
||||
Permission.Org_RequestDelete,
|
||||
Permission.Org_BillingInformation_View,
|
||||
Permission.Org_BillingInformation_DownloadInvoice,
|
||||
Permission.Org_BillingInformation_CreateEditTransaction,
|
||||
@ -91,6 +98,7 @@ public static class RolePermissionMapping
|
||||
Permission.Provider_ResendEmailInvite,
|
||||
Permission.Tools_ChargeBrainTreeCustomer,
|
||||
Permission.Tools_PromoteAdmin,
|
||||
Permission.Tools_PromoteProviderServiceUser,
|
||||
Permission.Tools_GenerateLicenseFile,
|
||||
Permission.Tools_ManageTaxRates,
|
||||
Permission.Tools_ManageStripeSubscriptions,
|
||||
@ -102,7 +110,6 @@ public static class RolePermissionMapping
|
||||
Permission.User_List_View,
|
||||
Permission.User_UserInformation_View,
|
||||
Permission.User_GeneralDetails_View,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.User_UpgradePremium,
|
||||
Permission.User_BillingInformation_View,
|
||||
Permission.User_BillingInformation_DownloadInvoice,
|
||||
@ -110,6 +117,9 @@ public static class RolePermissionMapping
|
||||
Permission.User_Licensing_View,
|
||||
Permission.User_Billing_View,
|
||||
Permission.User_Billing_LaunchGateway,
|
||||
Permission.User_NewDeviceException_Edit,
|
||||
Permission.Org_Name_Edit,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.Org_List_View,
|
||||
Permission.Org_OrgInformation_View,
|
||||
Permission.Org_GeneralDetails_View,
|
||||
@ -121,6 +131,7 @@ public static class RolePermissionMapping
|
||||
Permission.Org_Licensing_View,
|
||||
Permission.Org_Billing_View,
|
||||
Permission.Org_Billing_LaunchGateway,
|
||||
Permission.Org_RequestDelete,
|
||||
Permission.Provider_List_View,
|
||||
Permission.Provider_View
|
||||
}
|
||||
@ -130,7 +141,6 @@ public static class RolePermissionMapping
|
||||
Permission.User_List_View,
|
||||
Permission.User_UserInformation_View,
|
||||
Permission.User_GeneralDetails_View,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.User_UpgradePremium,
|
||||
Permission.User_BillingInformation_View,
|
||||
Permission.User_BillingInformation_DownloadInvoice,
|
||||
@ -141,6 +151,8 @@ public static class RolePermissionMapping
|
||||
Permission.User_Billing_View,
|
||||
Permission.User_Billing_Edit,
|
||||
Permission.User_Billing_LaunchGateway,
|
||||
Permission.Org_Name_Edit,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.Org_List_View,
|
||||
Permission.Org_OrgInformation_View,
|
||||
Permission.Org_GeneralDetails_View,
|
||||
@ -154,6 +166,7 @@ public static class RolePermissionMapping
|
||||
Permission.Org_Billing_View,
|
||||
Permission.Org_Billing_Edit,
|
||||
Permission.Org_Billing_LaunchGateway,
|
||||
Permission.Org_RequestDelete,
|
||||
Permission.Provider_Edit,
|
||||
Permission.Provider_View,
|
||||
Permission.Provider_List_View,
|
||||
@ -171,12 +184,13 @@ public static class RolePermissionMapping
|
||||
Permission.User_List_View,
|
||||
Permission.User_UserInformation_View,
|
||||
Permission.User_GeneralDetails_View,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.User_BillingInformation_View,
|
||||
Permission.User_BillingInformation_DownloadInvoice,
|
||||
Permission.User_Premium_View,
|
||||
Permission.User_Licensing_View,
|
||||
Permission.User_Licensing_Edit,
|
||||
Permission.Org_Name_Edit,
|
||||
Permission.Org_CheckEnabledBox,
|
||||
Permission.Org_List_View,
|
||||
Permission.Org_OrgInformation_View,
|
||||
Permission.Org_GeneralDetails_View,
|
||||
|
@ -11,14 +11,14 @@
|
||||
var canChargeBraintree = AccessControlService.UserHasPermission(Permission.Tools_ChargeBrainTreeCustomer);
|
||||
var canCreateTransaction = AccessControlService.UserHasPermission(Permission.Tools_CreateEditTransaction);
|
||||
var canPromoteAdmin = AccessControlService.UserHasPermission(Permission.Tools_PromoteAdmin);
|
||||
var canPromoteProviderServiceUser = AccessControlService.UserHasPermission(Permission.Tools_PromoteProviderServiceUser);
|
||||
var canGenerateLicense = AccessControlService.UserHasPermission(Permission.Tools_GenerateLicenseFile);
|
||||
var canManageTaxRates = AccessControlService.UserHasPermission(Permission.Tools_ManageTaxRates);
|
||||
var canManageStripeSubscriptions = AccessControlService.UserHasPermission(Permission.Tools_ManageStripeSubscriptions);
|
||||
var canProcessStripeEvents = AccessControlService.UserHasPermission(Permission.Tools_ProcessStripeEvents);
|
||||
var canMigrateProviders = AccessControlService.UserHasPermission(Permission.Tools_MigrateProviders);
|
||||
|
||||
var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin ||
|
||||
canGenerateLicense || canManageTaxRates || canManageStripeSubscriptions;
|
||||
var canViewTools = canChargeBraintree || canCreateTransaction || canPromoteAdmin || canPromoteProviderServiceUser ||
|
||||
canGenerateLicense || canManageStripeSubscriptions;
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -88,7 +88,13 @@
|
||||
@if (canPromoteAdmin)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="PromoteAdmin">
|
||||
Promote Admin
|
||||
Promote Organization Admin
|
||||
</a>
|
||||
}
|
||||
@if (canPromoteProviderServiceUser)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="PromoteProviderServiceUser">
|
||||
Promote Provider Service User
|
||||
</a>
|
||||
}
|
||||
@if (canGenerateLicense)
|
||||
@ -97,12 +103,6 @@
|
||||
Generate License
|
||||
</a>
|
||||
}
|
||||
@if (canManageTaxRates)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="TaxRate">
|
||||
Manage Tax Rates
|
||||
</a>
|
||||
}
|
||||
@if (canManageStripeSubscriptions)
|
||||
{
|
||||
<a class="dropdown-item" asp-controller="Tools" asp-action="StripeSubscriptions">
|
||||
|
25
src/Admin/Views/Tools/PromoteProviderServiceUser.cshtml
Normal file
25
src/Admin/Views/Tools/PromoteProviderServiceUser.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
@model PromoteProviderServiceUserModel
|
||||
@{
|
||||
ViewData["Title"] = "Promote Provider Service User";
|
||||
}
|
||||
|
||||
<h1>Promote Provider Service User</h1>
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="UserId" class="form-label"></label>
|
||||
<input type="text" class="form-control" asp-for="UserId">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="ProviderId" class="form-label"></label>
|
||||
<input type="text" class="form-control" asp-for="ProviderId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Promote Service User</button>
|
||||
</form>
|
@ -1,127 +0,0 @@
|
||||
@model TaxRatesModel
|
||||
@{
|
||||
ViewData["Title"] = "Tax Rates";
|
||||
}
|
||||
|
||||
<h1>Manage Tax Rates</h1>
|
||||
|
||||
<h2>Bulk Upload Tax Rates</h2>
|
||||
<section>
|
||||
<p>
|
||||
Upload a CSV file containing multiple tax rates in bulk in order to update existing rates by country
|
||||
and postal code OR to create new rates where a currently active rate is not found already.
|
||||
</p>
|
||||
<p>CSV Upload Format</p>
|
||||
<ul>
|
||||
<li><b>Postal Code</b> (required) - The postal code for the tax rate.</li>
|
||||
<li><b>Rate</b> (required) - The effective tax rate for this postal code.</li>
|
||||
<li><b>State</b> (<i>optional</i>) - The ISO-2 character code for the state. Optional but recommended.</li>
|
||||
<li><b>Country</b> (<i>optional</i>) - The ISO-2 character country code, defaults to "US" if not provided.</li>
|
||||
</ul>
|
||||
<p>Example (white-space is ignored):</p>
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<pre class="mb-0">87654,8.25,FL,US
|
||||
22334,8.5,CA
|
||||
11223,7</pre>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" enctype="multipart/form-data" asp-action="TaxRateUpload">
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control" name="file" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="submit" value="Upload" class="btn btn-primary" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<hr class="my-4">
|
||||
<h2>View & Manage Tax Rates</h2>
|
||||
<a class="btn btn-primary mb-3" asp-controller="Tools" asp-action="TaxRateAddEdit">Add a Rate</a>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 190px;">Id</th>
|
||||
<th style="width: 80px;">Country</th>
|
||||
<th style="width: 80px;">State</th>
|
||||
<th style="width: 150px;">Postal Code</th>
|
||||
<th style="width: 160px;">Tax Rate</th>
|
||||
<th style="width: 80px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(!Model.Items.Any())
|
||||
{
|
||||
<tr>
|
||||
<td colspan="6">No results to list.</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach(var rate in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
@{
|
||||
var taxRateToEdit = new Dictionary<string, string>
|
||||
{
|
||||
{ "id", rate.Id },
|
||||
{ "stripeTaxRateId", rate.Id }
|
||||
};
|
||||
}
|
||||
<a asp-controller="Tools" asp-action="TaxRateAddEdit" asp-all-route-data="taxRateToEdit">@rate.Id</a>
|
||||
</td>
|
||||
<td>
|
||||
@rate.Country
|
||||
</td>
|
||||
<td>
|
||||
@rate.State
|
||||
</td>
|
||||
<td>
|
||||
@rate.PostalCode
|
||||
</td>
|
||||
<td>
|
||||
@rate.Rate%
|
||||
</td>
|
||||
<td>
|
||||
<a class="delete-button" data-id="@rate.Id" asp-controller="Tools" asp-action="TaxRateArchive" asp-route-stripeTaxRateId="@rate.Id">
|
||||
<i class="fa fa-trash fa-lg fa-fw"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav aria-label="Tax rates pagination">
|
||||
<ul class="pagination">
|
||||
@if(Model.PreviousPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-controller="Tools" asp-action="TaxRate" asp-route-page="@Model.PreviousPage.Value" asp-route-count="@Model.Count">Previous</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
}
|
||||
@if(Model.NextPage.HasValue)
|
||||
{
|
||||
<li class="page-item">
|
||||
<a class="page-link" asp-controller="Tools" asp-action="TaxRate" asp-route-page="@Model.NextPage.Value" asp-route-count="@Model.Count">Next</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Next</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
@ -1,356 +0,0 @@
|
||||
@model TaxRateAddEditModel
|
||||
@{
|
||||
ViewData["Title"] = "Add/Edit Tax Rate";
|
||||
}
|
||||
|
||||
|
||||
<h1>@(string.IsNullOrWhiteSpace(Model.StripeTaxRateId) ? "Create" : "Edit") Tax Rate</h1>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.StripeTaxRateId))
|
||||
{
|
||||
<p>Note: Updating a Tax Rate archives the currently selected rate and creates a new rate with a new ID. The previous data still exists in a disabled state.</p>
|
||||
}
|
||||
|
||||
<form method="post">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<input type="hidden" asp-for="StripeTaxRateId">
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="Country"></label>
|
||||
<select asp-for="Country" class="form-control" required>
|
||||
<option value="">-- Select --</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="-" disabled></option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland Islands</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia, Plurinational State of</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo</option>
|
||||
<option value="CD">Congo, the Democratic Republic of the</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands (Malvinas)</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Territories</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard Island and McDonald Islands</option>
|
||||
<option value="VA">Holy See (Vatican City State)</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran, Islamic Republic of</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, Democratic People's Republic of</option>
|
||||
<option value="KR">Korea, Republic of</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Lao People's Democratic Republic</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macao</option>
|
||||
<option value="MK">Macedonia, the former Yugoslav Republic of</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia, Federated States of</option>
|
||||
<option value="MD">Moldova, Republic of</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestinian Territory, Occupied</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Réunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena, Ascension and Tristan da Cunha</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and the South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syrian Arab Republic</option>
|
||||
<option value="TW">Taiwan</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania, United Republic of</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VE">Venezuela, Bolivarian Republic of</option>
|
||||
<option value="VN">Viet Nam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="State"></label>
|
||||
<select asp-for="State" class="form-control">
|
||||
<option value="">-- Select --</option>
|
||||
<option value="AL">Alabama</option>
|
||||
<option value="AK">Alaska</option>
|
||||
<option value="AZ">Arizona</option>
|
||||
<option value="AR">Arkansas</option>
|
||||
<option value="CA">California</option>
|
||||
<option value="CO">Colorado</option>
|
||||
<option value="CT">Connecticut</option>
|
||||
<option value="DE">Delaware</option>
|
||||
<option value="DC">District Of Columbia</option>
|
||||
<option value="FL">Florida</option>
|
||||
<option value="GA">Georgia</option>
|
||||
<option value="HI">Hawaii</option>
|
||||
<option value="ID">Idaho</option>
|
||||
<option value="IL">Illinois</option>
|
||||
<option value="IN">Indiana</option>
|
||||
<option value="IA">Iowa</option>
|
||||
<option value="KS">Kansas</option>
|
||||
<option value="KY">Kentucky</option>
|
||||
<option value="LA">Louisiana</option>
|
||||
<option value="ME">Maine</option>
|
||||
<option value="MD">Maryland</option>
|
||||
<option value="MA">Massachusetts</option>
|
||||
<option value="MI">Michigan</option>
|
||||
<option value="MN">Minnesota</option>
|
||||
<option value="MS">Mississippi</option>
|
||||
<option value="MO">Missouri</option>
|
||||
<option value="MT">Montana</option>
|
||||
<option value="NE">Nebraska</option>
|
||||
<option value="NV">Nevada</option>
|
||||
<option value="NH">New Hampshire</option>
|
||||
<option value="NJ">New Jersey</option>
|
||||
<option value="NM">New Mexico</option>
|
||||
<option value="NY">New York</option>
|
||||
<option value="NC">North Carolina</option>
|
||||
<option value="ND">North Dakota</option>
|
||||
<option value="OH">Ohio</option>
|
||||
<option value="OK">Oklahoma</option>
|
||||
<option value="OR">Oregon</option>
|
||||
<option value="PA">Pennsylvania</option>
|
||||
<option value="RI">Rhode Island</option>
|
||||
<option value="SC">South Carolina</option>
|
||||
<option value="SD">South Dakota</option>
|
||||
<option value="TN">Tennessee</option>
|
||||
<option value="TX">Texas</option>
|
||||
<option value="UT">Utah</option>
|
||||
<option value="VT">Vermont</option>
|
||||
<option value="VA">Virginia</option>
|
||||
<option value="WA">Washington</option>
|
||||
<option value="WV">West Virginia</option>
|
||||
<option value="WI">Wisconsin</option>
|
||||
<option value="WY">Wyoming</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="PostalCode">Postal Code</label>
|
||||
<input type="text" class="form-control" asp-for="PostalCode" required maxlength="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="form-group">
|
||||
<label asp-for="Rate">Tax Rate</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="Rate" pattern="^\d{0,3}.\d{0,3}$" required>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mb-2">@(string.IsNullOrWhiteSpace(Model.StripeTaxRateId) ? "Create" : "Save")</button>
|
||||
</form>
|
@ -1,11 +1,16 @@
|
||||
@using Bit.Admin.Enums;
|
||||
@inject Bit.Admin.Services.IAccessControlService AccessControlService
|
||||
@inject Bit.Core.Services.IFeatureService FeatureService
|
||||
@inject Bit.Core.Settings.GlobalSettings GlobalSettings
|
||||
@inject IWebHostEnvironment HostingEnvironment
|
||||
@model UserEditModel
|
||||
@{
|
||||
ViewData["Title"] = "User: " + Model.User.Email;
|
||||
|
||||
var canViewUserInformation = AccessControlService.UserHasPermission(Permission.User_UserInformation_View);
|
||||
var canViewNewDeviceException = AccessControlService.UserHasPermission(Permission.User_NewDeviceException_Edit) &&
|
||||
GlobalSettings.EnableNewDeviceVerification &&
|
||||
FeatureService.IsEnabled(Bit.Core.FeatureFlagKeys.NewDeviceVerification);
|
||||
var canViewBillingInformation = AccessControlService.UserHasPermission(Permission.User_BillingInformation_View);
|
||||
var canViewGeneral = AccessControlService.UserHasPermission(Permission.User_GeneralDetails_View);
|
||||
var canViewPremium = AccessControlService.UserHasPermission(Permission.User_Premium_View);
|
||||
@ -47,13 +52,13 @@
|
||||
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
const url = '@(HostingEnvironment.IsDevelopment()
|
||||
? "https://dashboard.stripe.com/test"
|
||||
: "https://dashboard.stripe.com")';
|
||||
? "https://dashboard.stripe.com/test"
|
||||
: "https://dashboard.stripe.com")';
|
||||
window.open(`${url}/customers/${customerId.value}/`, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
const url = '@(HostingEnvironment.IsDevelopment()
|
||||
? $"https://www.sandbox.braintreegateway.com/merchants/{Model.BraintreeMerchantId}"
|
||||
: $"https://www.braintreegateway.com/merchants/{Model.BraintreeMerchantId}")';
|
||||
? $"https://www.sandbox.braintreegateway.com/merchants/{Model.BraintreeMerchantId}"
|
||||
: $"https://www.braintreegateway.com/merchants/{Model.BraintreeMerchantId}")';
|
||||
window.open(`${url}/${customerId.value}`, '_blank');
|
||||
}
|
||||
});
|
||||
@ -67,13 +72,13 @@
|
||||
|
||||
if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Stripe)') {
|
||||
const url = '@(HostingEnvironment.IsDevelopment() || HostingEnvironment.IsEnvironment("QA")
|
||||
? "https://dashboard.stripe.com/test"
|
||||
: "https://dashboard.stripe.com")'
|
||||
? "https://dashboard.stripe.com/test"
|
||||
: "https://dashboard.stripe.com")'
|
||||
window.open(`${url}/subscriptions/${subId.value}`, '_blank');
|
||||
} else if (gateway.value === '@((byte)Bit.Core.Enums.GatewayType.Braintree)') {
|
||||
const url = '@(HostingEnvironment.IsDevelopment() || HostingEnvironment.IsEnvironment("QA")
|
||||
? $"https://www.sandbox.braintreegateway.com/merchants/{Model.BraintreeMerchantId}"
|
||||
: $"https://www.braintreegateway.com/merchants/{Model.BraintreeMerchantId}")';
|
||||
? $"https://www.sandbox.braintreegateway.com/merchants/{Model.BraintreeMerchantId}"
|
||||
: $"https://www.braintreegateway.com/merchants/{Model.BraintreeMerchantId}")';
|
||||
window.open(`${url}/subscriptions/${subId.value}`, '_blank');
|
||||
}
|
||||
});
|
||||
@ -88,11 +93,40 @@
|
||||
<h2>User Information</h2>
|
||||
@await Html.PartialAsync("_ViewInformation", Model.User)
|
||||
}
|
||||
@if (canViewNewDeviceException)
|
||||
{
|
||||
<h2>New Device Verification </h2>
|
||||
<dl class="row">
|
||||
<dt class="col d-flex">
|
||||
<form asp-action="ToggleNewDeviceVerification" asp-route-id="@Model.User.Id" method="post">
|
||||
@if (Model.ActiveNewDeviceVerificationException)
|
||||
{
|
||||
<p>Status: Bypassed</p>
|
||||
<button type="submit" class="btn btn-success" id="new-device-verification-exception">Require New
|
||||
Device Verification</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Status: Required</p>
|
||||
<button type="submit" class="btn btn-outline-danger" id="new-device-verification-exception">Bypass New
|
||||
Device Verification</button>
|
||||
}
|
||||
</form>
|
||||
|
||||
</dt>
|
||||
</dl>
|
||||
}
|
||||
@if (canViewBillingInformation)
|
||||
{
|
||||
<h2>Billing Information</h2>
|
||||
@await Html.PartialAsync("_BillingInformation",
|
||||
new BillingInformationModel { BillingInfo = Model.BillingInfo, BillingHistoryInfo = Model.BillingHistoryInfo, UserId = Model.User.Id, Entity = "User" })
|
||||
new BillingInformationModel
|
||||
{
|
||||
BillingInfo = Model.BillingInfo,
|
||||
BillingHistoryInfo = Model.BillingHistoryInfo,
|
||||
UserId = Model.User.Id,
|
||||
Entity = "User"
|
||||
})
|
||||
}
|
||||
@if (canViewGeneral)
|
||||
{
|
||||
@ -109,7 +143,7 @@
|
||||
<label class="form-check-label" asp-for="EmailVerified"></label>
|
||||
</div>
|
||||
}
|
||||
<form method="post" id="edit-form">
|
||||
<form method="post" id="edit-form">
|
||||
@if (canViewPremium)
|
||||
{
|
||||
<h2>Premium</h2>
|
||||
@ -139,54 +173,56 @@
|
||||
<div class="col-sm">
|
||||
<div class="mb-3">
|
||||
<label asp-for="PremiumExpirationDate" class="form-label"></label>
|
||||
<input type="datetime-local" class="form-control" asp-for="PremiumExpirationDate" readonly='@(!canEditLicensing)'>
|
||||
<input type="datetime-local" class="form-control" asp-for="PremiumExpirationDate"
|
||||
readonly='@(!canEditLicensing)'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (canViewBilling)
|
||||
{
|
||||
<h2>Billing</h2>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="Gateway" class="form-label"></label>
|
||||
<select class="form-select" asp-for="Gateway" disabled='@(canEditBilling ? null : "disabled")'
|
||||
@if (canViewBilling)
|
||||
{
|
||||
<h2>Billing</h2>
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="Gateway" class="form-label"></label>
|
||||
<select class="form-select" asp-for="Gateway" disabled='@(canEditBilling ? null : "disabled")'
|
||||
asp-items="Html.GetEnumSelectList<Bit.Core.Enums.GatewayType>()">
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
<option value="">--</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="GatewayCustomerId" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewayCustomerId" readonly='@(!canEditBilling)'>
|
||||
@if (canLaunchGateway)
|
||||
{
|
||||
<button class="btn btn-secondary" type="button" id="gateway-customer-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
}
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="GatewayCustomerId" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewayCustomerId" readonly='@(!canEditBilling)'>
|
||||
@if (canLaunchGateway)
|
||||
{
|
||||
<button class="btn btn-secondary" type="button" id="gateway-customer-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="GatewaySubscriptionId" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewaySubscriptionId"
|
||||
readonly='@(!canEditBilling)'>
|
||||
@if (canLaunchGateway)
|
||||
{
|
||||
<button class="btn btn-secondary" type="button" id="gateway-subscription-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md">
|
||||
<div class="mb-3">
|
||||
<label asp-for="GatewaySubscriptionId" class="form-label"></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" asp-for="GatewaySubscriptionId" readonly='@(!canEditBilling)'>
|
||||
@if (canLaunchGateway)
|
||||
{
|
||||
<button class="btn btn-secondary" type="button" id="gateway-subscription-link">
|
||||
<i class="fa fa-external-link"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</form>
|
||||
<div class="d-flex mt-4">
|
||||
<button type="submit" class="btn btn-primary" form="edit-form">Save</button>
|
||||
|
574
src/Admin/package-lock.json
generated
574
src/Admin/package-lock.json
generated
@ -17,10 +17,10 @@
|
||||
"devDependencies": {
|
||||
"css-loader": "7.1.2",
|
||||
"expose-loader": "5.0.0",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"sass": "1.79.5",
|
||||
"sass-loader": "16.0.2",
|
||||
"webpack": "5.95.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"webpack": "5.97.1",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
},
|
||||
@ -35,9 +35,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -99,10 +99,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz",
|
||||
"integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz",
|
||||
"integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
@ -118,24 +119,25 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.4.1",
|
||||
"@parcel/watcher-darwin-x64": "2.4.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.4.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.4.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.4.1",
|
||||
"@parcel/watcher-win32-arm64": "2.4.1",
|
||||
"@parcel/watcher-win32-ia32": "2.4.1",
|
||||
"@parcel/watcher-win32-x64": "2.4.1"
|
||||
"@parcel/watcher-android-arm64": "2.5.0",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.0",
|
||||
"@parcel/watcher-darwin-x64": "2.5.0",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.0",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.0",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.0",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.0",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.0",
|
||||
"@parcel/watcher-win32-arm64": "2.5.0",
|
||||
"@parcel/watcher-win32-ia32": "2.5.0",
|
||||
"@parcel/watcher-win32-x64": "2.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -154,9 +156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -175,9 +177,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -196,9 +198,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -217,9 +219,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -238,9 +261,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -259,9 +282,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -280,9 +303,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz",
|
||||
"integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz",
|
||||
"integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -301,9 +324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz",
|
||||
"integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz",
|
||||
"integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -322,9 +345,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz",
|
||||
"integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz",
|
||||
"integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -343,9 +366,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz",
|
||||
"integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz",
|
||||
"integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -364,9 +387,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz",
|
||||
"integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz",
|
||||
"integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -395,6 +418,28 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@ -410,83 +455,83 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
|
||||
"integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
|
||||
"version": "22.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
|
||||
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
||||
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
|
||||
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
|
||||
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
|
||||
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
|
||||
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
|
||||
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
|
||||
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
|
||||
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -494,9 +539,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
|
||||
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
|
||||
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -504,79 +549,79 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
|
||||
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
|
||||
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-section": "1.12.1",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
||||
"@webassemblyjs/wasm-opt": "1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "1.12.1",
|
||||
"@webassemblyjs/wast-printer": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-section": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-opt": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1",
|
||||
"@webassemblyjs/wast-printer": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
|
||||
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
|
||||
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/ieee754": "1.11.6",
|
||||
"@webassemblyjs/leb128": "1.11.6",
|
||||
"@webassemblyjs/utf8": "1.11.6"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
|
||||
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
|
||||
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "1.12.1"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
|
||||
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
|
||||
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
"@webassemblyjs/ieee754": "1.11.6",
|
||||
"@webassemblyjs/leb128": "1.11.6",
|
||||
"@webassemblyjs/utf8": "1.11.6"
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
|
||||
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
|
||||
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.12.1",
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
@ -642,9 +687,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
|
||||
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@ -654,16 +699,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-attributes": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
|
||||
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
@ -726,6 +761,7 @@
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
@ -744,9 +780,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz",
|
||||
"integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==",
|
||||
"version": "4.24.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz",
|
||||
"integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -764,10 +800,10 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001663",
|
||||
"electron-to-chromium": "^1.5.28",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.0"
|
||||
"caniuse-lite": "^1.0.30001688",
|
||||
"electron-to-chromium": "^1.5.73",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@ -784,9 +820,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001668",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001668.tgz",
|
||||
"integrity": "sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==",
|
||||
"version": "1.0.30001690",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz",
|
||||
"integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -805,9 +841,9 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
|
||||
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -860,9 +896,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -937,16 +973,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.36",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.36.tgz",
|
||||
"integrity": "sha512-HYTX8tKge/VNp6FGO+f/uVDmUkq+cEfcxYhKf15Akc4M5yxt5YmorwlAitKWjWhWQnKcDRBAQKXkhqqXMqcrjw==",
|
||||
"version": "1.5.75",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz",
|
||||
"integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||
"integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1076,11 +1112,11 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz",
|
||||
"integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
|
||||
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastest-levenshtein": {
|
||||
"version": "1.0.16",
|
||||
@ -1236,9 +1272,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1427,9 +1463,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz",
|
||||
"integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz",
|
||||
"integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1448,9 +1484,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1481,9 +1517,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -1554,9 +1590,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@ -1587,9 +1623,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1608,7 +1644,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.0",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -1629,14 +1665,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-local-by-default": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz",
|
||||
"integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==",
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
|
||||
"integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.0.2",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -1647,13 +1683,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-modules-scope": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz",
|
||||
"integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
|
||||
"integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.4"
|
||||
"postcss-selector-parser": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >= 14"
|
||||
@ -1679,9 +1715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1757,19 +1793,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@ -1838,9 +1877,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-loader": {
|
||||
"version": "16.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.2.tgz",
|
||||
"integrity": "sha512-Ll6iXZ1EYwYT19SqW4mSBb76vSSi8JgzElmzIerhEGgzB5hRjDQIWsPmuk1UrAXkR16KJHqVY0eH+5/uw9Tmfw==",
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1879,9 +1918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
|
||||
"integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -1891,7 +1930,7 @@
|
||||
"ajv-keywords": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -2028,9 +2067,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.34.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz",
|
||||
"integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==",
|
||||
"version": "5.37.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@ -2047,17 +2086,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.10",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
||||
"version": "5.3.11",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
|
||||
"integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.20",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.1",
|
||||
"terser": "^5.26.0"
|
||||
"schema-utils": "^4.3.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"terser": "^5.31.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
@ -2081,59 +2120,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@ -2156,9 +2142,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -2225,19 +2211,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.95.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
|
||||
"integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
|
||||
"version": "5.97.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
|
||||
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.5",
|
||||
"@webassemblyjs/ast": "^1.12.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.12.1",
|
||||
"acorn": "^8.7.1",
|
||||
"acorn-import-attributes": "^1.9.5",
|
||||
"browserslist": "^4.21.10",
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.14.0",
|
||||
"browserslist": "^4.24.0",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
|
@ -16,10 +16,10 @@
|
||||
"devDependencies": {
|
||||
"css-loader": "7.1.2",
|
||||
"expose-loader": "5.0.0",
|
||||
"mini-css-extract-plugin": "2.9.1",
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"sass": "1.79.5",
|
||||
"sass-loader": "16.0.2",
|
||||
"webpack": "5.95.0",
|
||||
"sass-loader": "16.0.4",
|
||||
"webpack": "5.97.1",
|
||||
"webpack-cli": "5.1.4"
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
using Bit.Api.AdminConsole.Models.Response;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.Vault.AuthorizationHandlers.Collections;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Authorization;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||
@ -90,7 +89,7 @@ public class GroupsController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroups(Guid orgId)
|
||||
public async Task<ListResponseModel<GroupResponseModel>> GetOrganizationGroups(Guid orgId)
|
||||
{
|
||||
var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll);
|
||||
if (!authResult.Succeeded)
|
||||
@ -98,24 +97,15 @@ public class GroupsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails))
|
||||
{
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId);
|
||||
var responses = groups.Select(g => new GroupDetailsResponseModel(g, []));
|
||||
return new ListResponseModel<GroupDetailsResponseModel>(responses);
|
||||
}
|
||||
|
||||
var groupDetails = await _groupRepository.GetManyWithCollectionsByOrganizationIdAsync(orgId);
|
||||
var detailResponses = groupDetails.Select(g => new GroupDetailsResponseModel(g.Item1, g.Item2));
|
||||
return new ListResponseModel<GroupDetailsResponseModel>(detailResponses);
|
||||
var groups = await _groupRepository.GetManyByOrganizationIdAsync(orgId);
|
||||
var responses = groups.Select(g => new GroupResponseModel(g));
|
||||
return new ListResponseModel<GroupResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpGet("details")]
|
||||
public async Task<ListResponseModel<GroupDetailsResponseModel>> GetOrganizationGroupDetails(Guid orgId)
|
||||
{
|
||||
var authResult = _featureService.IsEnabled(FeatureFlagKeys.SecureOrgGroupDetails)
|
||||
? await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails)
|
||||
: await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAll);
|
||||
var authResult = await _authorizationService.AuthorizeAsync(User, new OrganizationScope(orgId), GroupOperations.ReadAllDetails);
|
||||
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
|
@ -311,10 +311,8 @@ public class OrganizationUsersController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
|
||||
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
|
||||
masterPasswordPolicy.Enabled &&
|
||||
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
|
||||
var useMasterPasswordPolicy = await ShouldHandleResetPasswordAsync(orgId);
|
||||
|
||||
if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey))
|
||||
{
|
||||
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
|
||||
@ -328,6 +326,23 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldHandleResetPasswordAsync(Guid orgId)
|
||||
{
|
||||
var organizationAbility = await _applicationCacheService.GetOrganizationAbilityAsync(orgId);
|
||||
|
||||
if (organizationAbility is not { UsePolicies: true })
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
|
||||
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
|
||||
masterPasswordPolicy.Enabled &&
|
||||
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
|
||||
|
||||
return useMasterPasswordPolicy;
|
||||
}
|
||||
|
||||
[HttpPost("{id}/confirm")]
|
||||
public async Task Confirm(string orgId, string id, [FromBody] OrganizationUserConfirmRequestModel model)
|
||||
{
|
||||
|
@ -13,6 +13,8 @@ using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -52,11 +54,12 @@ public class OrganizationsController : Controller
|
||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -73,11 +76,12 @@ public class OrganizationsController : Controller
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
IFeatureService featureService,
|
||||
GlobalSettings globalSettings,
|
||||
IPushNotificationService pushNotificationService,
|
||||
IProviderRepository providerRepository,
|
||||
IProviderBillingService providerBillingService,
|
||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||
IOrganizationDeleteCommand organizationDeleteCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@ -93,11 +97,12 @@ public class OrganizationsController : Controller
|
||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||
_featureService = featureService;
|
||||
_globalSettings = globalSettings;
|
||||
_pushNotificationService = pushNotificationService;
|
||||
_providerRepository = providerRepository;
|
||||
_providerBillingService = providerBillingService;
|
||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
||||
_organizationDeleteCommand = organizationDeleteCommand;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
@ -175,8 +180,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var organizationSignup = model.ToOrganizationSignup(user);
|
||||
var result = await _organizationService.SignUpAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Item1);
|
||||
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Organization);
|
||||
}
|
||||
|
||||
[HttpPost("create-without-payment")]
|
||||
@ -190,8 +195,8 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
|
||||
var organizationSignup = model.ToOrganizationSignup(user);
|
||||
var result = await _organizationService.SignUpAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Item1);
|
||||
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||
return new OrganizationResponseModel(result.Organization);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
@ -258,7 +263,7 @@ public class OrganizationsController : Controller
|
||||
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.UserLeaveAsync(id, user.Id);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
@ -302,7 +307,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await _organizationService.DeleteAsync(organization);
|
||||
await _organizationDeleteCommand.DeleteAsync(organization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/delete-recover-token")]
|
||||
@ -332,7 +337,7 @@ public class OrganizationsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await _organizationService.DeleteAsync(organization);
|
||||
await _organizationDeleteCommand.DeleteAsync(organization);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/api-key")]
|
||||
@ -525,14 +530,6 @@ public class OrganizationsController : Controller
|
||||
[HttpPut("{id}/collection-management")]
|
||||
public async Task<OrganizationResponseModel> PutCollectionManagement(Guid id, [FromBody] OrganizationCollectionManagementUpdateRequestModel model)
|
||||
{
|
||||
if (
|
||||
_globalSettings.SelfHosted &&
|
||||
!_featureService.IsEnabled(FeatureFlagKeys.LimitCollectionCreationDeletionSplit)
|
||||
)
|
||||
{
|
||||
throw new BadRequestException("Only allowed when not self hosted.");
|
||||
}
|
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
@ -547,4 +544,17 @@ public class OrganizationsController : Controller
|
||||
await _organizationService.UpdateAsync(model.ToOrganization(organization, _featureService), eventType: EventType.Organization_CollectionManagement_Updated);
|
||||
return new OrganizationResponseModel(organization);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/plan-type")]
|
||||
public async Task<PlanType> GetPlanType(string id)
|
||||
{
|
||||
var orgIdGuid = new Guid(id);
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
|
||||
if (organization == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return organization.PlanType;
|
||||
}
|
||||
}
|
||||
|
@ -27,19 +27,20 @@ namespace Bit.Api.AdminConsole.Controllers;
|
||||
[Authorize("Application")]
|
||||
public class PoliciesController : Controller
|
||||
{
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IDataProtector _organizationServiceDataProtector;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IOrganizationHasVerifiedDomainsQuery _organizationHasVerifiedDomainsQuery;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IDataProtector _organizationServiceDataProtector;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
private readonly ISavePolicyCommand _savePolicyCommand;
|
||||
|
||||
public PoliciesController(
|
||||
IPolicyRepository policyRepository,
|
||||
public PoliciesController(IPolicyRepository policyRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IUserService userService,
|
||||
ICurrentContext currentContext,
|
||||
@ -48,6 +49,7 @@ public class PoliciesController : Controller
|
||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||
IFeatureService featureService,
|
||||
IOrganizationHasVerifiedDomainsQuery organizationHasVerifiedDomainsQuery,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ISavePolicyCommand savePolicyCommand)
|
||||
{
|
||||
_policyRepository = policyRepository;
|
||||
@ -57,7 +59,7 @@ public class PoliciesController : Controller
|
||||
_globalSettings = globalSettings;
|
||||
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
|
||||
"OrganizationServiceDataProtector");
|
||||
|
||||
_organizationRepository = organizationRepository;
|
||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||
_featureService = featureService;
|
||||
_organizationHasVerifiedDomainsQuery = organizationHasVerifiedDomainsQuery;
|
||||
@ -104,6 +106,13 @@ public class PoliciesController : Controller
|
||||
public async Task<ListResponseModel<PolicyResponseModel>> GetByToken(Guid orgId, [FromQuery] string email,
|
||||
[FromQuery] string token, [FromQuery] Guid organizationUserId)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
||||
|
||||
if (organization is not { UsePolicies: true })
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// TODO: PM-4142 - remove old token validation logic once 3 releases of backwards compatibility are complete
|
||||
var newTokenValid = OrgUserInviteTokenable.ValidateOrgUserInviteStringToken(
|
||||
_orgUserInviteTokenDataFactory, token, organizationUserId, email);
|
||||
@ -158,6 +167,13 @@ public class PoliciesController : Controller
|
||||
[HttpGet("master-password")]
|
||||
public async Task<PolicyResponseModel> GetMasterPasswordPolicy(Guid orgId)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgId);
|
||||
|
||||
if (organization is not { UsePolicies: true })
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
|
||||
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(orgId, userId);
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Api.Billing.Models.Requests;
|
||||
using Bit.Api.Billing.Controllers;
|
||||
using Bit.Api.Billing.Models.Requests;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
@ -7,13 +9,15 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.Billing.Controllers;
|
||||
namespace Bit.Api.AdminConsole.Controllers;
|
||||
|
||||
[Route("providers/{providerId:guid}/clients")]
|
||||
public class ProviderClientsController(
|
||||
ICurrentContext currentContext,
|
||||
IFeatureService featureService,
|
||||
ILogger<BaseProviderController> logger,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IProviderBillingService providerBillingService,
|
||||
@ -22,7 +26,10 @@ public class ProviderClientsController(
|
||||
IProviderService providerService,
|
||||
IUserService userService) : BaseProviderController(currentContext, logger, providerRepository, userService)
|
||||
{
|
||||
private readonly ICurrentContext _currentContext = currentContext;
|
||||
|
||||
[HttpPost]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IResult> CreateAsync(
|
||||
[FromRoute] Guid providerId,
|
||||
[FromBody] CreateClientOrganizationRequestBody requestBody)
|
||||
@ -80,6 +87,7 @@ public class ProviderClientsController(
|
||||
}
|
||||
|
||||
[HttpPut("{providerOrganizationId:guid}")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IResult> UpdateAsync(
|
||||
[FromRoute] Guid providerId,
|
||||
[FromRoute] Guid providerOrganizationId,
|
||||
@ -113,7 +121,7 @@ public class ProviderClientsController(
|
||||
clientOrganization.PlanType,
|
||||
seatAdjustment);
|
||||
|
||||
if (seatAdjustmentResultsInPurchase && !currentContext.ProviderProviderAdmin(provider.Id))
|
||||
if (seatAdjustmentResultsInPurchase && !_currentContext.ProviderProviderAdmin(provider.Id))
|
||||
{
|
||||
return Error.Unauthorized("Service users cannot purchase additional seats.");
|
||||
}
|
||||
@ -127,4 +135,58 @@ public class ProviderClientsController(
|
||||
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
|
||||
[HttpGet("addable")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IResult> GetAddableOrganizationsAsync([FromRoute] Guid providerId)
|
||||
{
|
||||
if (!featureService.IsEnabled(FeatureFlagKeys.P15179_AddExistingOrgsFromProviderPortal))
|
||||
{
|
||||
return Error.NotFound();
|
||||
}
|
||||
|
||||
var (provider, result) = await TryGetBillableProviderForServiceUserOperation(providerId);
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var userId = _currentContext.UserId;
|
||||
|
||||
if (!userId.HasValue)
|
||||
{
|
||||
return Error.Unauthorized();
|
||||
}
|
||||
|
||||
var addable =
|
||||
await providerBillingService.GetAddableOrganizations(provider, userId.Value);
|
||||
|
||||
return TypedResults.Ok(addable);
|
||||
}
|
||||
|
||||
[HttpPost("existing")]
|
||||
[SelfHosted(NotSelfHostedOnly = true)]
|
||||
public async Task<IResult> AddExistingOrganizationAsync(
|
||||
[FromRoute] Guid providerId,
|
||||
[FromBody] AddExistingOrganizationRequestBody requestBody)
|
||||
{
|
||||
var (provider, result) = await TryGetBillableProviderForServiceUserOperation(providerId);
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var organization = await organizationRepository.GetByIdAsync(requestBody.OrganizationId);
|
||||
|
||||
if (organization == null)
|
||||
{
|
||||
return Error.BadRequest("The organization being added to the provider does not exist.");
|
||||
}
|
||||
|
||||
await providerBillingService.AddExistingOrganization(provider, organization, requestBody.Key);
|
||||
|
||||
return TypedResults.Ok();
|
||||
}
|
||||
}
|
@ -57,9 +57,9 @@ public class OrganizationResponseModel : ResponseModel
|
||||
MaxAutoscaleSmServiceAccounts = organization.MaxAutoscaleSmServiceAccounts;
|
||||
LimitCollectionCreation = organization.LimitCollectionCreation;
|
||||
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
||||
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
|
||||
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
|
||||
LimitItemDeletion = organization.LimitItemDeletion;
|
||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||
UseRiskInsights = organization.UseRiskInsights;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
@ -103,9 +103,9 @@ public class OrganizationResponseModel : ResponseModel
|
||||
public int? MaxAutoscaleSmServiceAccounts { get; set; }
|
||||
public bool LimitCollectionCreation { get; set; }
|
||||
public bool LimitCollectionDeletion { get; set; }
|
||||
// Deperectated: https://bitwarden.atlassian.net/browse/PM-10863
|
||||
public bool LimitCollectionCreationDeletion { get; set; }
|
||||
public bool LimitItemDeletion { get; set; }
|
||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||
public bool UseRiskInsights { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
|
||||
|
@ -67,10 +67,10 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
AccessSecretsManager = organization.AccessSecretsManager;
|
||||
LimitCollectionCreation = organization.LimitCollectionCreation;
|
||||
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
||||
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
|
||||
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
|
||||
LimitItemDeletion = organization.LimitItemDeletion;
|
||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||
UserIsManagedByOrganization = organizationIdsManagingUser.Contains(organization.OrganizationId);
|
||||
UseRiskInsights = organization.UseRiskInsights;
|
||||
|
||||
if (organization.SsoConfig != null)
|
||||
{
|
||||
@ -129,8 +129,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
public bool AccessSecretsManager { get; set; }
|
||||
public bool LimitCollectionCreation { get; set; }
|
||||
public bool LimitCollectionDeletion { get; set; }
|
||||
// Deprecated: https://bitwarden.atlassian.net/browse/PM-10863
|
||||
public bool LimitCollectionCreationDeletion { get; set; }
|
||||
public bool LimitItemDeletion { get; set; }
|
||||
public bool AllowAdminAccessToAllCollectionItems { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the organization manages the user.
|
||||
@ -143,4 +142,5 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
||||
/// False if the Account Deprovisioning feature flag is disabled.
|
||||
/// </returns>
|
||||
public bool UserIsManagedByOrganization { get; set; }
|
||||
public bool UseRiskInsights { get; set; }
|
||||
}
|
||||
|
@ -43,11 +43,12 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
||||
UserId = organization.UserId;
|
||||
ProviderId = organization.ProviderId;
|
||||
ProviderName = organization.ProviderName;
|
||||
ProviderType = organization.ProviderType;
|
||||
ProductTierType = StaticStore.GetPlan(organization.PlanType).ProductTier;
|
||||
LimitCollectionCreation = organization.LimitCollectionCreation;
|
||||
LimitCollectionDeletion = organization.LimitCollectionDeletion;
|
||||
// https://bitwarden.atlassian.net/browse/PM-10863
|
||||
LimitCollectionCreationDeletion = organization.LimitCollectionCreationDeletion;
|
||||
LimitItemDeletion = organization.LimitItemDeletion;
|
||||
AllowAdminAccessToAllCollectionItems = organization.AllowAdminAccessToAllCollectionItems;
|
||||
UseRiskInsights = organization.UseRiskInsights;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class EventsController : Controller
|
||||
/// If no filters are provided, it will return the last 30 days of event for the organization.
|
||||
/// </remarks>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(ListResponseModel<EventResponseModel>), (int)HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(PagedListResponseModel<EventResponseModel>), (int)HttpStatusCode.OK)]
|
||||
public async Task<IActionResult> List([FromQuery] EventFilterRequestModel request)
|
||||
{
|
||||
var dateRange = request.ToDateRange();
|
||||
@ -65,7 +65,7 @@ public class EventsController : Controller
|
||||
}
|
||||
|
||||
var eventResponses = result.Data.Select(e => new EventResponseModel(e));
|
||||
var response = new ListResponseModel<EventResponseModel>(eventResponses, result.ContinuationToken);
|
||||
var response = new PagedListResponseModel<EventResponseModel>(eventResponses, result.ContinuationToken);
|
||||
return new JsonResult(response);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ public class PoliciesController : Controller
|
||||
/// </remarks>
|
||||
/// <param name="type">The type of policy to be retrieved.</param>
|
||||
[HttpGet("{type}")]
|
||||
[ProducesResponseType(typeof(GroupResponseModel), (int)HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(PolicyResponseModel), (int)HttpStatusCode.OK)]
|
||||
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||
public async Task<IActionResult> Get(PolicyType type)
|
||||
{
|
||||
|
@ -50,6 +50,7 @@ public class MemberResponseModel : MemberBaseModel, IResponseModel
|
||||
Status = user.Status;
|
||||
Collections = collections?.Select(c => new AssociationWithPermissionsResponseModel(c));
|
||||
ResetPasswordEnrolled = user.ResetPasswordKey != null;
|
||||
SsoExternalId = user.SsoExternalId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -104,4 +105,10 @@ public class MemberResponseModel : MemberBaseModel, IResponseModel
|
||||
/// </summary>
|
||||
[Required]
|
||||
public bool ResetPasswordEnrolled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// SSO external identifier for linking this member to an identity provider.
|
||||
/// </summary>
|
||||
/// <example>sso_external_id_123456</example>
|
||||
public string SsoExternalId { get; set; }
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
<ANCMPreConfiguredForIIS>true</ANCMPreConfiguredForIIS>
|
||||
<!-- Temp exclusions until warnings are fixed -->
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors);CS8604</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
@ -34,7 +36,7 @@
|
||||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="8.0.2" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="8.0.1" />
|
||||
<PackageReference Include="Azure.Messaging.EventGrid" Version="4.25.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -266,8 +266,18 @@ public class AccountsController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
user = model.ToUser(user);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, e.Message);
|
||||
throw new BadRequestException(ModelState);
|
||||
}
|
||||
|
||||
var result = await _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(
|
||||
model.ToUser(user),
|
||||
user,
|
||||
model.MasterPasswordHash,
|
||||
model.Key,
|
||||
model.OrgIdentifier);
|
||||
@ -666,7 +676,7 @@ public class AccountsController : Controller
|
||||
new TaxInfo
|
||||
{
|
||||
BillingAddressCountry = model.Country,
|
||||
BillingAddressPostalCode = model.PostalCode,
|
||||
BillingAddressPostalCode = model.PostalCode
|
||||
});
|
||||
|
||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
||||
@ -721,8 +731,13 @@ public class AccountsController : Controller
|
||||
await _userService.ReplacePaymentMethodAsync(user, model.PaymentToken, model.PaymentMethodType.Value,
|
||||
new TaxInfo
|
||||
{
|
||||
BillingAddressLine1 = model.Line1,
|
||||
BillingAddressLine2 = model.Line2,
|
||||
BillingAddressCity = model.City,
|
||||
BillingAddressState = model.State,
|
||||
BillingAddressCountry = model.Country,
|
||||
BillingAddressPostalCode = model.PostalCode,
|
||||
TaxIdNumber = model.TaxId
|
||||
});
|
||||
}
|
||||
|
||||
@ -961,6 +976,30 @@ public class AccountsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
[RequireFeature(FeatureFlagKeys.NewDeviceVerification)]
|
||||
[AllowAnonymous]
|
||||
[HttpPost("resend-new-device-otp")]
|
||||
public async Task ResendNewDeviceOtpAsync([FromBody] UnauthenticatedSecretVerificationRequestModel request)
|
||||
{
|
||||
await _userService.ResendNewDeviceVerificationEmail(request.Email, request.Secret);
|
||||
}
|
||||
|
||||
[HttpPost("verify-devices")]
|
||||
[HttpPut("verify-devices")]
|
||||
public async Task SetUserVerifyDevicesAsync([FromBody] SetVerifyDevicesRequestModel request)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User) ?? throw new UnauthorizedAccessException();
|
||||
|
||||
if (!await _userService.VerifySecretAsync(user, request.Secret))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(string.Empty, "User verification failed.");
|
||||
}
|
||||
user.VerifyDevices = request.VerifyDevices;
|
||||
|
||||
await _userService.SaveUserAsync(user);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<Guid>> GetOrganizationIdsManagingUserAsync(Guid userId)
|
||||
{
|
||||
var organizationManagingUser = await _userService.GetOrganizationsManagingUserAsync(userId);
|
||||
|
@ -304,7 +304,7 @@ public class TwoFactorController : Controller
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
// check if 2FA email is from passwordless
|
||||
// Check if 2FA email is from Passwordless.
|
||||
if (!string.IsNullOrEmpty(requestModel.AuthRequestAccessCode))
|
||||
{
|
||||
if (await _verifyAuthRequestCommand
|
||||
@ -317,17 +317,14 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(requestModel.SsoEmail2FaSessionToken))
|
||||
{
|
||||
if (this.ValidateSsoEmail2FaToken(requestModel.SsoEmail2FaSessionToken, user))
|
||||
if (ValidateSsoEmail2FaToken(requestModel.SsoEmail2FaSessionToken, user))
|
||||
{
|
||||
await _userService.SendTwoFactorEmailAsync(user);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.ThrowDelayedBadRequestExceptionAsync(
|
||||
"Cannot send two-factor email: a valid, non-expired SSO Email 2FA Session token is required to send 2FA emails.",
|
||||
2000);
|
||||
}
|
||||
|
||||
await ThrowDelayedBadRequestExceptionAsync(
|
||||
"Cannot send two-factor email: a valid, non-expired SSO Email 2FA Session token is required to send 2FA emails.");
|
||||
}
|
||||
else if (await _userService.VerifySecretAsync(user, requestModel.Secret))
|
||||
{
|
||||
@ -336,8 +333,7 @@ public class TwoFactorController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
await this.ThrowDelayedBadRequestExceptionAsync(
|
||||
"Cannot send two-factor email.", 2000);
|
||||
await ThrowDelayedBadRequestExceptionAsync("Cannot send two-factor email.");
|
||||
}
|
||||
|
||||
[HttpPut("email")]
|
||||
@ -374,7 +370,7 @@ public class TwoFactorController : Controller
|
||||
public async Task<TwoFactorProviderResponseModel> PutOrganizationDisable(string id,
|
||||
[FromBody] TwoFactorProviderRequestModel model)
|
||||
{
|
||||
var user = await CheckAsync(model, false);
|
||||
await CheckAsync(model, false);
|
||||
|
||||
var orgIdGuid = new Guid(id);
|
||||
if (!await _currentContext.ManagePolicies(orgIdGuid))
|
||||
@ -401,6 +397,10 @@ public class TwoFactorController : Controller
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To be removed when the feature flag pm-17128-recovery-code-login is removed PM-18175.
|
||||
/// </summary>
|
||||
[Obsolete("Two Factor recovery is handled in the TwoFactorAuthenticationValidator.")]
|
||||
[HttpPost("recover")]
|
||||
[AllowAnonymous]
|
||||
public async Task PostRecover([FromBody] TwoFactorRecoveryRequestModel model)
|
||||
@ -463,10 +463,8 @@ public class TwoFactorController : Controller
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException(name, $"{name} is invalid.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
private bool ValidateSsoEmail2FaToken(string ssoEmail2FaSessionToken, User user)
|
||||
|
@ -0,0 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||
|
||||
public class SetVerifyDevicesRequestModel : SecretVerificationRequestModel
|
||||
{
|
||||
[Required]
|
||||
public bool VerifyDevices { get; set; }
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Request.Accounts;
|
||||
|
||||
public class UnauthenticatedSecretVerificationRequestModel : SecretVerificationRequestModel
|
||||
{
|
||||
[Required]
|
||||
[StrictEmailAddress]
|
||||
[StringLength(256)]
|
||||
public string Email { get; set; }
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Response;
|
||||
@ -17,6 +18,7 @@ public class AuthRequestResponseModel : ResponseModel
|
||||
|
||||
Id = authRequest.Id;
|
||||
PublicKey = authRequest.PublicKey;
|
||||
RequestDeviceTypeValue = authRequest.RequestDeviceType;
|
||||
RequestDeviceType = authRequest.RequestDeviceType.GetType().GetMember(authRequest.RequestDeviceType.ToString())
|
||||
.FirstOrDefault()?.GetCustomAttribute<DisplayAttribute>()?.GetName();
|
||||
RequestIpAddress = authRequest.RequestIpAddress;
|
||||
@ -30,6 +32,7 @@ public class AuthRequestResponseModel : ResponseModel
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
public DeviceType RequestDeviceTypeValue { get; set; }
|
||||
public string RequestDeviceType { get; set; }
|
||||
public string RequestIpAddress { get; set; }
|
||||
public string Key { get; set; }
|
||||
|
@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using Bit.Api.Billing.Models.Responses;
|
||||
using Bit.Core.Billing.Models.Api.Requests.Accounts;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
@ -77,4 +78,18 @@ public class AccountsBillingController(
|
||||
|
||||
return TypedResults.Ok(transactions);
|
||||
}
|
||||
|
||||
[HttpPost("preview-invoice")]
|
||||
public async Task<IResult> PreviewInvoiceAsync([FromBody] PreviewIndividualInvoiceRequestBody model)
|
||||
{
|
||||
var user = await userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var invoice = await paymentService.PreviewInvoiceAsync(model, user.GatewayCustomerId, user.GatewaySubscriptionId);
|
||||
|
||||
return TypedResults.Ok(invoice);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user