1
0
mirror of https://github.com/bitwarden/server synced 2026-01-26 14:23:21 +00:00

Merge branch 'main' into auth/pm-27084/register-accepts-new-data-types

This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-12-31 17:09:38 -05:00
committed by GitHub
251 changed files with 24359 additions and 1119 deletions

117
.github/renovate.json5 vendored
View File

@@ -10,42 +10,7 @@
"nuget",
],
packageRules: [
{
groupName: "cargo minor",
matchManagers: ["cargo"],
matchUpdateTypes: ["minor"],
},
{
groupName: "dockerfile minor",
matchManagers: ["dockerfile"],
matchUpdateTypes: ["minor"],
},
{
groupName: "docker-compose minor",
matchManagers: ["docker-compose"],
matchUpdateTypes: ["minor"],
},
{
groupName: "github-action minor",
matchManagers: ["github-actions"],
matchUpdateTypes: ["minor"],
addLabels: ["hold"],
},
{
// For any Microsoft.Extensions.* and Microsoft.AspNetCore.* packages, we want to create PRs for patch updates.
// This overrides the default that ignores patch updates for nuget dependencies.
matchPackageNames: [
"/^Microsoft\\.Extensions\\./",
"/^Microsoft\\.AspNetCore\\./",
],
matchUpdateTypes: ["patch"],
dependencyDashboardApproval: false,
},
{
matchPackageNames: ["https://github.com/bitwarden/sdk-internal.git"],
groupName: "sdk-internal",
dependencyDashboardApproval: true
},
// ==================== Team Ownership Rules ====================
{
matchManagers: ["dockerfile", "docker-compose"],
commitMessagePrefix: "[deps] BRE:",
@@ -68,6 +33,7 @@
"Fido2.AspNet",
"Duende.IdentityServer",
"Microsoft.AspNetCore.Authentication.JwtBearer",
"Microsoft.Extensions.Caching.Cosmos",
"Microsoft.Extensions.Identity.Stores",
"Otp.NET",
"Sustainsys.Saml2.AspNetCore2",
@@ -101,11 +67,6 @@
commitMessagePrefix: "[deps] Billing:",
reviewers: ["team:team-billing-dev"],
},
{
matchPackageNames: ["/^Microsoft\\.EntityFrameworkCore\\./", "/^dotnet-ef/"],
groupName: "EntityFrameworkCore",
description: "Group EntityFrameworkCore to exclude them from the dotnet monorepo preset",
},
{
matchPackageNames: [
"Dapper",
@@ -153,7 +114,6 @@
"Microsoft.Extensions.DependencyInjection",
"Microsoft.Extensions.Logging",
"Microsoft.Extensions.Logging.Console",
"Microsoft.Extensions.Caching.Cosmos",
"Microsoft.Extensions.Caching.SqlServer",
"Microsoft.Extensions.Caching.StackExchangeRedis",
"Quartz",
@@ -162,6 +122,12 @@
commitMessagePrefix: "[deps] Platform:",
reviewers: ["team:team-platform-dev"],
},
{
matchUpdateTypes: ["lockFileMaintenance"],
description: "Platform owns lock file maintenance",
commitMessagePrefix: "[deps] Platform:",
reviewers: ["team:team-platform-dev"],
},
{
matchPackageNames: [
"AutoMapper.Extensions.Microsoft.DependencyInjection",
@@ -191,6 +157,73 @@
commitMessagePrefix: "[deps] Vault:",
reviewers: ["team:team-vault-dev"],
},
// ==================== Grouping Rules ====================
// These come after any specific team assignment rules to ensure
// that grouping is not overridden by subsequent rule definitions.
{
groupName: "cargo minor",
matchManagers: ["cargo"],
matchUpdateTypes: ["minor"],
},
{
groupName: "dockerfile minor",
matchManagers: ["dockerfile"],
matchUpdateTypes: ["minor"],
},
{
groupName: "docker-compose minor",
matchManagers: ["docker-compose"],
matchUpdateTypes: ["minor"],
},
{
groupName: "github-action minor",
matchManagers: ["github-actions"],
matchUpdateTypes: ["minor"],
addLabels: ["hold"],
},
{
matchPackageNames: ["/^Microsoft\\.EntityFrameworkCore\\./", "/^dotnet-ef/"],
groupName: "EntityFrameworkCore",
description: "Group EntityFrameworkCore to exclude them from the dotnet monorepo preset",
},
{
matchPackageNames: ["https://github.com/bitwarden/sdk-internal.git"],
groupName: "sdk-internal",
dependencyDashboardApproval: true
},
// ==================== Dashboard Rules ====================
{
// For any Microsoft.Extensions.* and Microsoft.AspNetCore.* packages, we want to create PRs for patch updates.
// This overrides the default that ignores patch updates for nuget dependencies.
matchPackageNames: [
"/^Microsoft\\.Extensions\\./",
"/^Microsoft\\.AspNetCore\\./",
],
matchUpdateTypes: ["patch"],
dependencyDashboardApproval: false,
},
{
// For the Platform-owned dependencies below, we have decided we will only be creating PRs
// for major updates, and sending minor (as well as patch, inherited from base config) to the dashboard.
// This rule comes AFTER grouping rules so that groups are respected while still
// sending minor/patch updates to the dependency dashboard for approval.
matchPackageNames: [
"AspNetCoreRateLimit",
"AspNetCoreRateLimit.Redis",
"Azure.Data.Tables",
"Azure.Extensions.AspNetCore.DataProtection.Blobs",
"Azure.Messaging.EventGrid",
"Azure.Messaging.ServiceBus",
"Azure.Storage.Blobs",
"Azure.Storage.Queues",
"LaunchDarkly.ServerSdk",
"Quartz",
],
matchUpdateTypes: ["minor"],
dependencyDashboardApproval: true,
},
],
ignoreDeps: ["dotnet-sdk"],
}

View File

@@ -123,7 +123,7 @@ jobs:
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
- name: Set up Node
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
cache: "npm"
cache-dependency-path: "**/package-lock.json"
@@ -160,7 +160,7 @@ jobs:
ls -atlh ../../../
- name: Upload project artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: ${{ matrix.dotnet }}
with:
name: ${{ matrix.project_name }}.zip
@@ -169,10 +169,10 @@ jobs:
########## Set up Docker ##########
- name: Set up QEMU emulators
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
########## ACRs ##########
- name: Log in to Azure
@@ -246,7 +246,7 @@ jobs:
- name: Install Cosign
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
- name: Sign image with Cosign
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
@@ -264,7 +264,7 @@ jobs:
- name: Scan Docker image
id: container-scan
uses: anchore/scan-action@f6601287cdb1efc985d6b765bbf99cb4c0ac29d8 # v7.0.0
uses: anchore/scan-action@3c9a191a0fbab285ca6b8530b5de5a642cba332f # v7.2.2
with:
image: ${{ steps.image-tags.outputs.primary_tag }}
fail-build: false
@@ -356,7 +356,7 @@ jobs:
if: |
github.event_name != 'pull_request'
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: docker-stub-US.zip
path: docker-stub-US.zip
@@ -366,7 +366,7 @@ jobs:
if: |
github.event_name != 'pull_request'
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: docker-stub-EU.zip
path: docker-stub-EU.zip
@@ -378,21 +378,21 @@ jobs:
pwsh ./generate_openapi_files.ps1
- name: Upload Public API Swagger artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: swagger.json
path: api.public.json
if-no-files-found: error
- name: Upload Internal API Swagger artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: internal.json
path: api.json
if-no-files-found: error
- name: Upload Identity Swagger artifact
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: identity.json
path: identity.json
@@ -438,7 +438,7 @@ jobs:
- name: Upload project artifact for Windows
if: ${{ contains(matrix.target, 'win') == true }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe
@@ -446,7 +446,7 @@ jobs:
- name: Upload project artifact
if: ${{ contains(matrix.target, 'win') == false }}
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility
@@ -481,7 +481,7 @@ jobs:
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
@@ -531,7 +531,7 @@ jobs:
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}

View File

@@ -59,7 +59,7 @@ jobs:
- name: Collect
id: collect
uses: launchdarkly/find-code-references@e3e9da201b87ada54eb4c550c14fb783385c5c8a # v2.13.0
uses: launchdarkly/find-code-references@89a7d362d1d4b3725fe0fe0ccd0dc69e3bdcba58 # v2.14.0
with:
accessToken: ${{ steps.get-kv-secrets.outputs.LD-ACCESS-TOKEN }}
projKey: default

View File

@@ -95,7 +95,7 @@ jobs:
uses: grafana/setup-k6-action@ffe7d7290dfa715e48c2ccc924d068444c94bde2 # v1.1.0
- name: Run k6 tests
uses: grafana/run-k6-action@c6b79182b9b666aa4f630f4a6be9158ead62536e # v1.2.0
uses: grafana/run-k6-action@a15e2072ede004e8d46141e33d7f7dad8ad08d9d # v1.3.1
continue-on-error: false
env:
K6_OTEL_METRIC_PREFIX: k6_

View File

@@ -91,7 +91,6 @@ jobs:
- project_name: Nginx
- project_name: Notifications
- project_name: Scim
- project_name: Server
- project_name: Setup
- project_name: Sso
steps:

View File

@@ -89,7 +89,7 @@ jobs:
- name: Create release
if: ${{ inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
with:
artifacts: "docker-stub-US.zip,
docker-stub-EU.zip,

View File

@@ -83,7 +83,7 @@ jobs:
version: ${{ inputs.version_number_override }}
- name: Generate GH App token
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
@@ -207,7 +207,7 @@ jobs:
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
id: app-token
with:
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}

View File

@@ -2,7 +2,7 @@ name: Code Review
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
types: [opened, synchronize, reopened]
permissions: {}

View File

@@ -156,7 +156,7 @@ jobs:
run: 'docker logs "$(docker ps --quiet --filter "name=mssql")"'
- name: Report test results
uses: dorny/test-reporter@890a17cecf52a379fc869ab770a71657660be727 # v2.1.0
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
with:
name: Test Results
@@ -165,7 +165,7 @@ jobs:
fail-on-error: true
- name: Upload to codecov.io
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
- name: Docker Compose down
if: always()
@@ -197,7 +197,7 @@ jobs:
shell: pwsh
- name: Upload DACPAC
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: sql.dacpac
path: Sql.dacpac
@@ -223,7 +223,7 @@ jobs:
shell: pwsh
- name: Report validation results
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: report.xml
path: |

View File

@@ -40,7 +40,7 @@ jobs:
toolchain: stable
- name: Cache cargo registry
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: Print environment
run: |
@@ -59,7 +59,7 @@ jobs:
run: dotnet test ./bitwarden_license/test --configuration Debug --logger "trx;LogFileName=bw-test-results.trx" /p:CoverletOutputFormatter="cobertura" --collect:"XPlat Code Coverage"
- name: Report test results
uses: dorny/test-reporter@890a17cecf52a379fc869ab770a71657660be727 # v2.1.0
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }}
with:
name: Test Results
@@ -68,4 +68,4 @@ jobs:
fail-on-error: true
- name: Upload to codecov.io
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2

View File

@@ -58,6 +58,42 @@ Invoke-RestMethod -OutFile bitwarden.ps1 `
.\bitwarden.ps1 -start
```
## Production Container Images
<details>
<summary><b>View Current Production Image Hashes</b> (click to expand)</summary>
<br>
### US Production Cluster
| Service | Image Hash |
|---------|------------|
| **Admin** | ![admin](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.admin&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **API** | ![api](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.api&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Billing** | ![billing](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.billing&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Events** | ![events](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.events&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **EventsProcessor** | ![eventsprocessor](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.eventsprocessor&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Identity** | ![identity](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.identity&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Notifications** | ![notifications](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.notifications&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **SCIM** | ![scim](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.scim&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **SSO** | ![sso](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-us.json&query=%24.sso&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
### EU Production Cluster
| Service | Image Hash |
|---------|------------|
| **Admin** | ![admin](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.admin&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **API** | ![api](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.api&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Billing** | ![billing](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.billing&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Events** | ![events](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.events&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **EventsProcessor** | ![eventsprocessor](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.eventsprocessor&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Identity** | ![identity](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.identity&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **Notifications** | ![notifications](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.notifications&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **SCIM** | ![scim](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.scim&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
| **SSO** | ![sso](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fbitwarden%2Fserver%2Frefs%2Fheads%2Fmetadata%2Fbadges%2Fshieldsio-badge-eu.json&query=%24.sso&style=flat-square&logo=docker&logoColor=white&label=&color=2496ED) |
</details>
## We're Hiring!
Interested in contributing in a big way? Consider joining our team! We're hiring for many positions. Please take a look at our [Careers page](https://bitwarden.com/careers/) to see what opportunities are currently open as well as what it's like to work at Bitwarden.

View File

@@ -57,8 +57,7 @@ public class ProviderClientsController(
Owner = user,
BillingEmail = provider.BillingEmail,
OwnerKey = requestBody.Key,
PublicKey = requestBody.KeyPair.PublicKey,
PrivateKey = requestBody.KeyPair.EncryptedPrivateKey,
Keys = requestBody.KeyPair.ToPublicKeyEncryptionKeyPairData(),
CollectionName = requestBody.CollectionName,
IsFromProvider = true
};

View File

@@ -113,11 +113,10 @@ public class OrganizationCreateRequestModel : IValidatableObject
BillingAddressCountry = BillingAddressCountry,
},
InitiationPath = InitiationPath,
SkipTrial = SkipTrial
SkipTrial = SkipTrial,
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationSignup(orgSignup);
return orgSignup;
}

View File

@@ -2,8 +2,7 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Business;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
@@ -14,48 +13,10 @@ public class OrganizationKeysRequestModel
[Required]
public string EncryptedPrivateKey { get; set; }
public OrganizationSignup ToOrganizationSignup(OrganizationSignup existingSignup)
public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData()
{
if (string.IsNullOrWhiteSpace(existingSignup.PublicKey))
{
existingSignup.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingSignup.PrivateKey))
{
existingSignup.PrivateKey = EncryptedPrivateKey;
}
return existingSignup;
}
public OrganizationUpgrade ToOrganizationUpgrade(OrganizationUpgrade existingUpgrade)
{
if (string.IsNullOrWhiteSpace(existingUpgrade.PublicKey))
{
existingUpgrade.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingUpgrade.PrivateKey))
{
existingUpgrade.PrivateKey = EncryptedPrivateKey;
}
return existingUpgrade;
}
public Organization ToOrganization(Organization existingOrg)
{
if (string.IsNullOrWhiteSpace(existingOrg.PublicKey))
{
existingOrg.PublicKey = PublicKey;
}
if (string.IsNullOrWhiteSpace(existingOrg.PrivateKey))
{
existingOrg.PrivateKey = EncryptedPrivateKey;
}
return existingOrg;
return new PublicKeyEncryptionKeyPairData(
wrappedPrivateKey: EncryptedPrivateKey,
publicKey: PublicKey);
}
}

View File

@@ -110,10 +110,9 @@ public class OrganizationNoPaymentCreateRequest
BillingAddressCountry = BillingAddressCountry,
},
InitiationPath = InitiationPath,
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationSignup(orgSignup);
return orgSignup;
}
}

View File

@@ -22,7 +22,6 @@ public class OrganizationUpdateRequestModel
OrganizationId = organizationId,
Name = Name,
BillingEmail = BillingEmail,
PublicKey = Keys?.PublicKey,
EncryptedPrivateKey = Keys?.EncryptedPrivateKey
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
}

View File

@@ -43,11 +43,10 @@ public class OrganizationUpgradeRequestModel
{
BillingAddressCountry = BillingAddressCountry,
BillingAddressPostalCode = BillingAddressPostalCode
}
},
Keys = Keys?.ToPublicKeyEncryptionKeyPairData()
};
Keys?.ToOrganizationUpgrade(orgUpgrade);
return orgUpgrade;
}
}

View File

@@ -33,7 +33,7 @@
<ItemGroup>
<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.31.0" />
<PackageReference Include="Azure.Messaging.EventGrid" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
</ItemGroup>

View File

@@ -21,7 +21,6 @@ using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Auth.Controllers;
[Route("webauthn")]
[Authorize(Policies.Web)]
public class WebAuthnController : Controller
{
private readonly IUserService _userService;
@@ -62,6 +61,7 @@ public class WebAuthnController : Controller
_featureService = featureService;
}
[Authorize(Policies.Web)]
[HttpGet("")]
public async Task<ListResponseModel<WebAuthnCredentialResponseModel>> Get()
{
@@ -71,6 +71,7 @@ public class WebAuthnController : Controller
return new ListResponseModel<WebAuthnCredentialResponseModel>(credentials.Select(c => new WebAuthnCredentialResponseModel(c)));
}
[Authorize(Policies.Application)]
[HttpPost("attestation-options")]
public async Task<WebAuthnCredentialCreateOptionsResponseModel> AttestationOptions([FromBody] SecretVerificationRequestModel model)
{
@@ -88,6 +89,7 @@ public class WebAuthnController : Controller
};
}
[Authorize(Policies.Web)]
[HttpPost("assertion-options")]
public async Task<WebAuthnLoginAssertionOptionsResponseModel> AssertionOptions([FromBody] SecretVerificationRequestModel model)
{
@@ -104,6 +106,7 @@ public class WebAuthnController : Controller
};
}
[Authorize(Policies.Application)]
[HttpPost("")]
public async Task Post([FromBody] WebAuthnLoginCredentialCreateRequestModel model)
{
@@ -149,6 +152,7 @@ public class WebAuthnController : Controller
}
}
[Authorize(Policies.Application)]
[HttpPut()]
public async Task UpdateCredential([FromBody] WebAuthnLoginCredentialUpdateRequestModel model)
{
@@ -172,6 +176,7 @@ public class WebAuthnController : Controller
await _credentialRepository.UpdateAsync(credential);
}
[Authorize(Policies.Web)]
[HttpPost("{id}/delete")]
public async Task Delete(Guid id, [FromBody] SecretVerificationRequestModel model)
{

View File

@@ -273,7 +273,7 @@ public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestMode
yield return validationResult;
}
if (!Id.HasValue || Id < 0 || Id > 5)
if (!Id.HasValue)
{
yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) });
}

View File

@@ -2,6 +2,7 @@
using Bit.Api.Billing.Models.Requests.Payment;
using Bit.Api.Billing.Models.Requests.Premium;
using Bit.Core;
using Bit.Core.Billing.Licenses.Queries;
using Bit.Core.Billing.Payment.Commands;
using Bit.Core.Billing.Payment.Queries;
using Bit.Core.Billing.Premium.Commands;
@@ -21,6 +22,7 @@ public class AccountBillingVNextController(
ICreatePremiumCloudHostedSubscriptionCommand createPremiumCloudHostedSubscriptionCommand,
IGetCreditQuery getCreditQuery,
IGetPaymentMethodQuery getPaymentMethodQuery,
IGetUserLicenseQuery getUserLicenseQuery,
IUpdatePaymentMethodCommand updatePaymentMethodCommand) : BaseBillingController
{
[HttpGet("credit")]
@@ -77,4 +79,13 @@ public class AccountBillingVNextController(
user, paymentMethod, billingAddress, additionalStorageGb);
return Handle(result);
}
[HttpGet("license")]
[InjectUser]
public async Task<IResult> GetLicenseAsync(
[BindNever] User user)
{
var response = await getUserLicenseQuery.Run(user);
return TypedResults.Ok(response);
}
}

View File

@@ -2,6 +2,7 @@
#nullable disable
using System.ComponentModel.DataAnnotations;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Api.Billing.Models.Requests;
@@ -12,4 +13,11 @@ public class KeyPairRequestBody
public string PublicKey { get; set; }
[Required(ErrorMessage = "'encryptedPrivateKey' must be provided")]
public string EncryptedPrivateKey { get; set; }
public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData()
{
return new PublicKeyEncryptionKeyPairData(
wrappedPrivateKey: EncryptedPrivateKey,
publicKey: PublicKey);
}
}

View File

@@ -1,12 +1,12 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Api.Dirt.Models.Request;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations/{organizationId:guid}/integrations/{integrationId:guid}/configurations")]
[Authorize("Application")]

View File

@@ -1,12 +1,12 @@
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Api.Dirt.Models.Request;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations/{organizationId:guid}/integrations")]
[Authorize("Application")]

View File

@@ -1,16 +1,16 @@
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations")]
[Authorize("Application")]

View File

@@ -1,18 +1,18 @@
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
namespace Bit.Api.AdminConsole.Controllers;
namespace Bit.Api.Dirt.Controllers;
[Route("organizations")]
[Authorize("Application")]

View File

@@ -1,8 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Enums;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
namespace Bit.Api.Dirt.Models.Request;
public class OrganizationIntegrationConfigurationRequestModel
{

View File

@@ -1,10 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
namespace Bit.Api.Dirt.Models.Request;
public class OrganizationIntegrationRequestModel : IValidatableObject
{

View File

@@ -1,8 +1,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Api.Dirt.Models.Response;
public class OrganizationIntegrationConfigurationResponseModel : ResponseModel
{

View File

@@ -1,10 +1,10 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Enums;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Models.Api;
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Api.Dirt.Models.Response;
public class OrganizationIntegrationResponseModel : ResponseModel
{

View File

@@ -44,7 +44,7 @@ public class SendRotationValidator : IRotationValidator<IEnumerable<SendWithIdRe
throw new BadRequestException("All existing sends must be included in the rotation.");
}
result.Add(send.ToSend(existing, _sendAuthorizationService));
result.Add(send.UpdateSend(existing, _sendAuthorizationService));
}
return result;

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Services;
@@ -45,7 +46,8 @@ public class ConfigResponseModel : ResponseModel
Sso = globalSettings.BaseServiceUri.Sso
};
FeatureStates = featureService.GetAll();
Push = PushSettings.Build(globalSettings);
var webPushEnabled = FeatureStates.TryGetValue(FeatureFlagKeys.WebPush, out var webPushEnabledValue) ? (bool)webPushEnabledValue : false;
Push = PushSettings.Build(webPushEnabled, globalSettings);
Settings = new ServerSettingsResponseModel
{
DisableUserRegistration = globalSettings.DisableUserRegistration
@@ -74,9 +76,9 @@ public class PushSettings
public PushTechnologyType PushTechnology { get; private init; }
public string VapidPublicKey { get; private init; }
public static PushSettings Build(IGlobalSettings globalSettings)
public static PushSettings Build(bool webPushEnabled, IGlobalSettings globalSettings)
{
var vapidPublicKey = globalSettings.WebPush.VapidPublicKey;
var vapidPublicKey = webPushEnabled ? globalSettings.WebPush.VapidPublicKey : null;
var pushTechnology = vapidPublicKey != null ? PushTechnologyType.WebPush : PushTechnologyType.SignalR;
return new()
{

View File

@@ -1,7 +1,4 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using System.Text.Json;
using System.Text.Json;
using Azure.Messaging.EventGrid;
using Bit.Api.Models.Response;
using Bit.Api.Tools.Models.Request;
@@ -16,6 +13,7 @@ using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Repositories;
using Bit.Core.Tools.SendFeatures;
using Bit.Core.Tools.SendFeatures.Commands.Interfaces;
using Bit.Core.Tools.SendFeatures.Queries.Interfaces;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
@@ -33,6 +31,9 @@ public class SendsController : Controller
private readonly ISendFileStorageService _sendFileStorageService;
private readonly IAnonymousSendCommand _anonymousSendCommand;
private readonly INonAnonymousSendCommand _nonAnonymousSendCommand;
private readonly ISendOwnerQuery _sendOwnerQuery;
private readonly ILogger<SendsController> _logger;
private readonly GlobalSettings _globalSettings;
@@ -42,6 +43,7 @@ public class SendsController : Controller
ISendAuthorizationService sendAuthorizationService,
IAnonymousSendCommand anonymousSendCommand,
INonAnonymousSendCommand nonAnonymousSendCommand,
ISendOwnerQuery sendOwnerQuery,
ISendFileStorageService sendFileStorageService,
ILogger<SendsController> logger,
GlobalSettings globalSettings)
@@ -51,6 +53,7 @@ public class SendsController : Controller
_sendAuthorizationService = sendAuthorizationService;
_anonymousSendCommand = anonymousSendCommand;
_nonAnonymousSendCommand = nonAnonymousSendCommand;
_sendOwnerQuery = sendOwnerQuery;
_sendFileStorageService = sendFileStorageService;
_logger = logger;
_globalSettings = globalSettings;
@@ -70,7 +73,11 @@ public class SendsController : Controller
var guid = new Guid(CoreHelpers.Base64UrlDecode(id));
var send = await _sendRepository.GetByIdAsync(guid);
SendAccessResult sendAuthResult =
if (send == null)
{
throw new BadRequestException("Could not locate send");
}
var sendAuthResult =
await _sendAuthorizationService.AccessAsync(send, model.Password);
if (sendAuthResult.Equals(SendAccessResult.PasswordRequired))
{
@@ -86,7 +93,7 @@ public class SendsController : Controller
throw new NotFoundException();
}
var sendResponse = new SendAccessResponseModel(send, _globalSettings);
var sendResponse = new SendAccessResponseModel(send);
if (send.UserId.HasValue && !send.HideEmail.GetValueOrDefault())
{
var creator = await _userService.GetUserByIdAsync(send.UserId.Value);
@@ -181,33 +188,29 @@ public class SendsController : Controller
[HttpGet("{id}")]
public async Task<SendResponseModel> Get(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
return new SendResponseModel(send, _globalSettings);
var sendId = new Guid(id);
var send = await _sendOwnerQuery.Get(sendId, User);
return new SendResponseModel(send);
}
[HttpGet("")]
public async Task<ListResponseModel<SendResponseModel>> GetAll()
{
var userId = _userService.GetProperUserId(User).Value;
var sends = await _sendRepository.GetManyByUserIdAsync(userId);
var responses = sends.Select(s => new SendResponseModel(s, _globalSettings));
return new ListResponseModel<SendResponseModel>(responses);
var sends = await _sendOwnerQuery.GetOwned(User);
var responses = sends.Select(s => new SendResponseModel(s));
var result = new ListResponseModel<SendResponseModel>(responses);
return result;
}
[HttpPost("")]
public async Task<SendResponseModel> Post([FromBody] SendRequestModel model)
{
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = model.ToSend(userId, _sendAuthorizationService);
await _nonAnonymousSendCommand.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
return new SendResponseModel(send);
}
[HttpPost("file/v2")]
@@ -229,27 +232,27 @@ public class SendsController : Controller
}
model.ValidateCreation();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var (send, data) = model.ToSend(userId, model.File.FileName, _sendAuthorizationService);
var uploadUrl = await _nonAnonymousSendCommand.SaveFileSendAsync(send, data, model.FileLength.Value);
return new SendFileUploadDataResponseModel
{
Url = uploadUrl,
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings)
SendResponse = new SendResponseModel(send)
};
}
[HttpGet("{id}/file/{fileId}")]
public async Task<SendFileUploadDataResponseModel> RenewFileUpload(string id, string fileId)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var sendId = new Guid(id);
var send = await _sendRepository.GetByIdAsync(sendId);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data);
var fileData = JsonSerializer.Deserialize<SendFileData>(send?.Data ?? string.Empty);
if (send == null || send.Type != SendType.File || (send.UserId.HasValue && send.UserId.Value != userId) ||
!send.UserId.HasValue || fileData.Id != fileId || fileData.Validated)
!send.UserId.HasValue || fileData?.Id != fileId || fileData.Validated)
{
// Not found if Send isn't found, user doesn't have access, request is faulty,
// or we've already validated the file. This last is to emulate create-only blob permissions for Azure
@@ -260,7 +263,7 @@ public class SendsController : Controller
{
Url = await _sendFileStorageService.GetSendFileUploadUrlAsync(send, fileId),
FileUploadType = _sendFileStorageService.FileUploadType,
SendResponse = new SendResponseModel(send, _globalSettings),
SendResponse = new SendResponseModel(send),
};
}
@@ -270,12 +273,16 @@ public class SendsController : Controller
[DisableFormValueModelBinding]
public async Task PostFileForExistingSend(string id, string fileId)
{
if (!Request?.ContentType.Contains("multipart/") ?? true)
if (!Request?.ContentType?.Contains("multipart/") ?? true)
{
throw new BadRequestException("Invalid content.");
}
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null)
{
throw new BadRequestException("Could not locate send");
}
await Request.GetFileAsync(async (stream) =>
{
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
@@ -286,36 +293,39 @@ public class SendsController : Controller
public async Task<SendResponseModel> Put(string id, [FromBody] SendRequestModel model)
{
model.ValidateEdit();
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
await _nonAnonymousSendCommand.SaveSendAsync(model.ToSend(send, _sendAuthorizationService));
return new SendResponseModel(send, _globalSettings);
await _nonAnonymousSendCommand.SaveSendAsync(model.UpdateSend(send, _sendAuthorizationService));
return new SendResponseModel(send);
}
[HttpPut("{id}/remove-password")]
public async Task<SendResponseModel> PutRemovePassword(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{
throw new NotFoundException();
}
// This endpoint exists because PUT preserves existing Password/Emails when not provided.
// This allows clients to update other fields without re-submitting sensitive auth data.
send.Password = null;
send.AuthType = AuthType.None;
await _nonAnonymousSendCommand.SaveSendAsync(send);
return new SendResponseModel(send, _globalSettings);
return new SendResponseModel(send);
}
[HttpDelete("{id}")]
public async Task Delete(string id)
{
var userId = _userService.GetProperUserId(User).Value;
var userId = _userService.GetProperUserId(User) ?? throw new InvalidOperationException("User ID not found");
var send = await _sendRepository.GetByIdAsync(new Guid(id));
if (send == null || send.UserId != userId)
{

View File

@@ -3,6 +3,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Api.Tools.Utilities;
using Bit.Core.Exceptions;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
@@ -10,35 +11,119 @@ using Bit.Core.Tools.Models.Data;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using static System.StringSplitOptions;
namespace Bit.Api.Tools.Models.Request;
/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendRequestModel
{
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Estimated length of the file accompanying the send. <see langword="null"/> when
/// <see cref="Type"/> is <see cref="SendType.Text"/>.
/// </summary>
public long? FileLength { get; set; } = null;
/// <summary>
/// Label for the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Name { get; set; }
/// <summary>
/// Notes for the send. This is only visible to the owner of the send.
/// </summary>
[EncryptedString]
[EncryptedStringLength(1000)]
public string Notes { get; set; }
/// <summary>
/// A base64-encoded byte array containing the Send's encryption key. This key is
/// also provided to send recipients in the Send's URL.
/// </summary>
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
public string Key { get; set; }
/// <summary>
/// The maximum number of times a send can be accessed before it expires.
/// When this value is <see langword="null" />, there is no limit.
/// </summary>
[Range(1, int.MaxValue)]
public int? MaxAccessCount { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// The date after which a send may be automatically deleted from the server.
/// When this is <see langword="null" />, the send may be deleted after it has
/// exceeded the global send timeout limit.
/// </summary>
[Required]
public DateTime? DeletionDate { get; set; }
/// <summary>
/// Contains file metadata uploaded with the send.
/// The file content is uploaded separately.
/// </summary>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
/// </summary>
[StringLength(1000)]
public string Password { get; set; }
/// <summary>
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with <see cref="Password"/>.
/// </summary>
[StringLength(4000)]
public string Emails { get; set; }
/// <summary>
/// When <see langword="true"/>, send access is disabled.
/// Defaults to <see langword="false"/>.
/// </summary>
[Required]
public bool? Disabled { get; set; }
/// <summary>
/// When <see langword="true"/> send access hides the user's email address
/// and displays a confirmation message instead. Defaults to <see langword="false"/>.
/// </summary>
public bool? HideEmail { get; set; }
/// <summary>
/// Transforms the request into a send object.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
public Send ToSend(Guid userId, ISendAuthorizationService sendAuthorizationService)
{
var send = new Send
@@ -46,12 +131,21 @@ public class SendRequestModel
Type = Type,
UserId = (Guid?)userId
};
ToSend(send, sendAuthorizationService);
send = UpdateSend(send, sendAuthorizationService);
return send;
}
/// <summary>
/// Transforms the request into a send object and file data.
/// </summary>
/// <param name="userId">The user that owns the send.</param>
/// <param name="fileName">Name of the file uploaded with the send.</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object and file data.</returns>
public (Send, SendFileData) ToSend(Guid userId, string fileName, ISendAuthorizationService sendAuthorizationService)
{
// FIXME: This method does two things: creates a send and a send file data.
// It should only do one thing.
var send = ToSendBase(new Send
{
Type = Type,
@@ -61,7 +155,13 @@ public class SendRequestModel
return (send, data);
}
public Send ToSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
/// <summary>
/// Update a send object with request content
/// </summary>
/// <param name="existingSend">The send to update</param>
/// <param name="sendAuthorizationService">Hashes the send password.</param>
/// <returns>The send object</returns>
public Send UpdateSend(Send existingSend, ISendAuthorizationService sendAuthorizationService)
{
existingSend = ToSendBase(existingSend, sendAuthorizationService);
switch (existingSend.Type)
@@ -81,6 +181,12 @@ public class SendRequestModel
return existingSend;
}
/// <summary>
/// Validates that the request is internally consistent for send creation.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's expiration date has already expired.
/// </exception>
public void ValidateCreation()
{
var now = DateTime.UtcNow;
@@ -94,6 +200,13 @@ public class SendRequestModel
ValidateEdit();
}
/// <summary>
/// Validates that the request is internally consistent for send administration.
/// </summary>
/// <exception cref="BadRequestException">
/// Thrown when the send's deletion date has already expired or when its
/// expiration occurs after its deletion.
/// </exception>
public void ValidateEdit()
{
var now = DateTime.UtcNow;
@@ -134,12 +247,30 @@ public class SendRequestModel
existingSend.ExpirationDate = ExpirationDate;
existingSend.DeletionDate = DeletionDate.Value;
existingSend.MaxAccessCount = MaxAccessCount;
if (!string.IsNullOrWhiteSpace(Password))
if (!string.IsNullOrWhiteSpace(Emails))
{
// normalize encoding
var emails = Emails.Split(',', RemoveEmptyEntries | TrimEntries);
existingSend.Emails = string.Join(",", emails);
existingSend.Password = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Email;
}
else if (!string.IsNullOrWhiteSpace(Password))
{
existingSend.Password = authorizationService.HashPassword(Password);
existingSend.Emails = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Password;
}
else
{
// Neither Password nor Emails provided - preserve existing values and infer AuthType
existingSend.AuthType = SendUtilities.InferAuthType(existingSend);
}
existingSend.Disabled = Disabled.GetValueOrDefault();
existingSend.HideEmail = HideEmail.GetValueOrDefault();
return existingSend;
}
@@ -149,8 +280,15 @@ public class SendRequestModel
}
}
/// <summary>
/// A send request issued by a Bitwarden client
/// </summary>
public class SendWithIdRequestModel : SendRequestModel
{
/// <summary>
/// Identifies the send. When this is <see langword="null" />, the client is requesting
/// a new send.
/// </summary>
[Required]
public Guid? Id { get; set; }
}

View File

@@ -3,7 +3,6 @@
using System.Text.Json;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
@@ -11,9 +10,22 @@ using Bit.Core.Utilities;
namespace Bit.Api.Tools.Models.Response;
/// <summary>
/// A response issued to a Bitwarden client in response to access operations.
/// </summary>
public class SendAccessResponseModel : ResponseModel
{
public SendAccessResponseModel(Send send, GlobalSettings globalSettings)
/// <summary>
/// Instantiates a send access response model
/// </summary>
/// <param name="send">Content to transmit to the client.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="send"/> is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
/// </exception>
public SendAccessResponseModel(Send send)
: base("send-access")
{
if (send == null)
@@ -23,6 +35,7 @@ public class SendAccessResponseModel : ResponseModel
Id = CoreHelpers.Base64UrlEncode(send.Id.ToByteArray());
Type = send.Type;
AuthType = send.AuthType;
SendData sendData;
switch (send.Type)
@@ -45,11 +58,52 @@ public class SendAccessResponseModel : ResponseModel
ExpirationDate = send.ExpirationDate;
}
/// <summary>
/// Identifies the send in a send URL
/// </summary>
public string Id { get; set; }
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Label for the send. This is only visible to the owner of the send.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Name { get; set; }
/// <summary>
/// Describes the file attached to the send.
/// </summary>
/// <remarks>
/// File content is downloaded separately using
/// <see cref="Bit.Api.Tools.Controllers.SendsController.GetSendFileDownloadData" />
/// </remarks>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// Indicates the person that created the send to the accessor.
/// </summary>
public string CreatorIdentifier { get; set; }
}

View File

@@ -2,8 +2,8 @@
#nullable disable
using System.Text.Json;
using Bit.Api.Tools.Utilities;
using Bit.Core.Models.Api;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Data;
@@ -11,9 +11,23 @@ using Bit.Core.Utilities;
namespace Bit.Api.Tools.Models.Response;
/// <summary>
/// A response issued to a Bitwarden client in response to ownership operations.
/// </summary>
/// <seealso cref="SendAccessResponseModel" />
public class SendResponseModel : ResponseModel
{
public SendResponseModel(Send send, GlobalSettings globalSettings)
/// <summary>
/// Instantiates a send response model
/// </summary>
/// <param name="send">Content to transmit to the client.</param>
/// <exception cref="ArgumentNullException">
/// Thrown when <paramref name="send"/> is <see langword="null" />
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="send" /> has an invalid <see cref="Send.Type"/>.
/// </exception>
public SendResponseModel(Send send)
: base("send")
{
if (send == null)
@@ -24,6 +38,7 @@ public class SendResponseModel : ResponseModel
Id = send.Id;
AccessId = CoreHelpers.Base64UrlEncode(send.Id.ToByteArray());
Type = send.Type;
AuthType = send.AuthType ?? SendUtilities.InferAuthType(send);
Key = send.Key;
MaxAccessCount = send.MaxAccessCount;
AccessCount = send.AccessCount;
@@ -31,6 +46,7 @@ public class SendResponseModel : ResponseModel
ExpirationDate = send.ExpirationDate;
DeletionDate = send.DeletionDate;
Password = send.Password;
Emails = send.Emails;
Disabled = send.Disabled;
HideEmail = send.HideEmail.GetValueOrDefault();
@@ -55,20 +71,113 @@ public class SendResponseModel : ResponseModel
Notes = sendData.Notes;
}
/// <summary>
/// Identifies the send to its owner
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Identifies the send in a send URL
/// </summary>
public string AccessId { get; set; }
/// <summary>
/// Indicates whether the send contains text or file data.
/// </summary>
public SendType Type { get; set; }
/// <summary>
/// Specifies the authentication method required to access this Send.
/// </summary>
public AuthType? AuthType { get; set; }
/// <summary>
/// Label for the send.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Name { get; set; }
/// <summary>
/// Notes for the send. This is only visible to the owner of the send.
/// This field is encrypted.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted encrypted content.
/// </remarks>
public string Notes { get; set; }
/// <summary>
/// Contains file metadata uploaded with the send.
/// The file content is uploaded separately.
/// </summary>
public SendFileModel File { get; set; }
/// <summary>
/// Contains text data uploaded with the send.
/// </summary>
public SendTextModel Text { get; set; }
/// <summary>
/// A base64-encoded byte array containing the Send's encryption key.
/// It's also provided to send recipients in the Send's URL.
/// </summary>
/// <remarks>
/// This field contains a base64-encoded byte array. The array contains
/// the E2E-encrypted content.
/// </remarks>
public string Key { get; set; }
/// <summary>
/// The maximum number of times a send can be accessed before it expires.
/// When this value is <see langword="null" />, there is no limit.
/// </summary>
public int? MaxAccessCount { get; set; }
/// <summary>
/// The number of times a send has been accessed since it was created.
/// </summary>
public int AccessCount { get; set; }
/// <summary>
/// Base64-encoded byte array of a password hash that grants access to the send.
/// Mutually exclusive with <see cref="Emails"/>.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with <see cref="Password"/>.
/// </summary>
public string Emails { get; set; }
/// <summary>
/// When <see langword="true"/>, send access is disabled.
/// </summary>
public bool Disabled { get; set; }
/// <summary>
/// The last time this send's data changed.
/// </summary>
public DateTime RevisionDate { get; set; }
/// <summary>
/// The date after which a send cannot be accessed. When this value is
/// <see langword="null"/>, there is no expiration date.
/// </summary>
public DateTime? ExpirationDate { get; set; }
/// <summary>
/// The date after which a send may be automatically deleted from the server.
/// </summary>
public DateTime DeletionDate { get; set; }
/// <summary>
/// When <see langword="true"/> send access hides the user's email address
/// and displays a confirmation message instead.
/// </summary>
public bool HideEmail { get; set; }
}

View File

@@ -0,0 +1,23 @@
namespace Bit.Api.Tools.Utilities;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
public class SendUtilities
{
public static AuthType InferAuthType(Send send)
{
if (!string.IsNullOrWhiteSpace(send.Password))
{
return AuthType.Password;
}
if (!string.IsNullOrWhiteSpace(send.Emails))
{
return AuthType.Email;
}
return AuthType.None;
}
}

View File

@@ -56,7 +56,7 @@ public class SyncResponseModel() : ResponseModel("sync")
c => new CollectionDetailsResponseModel(c)) ?? new List<CollectionDetailsResponseModel>();
Domains = excludeDomains ? null : new DomainsResponseModel(user, false);
Policies = policies?.Select(p => new PolicyResponseModel(p)) ?? new List<PolicyResponseModel>();
Sends = sends.Select(s => new SendResponseModel(s, globalSettings));
Sends = sends.Select(s => new SendResponseModel(s));
UserDecryption = new UserDecryptionResponseModel
{
MasterPasswordUnlock = user.HasMasterPassword()

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record DatadogIntegration(string ApiKey, Uri Uri);

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record SlackIntegration(string Token);

View File

@@ -1,3 +0,0 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
public record SlackIntegrationConfiguration(string ChannelId);

View File

@@ -99,8 +99,8 @@ public class CloudOrganizationSignUpCommand(
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
LicenseKey = CoreHelpers.SecureRandomString(20),
PublicKey = signup.PublicKey,
PrivateKey = signup.PrivateKey,
PublicKey = signup.Keys?.PublicKey,
PrivateKey = signup.Keys?.WrappedPrivateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,

View File

@@ -0,0 +1,28 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
public static class OrganizationExtensions
{
/// <summary>
/// Updates the organization public and private keys if provided and not already set.
/// This is legacy code for old organizations that were not created with a public/private keypair.
/// It is a soft migration that will silently migrate organizations when they perform certain actions,
/// e.g. change their details or upgrade their plan.
/// </summary>
public static void BackfillPublicPrivateKeys(this Organization organization, PublicKeyEncryptionKeyPairData? keyPair)
{
// Only backfill if both new keys are provided and both old keys are missing.
if (string.IsNullOrWhiteSpace(keyPair?.PublicKey) ||
string.IsNullOrWhiteSpace(keyPair.WrappedPrivateKey) ||
!string.IsNullOrWhiteSpace(organization.PublicKey) ||
!string.IsNullOrWhiteSpace(organization.PrivateKey))
{
return;
}
organization.PublicKey = keyPair.PublicKey;
organization.PrivateKey = keyPair.WrappedPrivateKey;
}
}

View File

@@ -93,8 +93,8 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
LicenseKey = CoreHelpers.SecureRandomString(20),
PublicKey = signup.PublicKey,
PrivateKey = signup.PrivateKey,
PublicKey = signup.Keys?.PublicKey,
PrivateKey = signup.Keys?.WrappedPrivateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created,

View File

@@ -39,8 +39,20 @@ public class OrganizationUpdateCommand(
var originalBillingEmail = organization.BillingEmail;
// Apply updates to organization
organization.UpdateDetails(request);
organization.BackfillPublicPrivateKeys(request);
// These values may or may not be sent by the client depending on the operation being performed.
// Skip any values not provided.
if (request.Name is not null)
{
organization.Name = request.Name;
}
if (request.BillingEmail is not null)
{
organization.BillingEmail = request.BillingEmail.ToLowerInvariant().Trim();
}
organization.BackfillPublicPrivateKeys(request.Keys);
await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
// Update billing information in Stripe if required
@@ -56,7 +68,7 @@ public class OrganizationUpdateCommand(
/// </summary>
private async Task<Organization> UpdateSelfHostedAsync(Organization organization, OrganizationUpdateRequest request)
{
organization.BackfillPublicPrivateKeys(request);
organization.BackfillPublicPrivateKeys(request.Keys);
await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
return organization;
}

View File

@@ -1,43 +0,0 @@
using Bit.Core.AdminConsole.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
public static class OrganizationUpdateExtensions
{
/// <summary>
/// Updates the organization name and/or billing email.
/// Any null property on the request object will be skipped.
/// </summary>
public static void UpdateDetails(this Organization organization, OrganizationUpdateRequest request)
{
// These values may or may not be sent by the client depending on the operation being performed.
// Skip any values not provided.
if (request.Name is not null)
{
organization.Name = request.Name;
}
if (request.BillingEmail is not null)
{
organization.BillingEmail = request.BillingEmail.ToLowerInvariant().Trim();
}
}
/// <summary>
/// Updates the organization public and private keys if provided and not already set.
/// This is legacy code for old organizations that were not created with a public/private keypair. It is a soft
/// migration that will silently migrate organizations when they change their details.
/// </summary>
public static void BackfillPublicPrivateKeys(this Organization organization, OrganizationUpdateRequest request)
{
if (!string.IsNullOrWhiteSpace(request.PublicKey) && string.IsNullOrWhiteSpace(organization.PublicKey))
{
organization.PublicKey = request.PublicKey;
}
if (!string.IsNullOrWhiteSpace(request.EncryptedPrivateKey) && string.IsNullOrWhiteSpace(organization.PrivateKey))
{
organization.PrivateKey = request.EncryptedPrivateKey;
}
}
}

View File

@@ -1,4 +1,6 @@
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
using Bit.Core.KeyManagement.Models.Data;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;
/// <summary>
/// Request model for updating the name, billing email, and/or public-private keys for an organization (legacy migration code).
@@ -22,12 +24,7 @@ public record OrganizationUpdateRequest
public string? BillingEmail { get; init; }
/// <summary>
/// The organization's public key to set (optional, only set if not already present on the organization).
/// The organization's public/private key pair to set (optional, only set if not already present on the organization).
/// </summary>
public string? PublicKey { get; init; }
/// <summary>
/// The organization's encrypted private key to set (optional, only set if not already present on the organization).
/// </summary>
public string? EncryptedPrivateKey { get; init; }
public PublicKeyEncryptionKeyPairData? Keys { get; init; }
}

View File

@@ -72,6 +72,17 @@ public class OrganizationDataOwnershipPolicyRequirement : IPolicyRequirement
{
return _policyDetails.Any(p => p.OrganizationId == organizationId);
}
/// <summary>
/// Ignore storage limits if the organization has data ownership policy enabled.
/// Allows users to seamlessly migrate their data into the organization without being blocked by storage limits.
/// Organization admins will need to manage storage after migration should overages occur.
/// </summary>
public bool IgnoreStorageLimitsOnMigration(Guid organizationId)
{
return _policyDetails.Any(p => p.OrganizationId == organizationId &&
p.OrganizationUserStatus == OrganizationUserStatusType.Confirmed);
}
}
public record DefaultCollectionRequest(Guid OrganizationUserId, bool ShouldCreateDefaultCollection)

View File

@@ -1,14 +0,0 @@
#nullable enable
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Services;
public interface IIntegrationConfigurationDetailsCache
{
List<OrganizationIntegrationConfigurationDetails> GetConfigurationDetails(
Guid organizationId,
IntegrationType integrationType,
EventType eventType);
}

View File

@@ -5,12 +5,94 @@ public static class Policies
/// <summary>
/// Policy for managing access to the Send feature.
/// </summary>
public const string Send = "Send"; // [Authorize(Policy = Policies.Send)]
public const string Application = "Application"; // [Authorize(Policy = Policies.Application)]
public const string Web = "Web"; // [Authorize(Policy = Policies.Web)]
public const string Push = "Push"; // [Authorize(Policy = Policies.Push)]
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Send)]
/// </code>
/// </example>
/// </remarks>
public const string Send = "Send";
/// <summary>
/// Policy to manage access to general API endpoints.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Application)]
/// </code>
/// </example>
/// </remarks>
public const string Application = "Application";
/// <summary>
/// Policy to manage access to API endpoints intended for use by the Web Vault and browser extension only.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Web)]
/// </code>
/// </example>
/// </remarks>
public const string Web = "Web";
/// <summary>
/// Policy to restrict access to API endpoints for the Push feature.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Push)]
/// </code>
/// </example>
/// </remarks>
public const string Push = "Push";
// TODO: This is unused
public const string Licensing = "Licensing"; // [Authorize(Policy = Policies.Licensing)]
public const string Organization = "Organization"; // [Authorize(Policy = Policies.Organization)]
public const string Installation = "Installation"; // [Authorize(Policy = Policies.Installation)]
public const string Secrets = "Secrets"; // [Authorize(Policy = Policies.Secrets)]
/// <summary>
/// Policy to restrict access to API endpoints related to the Organization features.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Licensing)]
/// </code>
/// </example>
/// </remarks>
public const string Organization = "Organization";
/// <summary>
/// Policy to restrict access to API endpoints related to the setting up new installations.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Installation)]
/// </code>
/// </example>
/// </remarks>
public const string Installation = "Installation";
/// <summary>
/// Policy to restrict access to API endpoints for Secrets Manager features.
/// </summary>
/// <remarks>
/// <example>
/// Can be used with the <c>Authorize</c> attribute, for example:
/// <code>
/// [Authorize(Policy = Policies.Secrets)]
/// </code>
/// </example>
/// </remarks>
public const string Secrets = "Secrets";
}

View File

@@ -1,5 +1,6 @@
using Bit.Core.Billing.Caches;
using Bit.Core.Billing.Caches.Implementations;
using Bit.Core.Billing.Licenses;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Organizations.Commands;
using Bit.Core.Billing.Organizations.Queries;
@@ -28,6 +29,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<ISetupIntentCache, SetupIntentDistributedCache>();
services.AddTransient<ISubscriberService, SubscriberService>();
services.AddLicenseServices();
services.AddLicenseOperations();
services.AddPricingClient();
services.AddPaymentOperations();
services.AddOrganizationLicenseCommandsQueries();

View File

@@ -0,0 +1,44 @@
using System.Security.Claims;
using Bit.Core.Billing.Licenses.Extensions;
using Bit.Core.Billing.Models.Business;
using Bit.Core.Models.Api;
namespace Bit.Core.Billing.Licenses.Models.Api.Response;
/// <summary>
/// Response model containing user license information.
/// Separated from subscription data to maintain separation of concerns.
/// </summary>
public class LicenseResponseModel : ResponseModel
{
public LicenseResponseModel(UserLicense license, ClaimsPrincipal? claimsPrincipal)
: base("license")
{
License = license;
// CRITICAL: When a license has a Token (JWT), ALWAYS use the expiration from the token claim
// The token's expiration is cryptographically secured and cannot be tampered with
// The file's Expires property can be manually edited and should NOT be trusted for display
if (claimsPrincipal != null)
{
Expiration = claimsPrincipal.GetValue<DateTime?>(UserLicenseConstants.Expires);
}
else
{
// No token - use the license file expiration (for older licenses without tokens)
Expiration = license.Expires;
}
}
/// <summary>
/// The user's license containing feature entitlements and metadata.
/// </summary>
public UserLicense License { get; set; }
/// <summary>
/// The license expiration date.
/// Extracted from the cryptographically secured JWT token when available,
/// otherwise falls back to the license file's expiration date.
/// </summary>
public DateTime? Expiration { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Bit.Core.Billing.Licenses.Models.Api.Response;
using Bit.Core.Billing.Services;
using Bit.Core.Entities;
using Bit.Core.Services;
namespace Bit.Core.Billing.Licenses.Queries;
public interface IGetUserLicenseQuery
{
Task<LicenseResponseModel> Run(User user);
}
public class GetUserLicenseQuery(
IUserService userService,
ILicensingService licensingService) : IGetUserLicenseQuery
{
public async Task<LicenseResponseModel> Run(User user)
{
var license = await userService.GenerateLicenseAsync(user);
var claimsPrincipal = licensingService.GetClaimsPrincipalFromLicense(license);
return new LicenseResponseModel(license, claimsPrincipal);
}
}

View File

@@ -0,0 +1,13 @@
using Bit.Core.Billing.Licenses.Queries;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Billing.Licenses;
public static class Registrations
{
public static void AddLicenseOperations(this IServiceCollection services)
{
// Queries
services.AddTransient<IGetUserLicenseQuery, GetUserLicenseQuery>();
}
}

View File

@@ -228,11 +228,16 @@ public static class FeatureFlagKeys
public const string CxpExportMobile = "cxp-export-mobile";
/* Platform Team */
public const string WebPush = "web-push";
public const string IpcChannelFramework = "ipc-channel-framework";
public const string PushNotificationsWhenLocked = "pm-19388-push-notifications-when-locked";
public const string PushNotificationsWhenInactive = "pm-25130-receive-push-notifications-for-inactive-users";
/* Tools Team */
/// <summary>
/// Enable this flag to share the send view used by the web and browser clients
/// on the desktop client.
/// </summary>
public const string DesktopSendUIRefresh = "desktop-send-ui-refresh";
public const string UseSdkPasswordGenerators = "pm-19976-use-sdk-password-generators";
public const string UseChromiumImporter = "pm-23982-chromium-importer";
@@ -240,11 +245,20 @@ public static class FeatureFlagKeys
public const string SendUIRefresh = "pm-28175-send-ui-refresh";
public const string SendEmailOTP = "pm-19051-send-email-verification";
/// <summary>
/// Enable this flag to output email/OTP authenticated sends from the `GET sends` endpoint. When
/// this flag is disabled, the `GET sends` endpoint omits email/OTP authenticated sends.
/// </summary>
/// <remarks>
/// This flag is server-side only, and only inhibits the endpoint returning all sends.
/// Email/OTP sends can still be created and downloaded through other endpoints.
/// </remarks>
public const string PM19051_ListEmailOtpSends = "tools-send-email-otp-listing";
/* Vault Team */
public const string CipherKeyEncryption = "cipher-key-encryption";
public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk";
public const string PhishingDetection = "phishing-detection";
public const string RemoveCardItemTypePolicy = "pm-16442-remove-card-item-type-policy";
public const string PM22134SdkCipherListView = "pm-22134-sdk-cipher-list-view";
public const string PM22136_SdkCipherEncryption = "pm-22136-sdk-cipher-encryption";
public const string PM23904_RiskInsightsForPremium = "pm-23904-risk-insights-for-premium";

View File

@@ -1,8 +1,8 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Entities;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.Entities;
namespace Bit.Core.Dirt.Entities;
public class OrganizationIntegration : ITableObject<Guid>
{

View File

@@ -2,7 +2,7 @@
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.AdminConsole.Entities;
namespace Bit.Core.Dirt.Entities;
public class OrganizationIntegrationConfiguration : ITableObject<Guid>
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.Enums;
namespace Bit.Core.Dirt.Enums;
public enum IntegrationType : int
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Api.AdminConsole.Models.Response.Organizations;
namespace Bit.Core.Dirt.Enums;
public enum OrganizationIntegrationStatus : int
{

View File

@@ -1,13 +1,15 @@
using Azure.Messaging.ServiceBus;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.AdminConsole.Models.Teams;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.AdminConsole.Services.NoopImplementations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Models.Data.EventIntegrations;
using Bit.Core.Dirt.Models.Data.Teams;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Dirt.Services.Implementations;
using Bit.Core.Dirt.Services.NoopImplementations;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;

View File

@@ -1,13 +1,13 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for creating organization integration configurations with validation and cache invalidation support.

View File

@@ -1,11 +1,11 @@
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for deleting organization integration configurations with cache invalidation support.

View File

@@ -1,9 +1,9 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Query implementation for retrieving organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for creating organization integration configurations.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for deleting organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Query interface for retrieving organization integration configurations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
/// <summary>
/// Command interface for updating organization integration configurations.

View File

@@ -1,13 +1,13 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Dirt.Services;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrationConfigurations;
/// <summary>
/// Command implementation for updating organization integration configurations with validation and cache invalidation support.

View File

@@ -1,12 +1,12 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for creating organization integrations with cache invalidation support.

View File

@@ -1,11 +1,11 @@
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for deleting organization integrations with cache invalidation support.

View File

@@ -1,8 +1,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Query implementation for retrieving organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for creating an OrganizationIntegration.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for deleting organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Query interface for retrieving organization integrations.

View File

@@ -1,6 +1,6 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
/// <summary>
/// Command interface for updating organization integrations.

View File

@@ -1,12 +1,12 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Microsoft.Extensions.DependencyInjection;
using ZiggyCreatures.Caching.Fusion;
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
namespace Bit.Core.Dirt.EventIntegrations.OrganizationIntegrations;
/// <summary>
/// Command implementation for updating organization integrations with cache invalidation support.

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record DatadogIntegration(string ApiKey, Uri Uri);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record DatadogIntegrationConfigurationDetails(string ApiKey, Uri Uri);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class DatadogListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record HecIntegration(Uri Uri, string Scheme, string Token, string? Service = null);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class HecListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IEventListenerConfiguration
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IIntegrationListenerConfiguration : IEventListenerConfiguration
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public interface IIntegrationMessage
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
/// <summary>
/// Categories of event integration failures used for classification and retry logic.

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationFilterGroup
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public enum IntegrationFilterOperation
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationFilterRule
{

View File

@@ -1,4 +1,4 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
/// <summary>
/// Represents the result of an integration handler operation, including success status,

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationMessage : IIntegrationMessage
{

View File

@@ -1,8 +1,8 @@
using System.Security.Cryptography;
using System.Text;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationOAuthState
{

View File

@@ -4,7 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class IntegrationTemplateContext(EventMessage eventMessage)
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public abstract class ListenerConfiguration
{

View File

@@ -1,9 +1,8 @@
using System.Text.Json.Nodes;
using Bit.Core.Dirt.Enums;
using Bit.Core.Enums;
#nullable enable
namespace Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class OrganizationIntegrationConfigurationDetails
{

View File

@@ -1,6 +1,6 @@
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class RepositoryListenerConfiguration(GlobalSettings globalSettings)
: ListenerConfiguration(globalSettings), IEventListenerConfiguration

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegration(string Token);

View File

@@ -0,0 +1,3 @@
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegrationConfiguration(string ChannelId);

View File

@@ -1,3 +1,3 @@
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public record SlackIntegrationConfigurationDetails(string ChannelId, string Token);

View File

@@ -1,7 +1,7 @@
using Bit.Core.Enums;
using Bit.Core.Dirt.Enums;
using Bit.Core.Settings;
namespace Bit.Core.AdminConsole.Models.Data.EventIntegrations;
namespace Bit.Core.Dirt.Models.Data.EventIntegrations;
public class SlackListenerConfiguration(GlobalSettings globalSettings) :
ListenerConfiguration(globalSettings), IIntegrationListenerConfiguration

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