Compare commits

..
15 Commits
Author SHA1 Message Date
Tom 3714b504f6 Fix Clippy warnings across all targets (#7782) 2026-10-03 13:50:29 +02:00
Tom f9f011d50c Hide the whole change-email section when EMAIL_CHANGE_ALLOWED is false (#7759) 2026-10-03 13:49:53 +02:00
Julian Speckmann cb89088bc3 Add Windows native credential sync to supported feature flags (#7798) 2026-10-03 13:49:44 +02:00
Pier Carlo Cadoppi 4444442075 Add undetermined-cipher-scenario-logic feature flag (#7802)
Introduced in Browser 2026.2.0 via bitwarden/clients#18395. Rewrites the
post-submit add/update login notification triggering logic, deferring
cipher-type inference until the vault is unlocked. Client-side only.
2026-10-03 13:49:37 +02:00
Mathijs van Veluw 061694d0cb Fix cortex-a53 build issues when using xx-cargo (#7774)
When using xx-cargo it uses a different linker then the default one.
This causes some default fixes Debian/Ubuntu add for the Cortex-A53
architectures to not be included.

This commit fixes that by checking if the target architecture is arm64
and if so, will add the needed fix argument. This should prevent issues
reported here #7754.

We can not say this with 100% certainty since this bug sometimes appears
and sometimes it does not.

Fixes #7754

Signed-off-by: BlackDex <black.dex@gmail.com>
2026-09-25 21:53:06 +02:00
Daniel García 32098ca7d1 Require confirmed membership for user access checks, not for admin views (#7763) 2026-09-23 23:22:43 +02:00
Abhi ShandilyaandDaniel García d2660324e6 Fix revoked org members retaining access to org ciphers (#7554)
The cipher access restriction queries (direct collection access, group
collection access, and full access via groups) never checked the
organization membership status. Since users_collections and groups_users
rows are kept when a membership is revoked, revoked (and not yet
confirmed) members kept read, write, delete, attachment and
collection-move access to org ciphers via the direct-by-UUID endpoints.

Require a confirmed membership in the cipher's organization in all
three queries, matching the behavior of the sync queries.

Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
2026-09-23 19:44:05 +02:00
Stefan Melmuk cc67d644f6 set user_created bool for each separate invitation (#7753) 2026-09-18 20:35:11 +02:00
Berk D. Demir 3347698712 Add pm-34171-card-scanner feature flag (#7477)
Introduced in 2026.4.1 Mobile releases (Android and iOS), enables use of
device camera to autofill a card type item.

N.B. This feature relies on Google ML Kit. F-Droid policy disallows
Google ML Kit. Because of this, F-Droid builds do not ship with card
scanner feature. (See https://github.com/bitwarden/android/pull/6890)
2026-09-18 20:34:41 +02:00
Mathijs van Veluw a24683636a Update Crates, GHA and JS (#7751)
Signed-off-by: BlackDex <black.dex@gmail.com>
2026-09-18 18:44:50 +02:00
Berk D. Demir 64c56411f6 Add pm-32009-new-item-types feature flag (#7478)
Introduced in 2026.4.0 for all clients, enables new item types bank
account, driver's license, and passport.
2026-09-18 16:53:27 +02:00
Tom c3f5477525 Add accepted organization data to sync response (#7666) 2026-09-18 16:53:17 +02:00
TimshelandTimshel 9c8aa2359f User key id (#7693)
Co-authored-by: Timshel <timshel@users.noreply.github.com>
2026-09-18 16:53:06 +02:00
Tom 8719f86fcf Add basic auth response client feature flag (#7745) 2026-09-18 16:52:57 +02:00
Tom fddc8e71be Support the vault banner policy (#7748) 2026-09-18 16:52:49 +02:00
40 changed files with 2733 additions and 2513 deletions

No files matched your search

+4
View File
@@ -402,10 +402,14 @@
## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >= 2025.9.2) ## - "cxp-import-mobile": Enable the import via CXP on iOS (Clients >= 2025.9.2)
## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >= 2025.9.2) ## - "cxp-export-mobile": Enable the export via CXP on iOS (Clients >= 2025.9.2)
## - "pm-30529-webauthn-related-origins": ## - "pm-30529-webauthn-related-origins":
## - "pm-32009-new-item-types": Enable new item types: Bank Account, Driver's License, and Passport (Clients >= 2026.4.0)
## - "pm-34171-card-scanner": Enable the new card scanner feature on mobile (Android >= 2026.4.1, iOS >= 2026.4.1)
## - "desktop-ui-migration-milestone-1": Special feature flag for desktop UI (Desktop >= 2026.2.0) ## - "desktop-ui-migration-milestone-1": Special feature flag for desktop UI (Desktop >= 2026.2.0)
## - "desktop-ui-migration-milestone-2": Special feature flag for desktop UI (Desktop >= 2026.2.0) ## - "desktop-ui-migration-milestone-2": Special feature flag for desktop UI (Desktop >= 2026.2.0)
## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0) ## - "desktop-ui-migration-milestone-3": Special feature flag for desktop UI (Desktop >= 2026.2.0)
## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0) ## - "desktop-ui-migration-milestone-4": Special feature flag for desktop UI (Desktop >= 2026.2.0)
## - "enable-basic-auth-response": Enable HTTP Basic Auth autofill in the browser extension (Browser >= 2026.9.0)
## - "undetermined-cipher-scenario-logic": Enable the rewritten add/update login notification triggering logic in the browser extension (Browser >= 2026.2.0)
# EXPERIMENTAL_CLIENT_FEATURE_FLAGS= # EXPERIMENTAL_CLIENT_FEATURE_FLAGS=
## Require new device emails. When a user logs in an email is required to be sent. ## Require new device emails. When a user logs in an email is required to be sent.
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
steps: steps:
# Start Docker Buildx # Start Docker Buildx
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
# https://github.com/moby/buildkit/issues/3969 # https://github.com/moby/buildkit/issues/3969
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
with: with:
+3 -3
View File
@@ -58,13 +58,13 @@ jobs:
steps: steps:
- name: Initialize QEMU binfmt support - name: Initialize QEMU binfmt support
uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0 uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
with: with:
platforms: "arm64,arm" platforms: "arm64,arm"
# Start Docker Buildx # Start Docker Buildx
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
# https://github.com/moby/buildkit/issues/3969 # https://github.com/moby/buildkit/issues/3969
# Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills # Also set max parallelism to 2, the default of 4 breaks GitHub Actions and causes OOMKills
with: with:
@@ -185,7 +185,7 @@ jobs:
- name: Bake ${{ matrix.base_image }} containers - name: Bake ${{ matrix.base_image }} containers
id: bake_vw id: bake_vw
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 uses: docker/bake-action@018cb6412ab401ebaa809aa5f85966b74628600f # v7.4.0
env: env:
BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}" BASE_TAGS: "${{ steps.determine-version.outputs.BASE_TAGS }}"
SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}" SOURCE_COMMIT: "${{ env.SOURCE_COMMIT }}"
+1 -1
View File
@@ -50,6 +50,6 @@ jobs:
severity: CRITICAL,HIGH severity: CRITICAL,HIGH
- name: Upload Trivy scan results to GitHub Security tab - name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9 uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with: with:
sarif_file: 'trivy-results.sarif' sarif_file: 'trivy-results.sarif'
+1 -1
View File
@@ -23,4 +23,4 @@ jobs:
# When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too # When this version is updated, do not forget to update this in `.pre-commit-config.yaml` too
- name: Spell Check Repo - name: Spell Check Repo
uses: crate-ci/typos@d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 uses: crate-ci/typos@512fc24f32f44ab01972217aaaf3dc86ec234d53 # v1.50.2
+1 -1
View File
@@ -18,7 +18,7 @@ repos:
# When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too # When this version is updated, do not forget to update this in `.github/workflows/typos.yaml` too
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: d43b6c087ac471e2ea7b8af622ff15f05c0c365b # v1.50.1 rev: 512fc24f32f44ab01972217aaaf3dc86ec234d53 # v1.50.2
hooks: hooks:
- id: typos - id: typos
always_run: true always_run: true
Generated
+135 -136
View File
@@ -112,7 +112,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@@ -151,9 +151,9 @@ dependencies = [
[[package]] [[package]]
name = "async-compression" name = "async-compression"
version = "0.4.45" version = "0.4.48"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a8ec73eb862508b7041723c89386365894b4e7d9f6998bf1b8529e5b0ee254" checksum = "fb61aea1a7def73ee7c350a184f0e70b32c182344e2e75bf70c9b621b83417fd"
dependencies = [ dependencies = [
"compression-codecs", "compression-codecs",
"compression-core", "compression-core",
@@ -318,18 +318,14 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
name = "asyncband" name = "asyncband"
version = "0.6.7" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94a214ba60d6231afd0e805e3c27c45a1626d9debaa5a5061c45a1ea1b2f1ed0" checksum = "f2d85fd3d291fabcc40c7232c92c280ec1754fd7b5d7ea769f143222143e179a"
dependencies = [
"hashbrown 0.17.1",
"slab",
]
[[package]] [[package]]
name = "atomic" name = "atomic"
@@ -403,9 +399,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-runtime" name = "aws-runtime"
version = "1.9.2" version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef47857a1d4488b528f4a5d5715fa7c3300820897824152234d3fa22b1426657" checksum = "25b43ad47adc2517efe3d706559d94b97e50e80e0321b3cadbc9f77cee88adcd"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-sigv4", "aws-sigv4",
@@ -428,9 +424,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.109.0" version = "1.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3cfe74df5d9ad2fedd691973ad3521ebf4f27a3c68c792556686aedb5519bab" checksum = "65700d75f6da78c89a61a49f1539979400a4609d3329376b1dc2133d7a1b2b90"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -454,9 +450,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.111.0" version = "1.113.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81b0ec31ed6191bd11350aae4b2004198f2db21350cb0a20c57e0a92e55dd161" checksum = "eb1147c254af7b6cee3ace90ae7da6f21366ce9a9b899035fa8a0fdf586eb75a"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -480,9 +476,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.114.0" version = "1.116.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef45745026107ec30c4ef86bd8ae4b002e7e5f6a86e4225240bdf6b06a0b944a" checksum = "035c7392e751d20bf1060d625d023587eb14099646110a9cff7c697bfe4b253a"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"aws-credential-types", "aws-credential-types",
@@ -507,9 +503,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-sigv4" name = "aws-sigv4"
version = "1.5.1" version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" checksum = "31d955e76ff96acd555bf06fa0fa6d5bf9335fa84ae7c64481b20ae61d231f70"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-smithy-http", "aws-smithy-http",
@@ -561,9 +557,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-json" name = "aws-smithy-json"
version = "0.63.0" version = "0.63.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" checksum = "3385d469edbe8b60cc72002784652b5efca39178192aa9cc4b44c9875c6bdc18"
dependencies = [ dependencies = [
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-schema", "aws-smithy-schema",
@@ -581,9 +577,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-query" name = "aws-smithy-query"
version = "0.62.0" version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512346c7212ab7436df2d77a16d976a468ae44a418835511d2a69269810aaf62" checksum = "f1d1d71f6562be974caa85442ecd90194c40fdb5df045f182a6c2e872ce95056"
dependencies = [ dependencies = [
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-schema", "aws-smithy-schema",
@@ -594,9 +590,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-runtime" name = "aws-smithy-runtime"
version = "1.14.0" version = "1.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" checksum = "3296253d3a91b3f938a3f2bcce4daebadcb4aa4228153fcec90f9d23532b4484"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http", "aws-smithy-http",
@@ -619,9 +615,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-runtime-api" name = "aws-smithy-runtime-api"
version = "1.16.0" version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c054752dd9e4dc73d0b75748c99ac2d0feafbf2f25c7b0516f03a3534161223" checksum = "6d881a7b7ad179fd6611680c9de89f716fb00ab40299a9a7b8c6913e8f7511a8"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-runtime-api-macros", "aws-smithy-runtime-api-macros",
@@ -648,9 +644,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-schema" name = "aws-smithy-schema"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" checksum = "e8f395d93304280b64b7632fea798d177e74897fe7f063416ce627cd6fa24829"
dependencies = [ dependencies = [
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types", "aws-smithy-types",
@@ -659,9 +655,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-types" name = "aws-smithy-types"
version = "1.6.3" version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f94d16e797ec62cd999fc9d5942b48fa7050c3093ddadff48e4d7528d16fcb9" checksum = "0b791f3ac597193fe1d08b82366986eb1f5bc31f2ac6c194c0855276116c76cd"
dependencies = [ dependencies = [
"base64-simd", "base64-simd",
"bytes", "bytes",
@@ -682,9 +678,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-xml" name = "aws-smithy-xml"
version = "0.62.0" version = "0.62.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce84f71c72fee2cbbadde6e7d082f5fb466e3a84733855295fa7aafd1b31b7d8" checksum = "0b932c8d6dc127fc980eecd78f8694ae9b9551b69a93a7def2a199c1c0033daf"
dependencies = [ dependencies = [
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-schema", "aws-smithy-schema",
@@ -785,9 +781,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.13.1" version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]] [[package]]
name = "blake2" name = "blake2"
@@ -930,9 +926,9 @@ checksum = "f5813789573ae815c8b4be58c4428e0e7ae05f0227678ba9de332ded585b9159"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.5" version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -942,9 +938,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
@@ -1011,9 +1007,9 @@ dependencies = [
[[package]] [[package]]
name = "compression-codecs" name = "compression-codecs"
version = "0.4.40" version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "100590da849306918656ffbb22576bbdfc1382b50ad10d1d50ec177d3b205fb2" checksum = "bef16c47ba2797aa6a909cc37d39911f3a6743811fe7408ac0b0cc0276b656e9"
dependencies = [ dependencies = [
"brotli", "brotli",
"compression-core", "compression-core",
@@ -1170,9 +1166,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.1" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -1360,7 +1356,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1393,7 +1389,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [ dependencies = [
"darling_core 0.24.1", "darling_core 0.24.1",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1567,7 +1563,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
@@ -1581,7 +1577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a" checksum = "e3b934ddbdcb2abb9f9fc9c30bd47bcc5618b615eea1d334cda5fdf8ff9b072a"
dependencies = [ dependencies = [
"bigdecimal", "bigdecimal",
"bitflags 2.13.1", "bitflags 2.13.2",
"byteorder", "byteorder",
"chrono", "chrono",
"diesel_derives", "diesel_derives",
@@ -1676,7 +1672,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -1920,9 +1916,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
[[package]] [[package]]
name = "flate2" name = "flate2"
@@ -2040,7 +2036,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -2311,9 +2307,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hickory-net" name = "hickory-net"
version = "0.26.2" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084e7bd6a377435d568f652153e571b50970d7ccc1d1eeec0519f834632287e1" checksum = "c480823ed7c2c5d0f09c41020cb6b7c28029ce60ec42dc942158dcf22f8e0a4d"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"cfg-if", "cfg-if",
@@ -2335,9 +2331,9 @@ dependencies = [
[[package]] [[package]]
name = "hickory-proto" name = "hickory-proto"
version = "0.26.2" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e2da0694c15b44c6f68a6b05e0233617008c54080e31d6eb848d858a9c5b38d" checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"idna", "idna",
@@ -2355,9 +2351,9 @@ dependencies = [
[[package]] [[package]]
name = "hickory-resolver" name = "hickory-resolver"
version = "0.26.2" version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4f9f4603319422d482738f3f6fe5aac03157fdbfed1cd85a3ff45adb09072f" checksum = "3f3da5255c95d5a716857d54b5b8f4e8d67c3484d3beaaaae2ce25063b3ba981"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@@ -2555,7 +2551,7 @@ dependencies = [
"http 1.5.0", "http 1.5.0",
"hyper 1.11.1", "hyper 1.11.1",
"hyper-util", "hyper-util",
"rustls 0.23.44", "rustls 0.23.45",
"tokio", "tokio",
"tokio-rustls 0.26.5", "tokio-rustls 0.26.5",
"tower-service", "tower-service",
@@ -2805,9 +2801,9 @@ checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.35" version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
dependencies = [ dependencies = [
"defmt", "defmt",
"jiff-core", "jiff-core",
@@ -2824,18 +2820,19 @@ dependencies = [
[[package]] [[package]]
name = "jiff-core" name = "jiff-core"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
dependencies = [ dependencies = [
"defmt", "defmt",
"log",
] ]
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.35" version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
dependencies = [ dependencies = [
"jiff-core", "jiff-core",
"proc-macro2", "proc-macro2",
@@ -2941,9 +2938,9 @@ dependencies = [
[[package]] [[package]]
name = "jsonwebtoken" name = "jsonwebtoken"
version = "11.0.0" version = "11.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "881733cbc631fc9e472e24447ce32a64bedf2da498d6d8570b08edc87de71f65" checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",
@@ -3011,7 +3008,7 @@ dependencies = [
"nom 8.0.0", "nom 8.0.0",
"percent-encoding", "percent-encoding",
"quoted_printable", "quoted_printable",
"rustls 0.23.44", "rustls 0.23.45",
"rustls-native-certs", "rustls-native-certs",
"serde", "serde",
"socket2 0.6.5", "socket2 0.6.5",
@@ -3109,7 +3106,7 @@ name = "macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -3267,7 +3264,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -3299,7 +3296,7 @@ version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -3379,7 +3376,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -3485,9 +3482,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal" name = "opendal"
version = "0.59.1" version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950151f9587a51a7bed70a15fa0cff464eae96e41ae7499f97067bdafdf43eb" checksum = "9fe43e16d96bed57937eb7c4a28559bf4c5fc4a3951656505d1fdd4f856349b4"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"opendal-service-fs", "opendal-service-fs",
@@ -3496,9 +3493,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-core" name = "opendal-core"
version = "0.59.1" version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a43405d217dfdfb543f58847336d3af672897dd1939bb7dcf314b63cf364f1c9" checksum = "de4566a412776e3f65d53dd9fceced5b432a0a9c9a1e8d50ccf58823795e5c5b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"asyncband", "asyncband",
@@ -3522,9 +3519,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-http-transport-reqwest" name = "opendal-http-transport-reqwest"
version = "0.59.1" version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "401999057db611e592f883fcf2cbd6754ff37af587deaadd07b8c1398b2b6b06" checksum = "f772ce6c137ab43647726116d505a649a88d41928fcd813438eb30b02312bcbb"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
@@ -3536,9 +3533,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-fs" name = "opendal-service-fs"
version = "0.59.1" version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb9caf04d6d38713299dd4abac984b95ab16ee20e1ff09160f495c5a64644083" checksum = "a0e7d8974aebd33be899200c9d0d50b2141fd5fa5c462b29e20653e3ddbfe339"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@@ -3550,9 +3547,9 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-s3" name = "opendal-service-s3"
version = "0.59.1" version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388b1d39b62535c62803754ebef89808859558697366dbedd0299345887ba461" checksum = "2942f2d8d3d4953c0e8d07cd1244879628d084948e48ce6b6d808c5938f7858d"
dependencies = [ dependencies = [
"base64 0.23.1", "base64 0.23.1",
"bytes", "bytes",
@@ -3606,7 +3603,7 @@ version = "0.10.81"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@@ -4060,7 +4057,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [ dependencies = [
"toml_edit 0.25.13+spec-1.1.0", "toml_edit 0.25.15+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -4252,7 +4249,7 @@ version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
] ]
[[package]] [[package]]
@@ -4261,7 +4258,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
] ]
[[package]] [[package]]
@@ -4281,7 +4278,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -4424,7 +4421,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls 0.23.44", "rustls 0.23.45",
"rustls-pki-types", "rustls-pki-types",
"rustls-platform-verifier", "rustls-platform-verifier",
"serde", "serde",
@@ -4668,11 +4665,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.4" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -4693,9 +4690,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.44" version = "0.23.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@@ -4747,7 +4744,7 @@ dependencies = [
"jni", "jni",
"log", "log",
"once_cell", "once_cell",
"rustls 0.23.44", "rustls 0.23.45",
"rustls-native-certs", "rustls-native-certs",
"rustls-platform-verifier-android", "rustls-platform-verifier-android",
"rustls-webpki 0.103.15", "rustls-webpki 0.103.15",
@@ -4889,7 +4886,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"core-foundation 0.10.1", "core-foundation 0.10.1",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -4959,7 +4956,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -5055,7 +5052,7 @@ dependencies = [
"darling 0.24.1", "darling 0.24.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -5195,9 +5192,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.16.0" version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
[[package]] [[package]]
name = "socket2" name = "socket2"
@@ -5324,9 +5321,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.5" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -5353,6 +5350,17 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "synstructure"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
]
[[package]] [[package]]
name = "syslog" name = "syslog"
version = "7.0.0" version = "7.0.0"
@@ -5371,7 +5379,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.2",
"core-foundation 0.9.4", "core-foundation 0.9.4",
"system-configuration-sys", "system-configuration-sys",
] ]
@@ -5442,7 +5450,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -5507,18 +5515,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.13.2" version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
@@ -5545,7 +5544,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@@ -5564,7 +5563,7 @@ version = "0.26.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
dependencies = [ dependencies = [
"rustls 0.23.44", "rustls 0.23.45",
"tokio", "tokio",
] ]
@@ -5674,9 +5673,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.25.13+spec-1.1.0" version = "0.25.15+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"
dependencies = [ dependencies = [
"indexmap 2.14.2", "indexmap 2.14.2",
"toml_datetime 1.1.1+spec-1.1.0", "toml_datetime 1.1.1+spec-1.1.0",
@@ -5733,7 +5732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"async-compression", "async-compression",
"bitflags 2.13.1", "bitflags 2.13.2",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -5881,9 +5880,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@@ -5936,9 +5935,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.26.0" version = "1.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"js-sys", "js-sys",
@@ -6019,7 +6018,7 @@ dependencies = [
"rocket", "rocket",
"rocket_ws", "rocket_ws",
"rpassword", "rpassword",
"rustls 0.23.44", "rustls 0.23.45",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
@@ -6134,7 +6133,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -6612,14 +6611,14 @@ dependencies = [
[[package]] [[package]]
name = "yoke-derive" name = "yoke-derive"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
"synstructure", "synstructure 0.14.0",
] ]
[[package]] [[package]]
@@ -6666,14 +6665,14 @@ dependencies = [
[[package]] [[package]]
name = "zerofrom-derive" name = "zerofrom-derive"
version = "0.1.7" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
"synstructure", "synstructure 0.14.0",
] ]
[[package]] [[package]]
@@ -6726,14 +6725,14 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]] [[package]]
name = "zmij" name = "zmij"
+7 -7
View File
@@ -125,11 +125,11 @@ libsqlite3-sys = { version = "0.38.2", optional = true }
# Crypto-related libraries # Crypto-related libraries
rand = "0.10.2" rand = "0.10.2"
ring = "0.17.14" ring = "0.17.14"
rustls = { version = "0.23.44", features = ["ring", "std"], default-features = false } rustls = { version = "0.23.45", features = ["ring", "std"], default-features = false }
subtle = "2.6.1" subtle = "2.6.1"
# UUID generation # UUID generation
uuid = { version = "1.26.0", features = ["v4"] } uuid = { version = "1.26.1", features = ["v4"] }
# Date and time libraries # Date and time libraries
chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] } chrono = { version = "0.4.45", default-features = false, features = ["clock", "serde"] }
@@ -143,7 +143,7 @@ job_scheduler_ng = "2.5.0"
data-encoding = "2.11.1" data-encoding = "2.11.1"
# JWT library # JWT library
jsonwebtoken = { version = "11.0.0", default-features = false, features = ["rust_crypto", "use_pem"] } jsonwebtoken = { version = "11.1.0", default-features = false, features = ["rust_crypto", "use_pem"] }
# TOTP library # TOTP library
totp-lite = "2.0.1" totp-lite = "2.0.1"
@@ -201,7 +201,7 @@ reqwest = { version = "0.13.5", default-features = false, features = [
"socks", "socks",
"system-proxy", "system-proxy",
] } ] }
hickory-resolver = "0.26.2" hickory-resolver = "0.26.3"
# Favicon extraction libraries # Favicon extraction libraries
html5gum = "0.8.4" html5gum = "0.8.4"
@@ -257,8 +257,8 @@ rpassword = "7.5.4"
grass_compiler = { version = "0.13.4", default-features = false } grass_compiler = { version = "0.13.4", default-features = false }
# File are accessed through Apache OpenDAL # File are accessed through Apache OpenDAL
opendal = { version = "0.59.1", default-features = false, features = ["services-fs"] } opendal = { version = "0.59.2", default-features = false, features = ["services-fs"] }
opendal-http-transport-reqwest = { version = "0.59.1", default-features = false, features = ["rustls-no-provider"], optional = true } opendal-http-transport-reqwest = { version = "0.59.2", default-features = false, features = ["rustls-no-provider"], optional = true }
# For retrieving AWS credentials, including temporary SSO credentials # For retrieving AWS credentials, including temporary SSO credentials
aws-config = { version = "1.12.0", optional = true, default-features = false, features = [ aws-config = { version = "1.12.0", optional = true, default-features = false, features = [
@@ -268,7 +268,7 @@ aws-config = { version = "1.12.0", optional = true, default-features = false, fe
"sso", "sso",
] } ] }
aws-credential-types = { version = "1.3.0", optional = true } aws-credential-types = { version = "1.3.0", optional = true }
aws-smithy-runtime-api = { version = "1.16.0", optional = true } aws-smithy-runtime-api = { version = "1.16.2", optional = true }
http = { version = "1.5.0", optional = true } http = { version = "1.5.0", optional = true }
reqsign-aws-v4 = { version = "3.3.0", optional = true } reqsign-aws-v4 = { version = "3.3.0", optional = true }
reqsign-core = { version = "3.3.1", optional = true } reqsign-core = { version = "3.3.1", optional = true }
+20 -4
View File
@@ -101,9 +101,17 @@ RUN . /env-cargo && \
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
# https://github.com/dani-garcia/vaultwarden/discussions/7522 # https://github.com/dani-garcia/vaultwarden/discussions/7522
if xx-info is-cross; then \ if xx-info is-cross; then \
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
export XX_RUSTFLAGS; \
fi && \ fi && \
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
if [ "$(xx-info arch)" = "arm64" ]; then \
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
fi && \
export XX_RUSTFLAGS && \
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
find . -not -path "./target*" -delete find . -not -path "./target*" -delete
@@ -123,9 +131,17 @@ RUN . /env-cargo && \
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
# https://github.com/dani-garcia/vaultwarden/discussions/7522 # https://github.com/dani-garcia/vaultwarden/discussions/7522
if xx-info is-cross; then \ if xx-info is-cross; then \
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
export XX_RUSTFLAGS; \
fi && \ fi && \
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
if [ "$(xx-info arch)" = "arm64" ]; then \
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
fi && \
export XX_RUSTFLAGS && \
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \ PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" && \
if [ "${CARGO_PROFILE}" = "dev" ] ; then \ if [ "${CARGO_PROFILE}" = "dev" ] ; then \
ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \ ln -vfsr "/app/target/${CARGO_TARGET}/debug" /app/target/final ; \
+10 -2
View File
@@ -32,9 +32,17 @@
# https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977 # https://github.com/tonistiigi/xx/pull/108#issuecomment-3700635977
# https://github.com/dani-garcia/vaultwarden/discussions/7522 # https://github.com/dani-garcia/vaultwarden/discussions/7522
if xx-info is-cross; then \ if xx-info is-cross; then \
XX_RUSTFLAGS="-C link-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \ XX_RUSTFLAGS="-Clink-arg=-Wl,-rpath-link,/usr/lib/$(xx-info triple)"; \
export XX_RUSTFLAGS; \
fi && \ fi && \
# Debian's GCC adds a special fix for cortex-a53 arch's which gets losts when using xx-cargo, which uses clang
# Checking if the arch is arm64 and adding this extra link-arg should cause this fix to be applied
# This should fix issues reported when running on a RPi3 which is a Cortex-A35 chip
# See: https://salsa.debian.org/toolchain-team/gcc/-/blob/master/debian/rules2?pow_referer=#L543-548
# See: https://github.com/dani-garcia/vaultwarden/issues/7754
if [ "$(xx-info arch)" = "arm64" ]; then \
XX_RUSTFLAGS="${XX_RUSTFLAGS} -Clink-arg=-Wl,--fix-cortex-a53-843419"; \
fi && \
export XX_RUSTFLAGS && \
PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}" PKG_CONFIG="$(command -v "$(xx-info)-pkg-config")" xx-cargo build --features ${DB} --profile "${CARGO_PROFILE}"
{%- endmacro %} {%- endmacro %}
FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} AS vault FROM --platform=linux/amd64 docker.io/vaultwarden/web-vault@{{ vault_image_digest }} AS vault
+1 -1
View File
@@ -14,7 +14,7 @@ proc-macro = true
[dependencies] [dependencies]
quote = "1.0.47" quote = "1.0.47"
syn = "3.0.5" syn = "3.0.6"
[lints] [lints]
workspace = true workspace = true
Whitespace-only changes.
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN key_id TEXT;
Whitespace-only changes.
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN key_id TEXT;
Whitespace-only changes.
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN key_id TEXT;
Whitespace-only changes.
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN key_id TEXT;
+13 -13
View File
@@ -905,23 +905,23 @@ mod tests {
fn validate_web_vault_compare() { fn validate_web_vault_compare() {
// web_vault_compare(active, latest) // web_vault_compare(active, latest)
// Test normal versions // Test normal versions
assert!(web_vault_compare("2025.12.0", "2025.12.1") == -1); assert_eq!(web_vault_compare("2025.12.0", "2025.12.1"), -1);
assert!(web_vault_compare("2025.12.1", "2025.12.1") == 0); assert_eq!(web_vault_compare("2025.12.1", "2025.12.1"), 0);
assert!(web_vault_compare("2025.12.2", "2025.12.1") == 1); assert_eq!(web_vault_compare("2025.12.2", "2025.12.1"), 1);
// Test patched/+build.n versions // Test patched/+build.n versions
// Newer latest version // Newer latest version
assert!(web_vault_compare("2025.12.0+build.1", "2025.12.1") == -1); assert_eq!(web_vault_compare("2025.12.0+build.1", "2025.12.1"), -1);
assert!(web_vault_compare("2025.12.1", "2025.12.1+build.1") == -1); assert_eq!(web_vault_compare("2025.12.1", "2025.12.1+build.1"), -1);
assert!(web_vault_compare("2025.12.0+build.1", "2025.12.1+build.1") == -1); assert_eq!(web_vault_compare("2025.12.0+build.1", "2025.12.1+build.1"), -1);
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.2") == -1); assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.2"), -1);
// Equal versions // Equal versions
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.1") == 0); assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1+build.1"), 0);
assert!(web_vault_compare("2025.12.2+build.2", "2025.12.2+build.2") == 0); assert_eq!(web_vault_compare("2025.12.2+build.2", "2025.12.2+build.2"), 0);
// Newer active version // Newer active version
assert!(web_vault_compare("2025.12.1+build.1", "2025.12.1") == 1); assert_eq!(web_vault_compare("2025.12.1+build.1", "2025.12.1"), 1);
assert!(web_vault_compare("2025.12.2", "2025.12.1+build.1") == 1); assert_eq!(web_vault_compare("2025.12.2", "2025.12.1+build.1"), 1);
assert!(web_vault_compare("2025.12.2+build.1", "2025.12.1+build.1") == 1); assert_eq!(web_vault_compare("2025.12.2+build.1", "2025.12.1+build.1"), 1);
assert!(web_vault_compare("2025.12.1+build.3", "2025.12.1+build.2") == 1); assert_eq!(web_vault_compare("2025.12.1+build.3", "2025.12.1+build.2"), 1);
} }
} }
+21 -2
View File
@@ -21,8 +21,9 @@ use crate::{
DbConn, DbPool, DbConn, DbPool,
models::{ models::{
AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest, AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest,
EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, Membership, MembershipId, EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, KeyId, Membership,
OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId, UserKdfType, MembershipId, OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId,
UserKdfType,
}, },
}, },
mail, mail,
@@ -46,6 +47,7 @@ pub fn routes() -> Vec<rocket::Route> {
post_set_password, post_set_password,
post_kdf, post_kdf,
post_rotatekey, post_rotatekey,
post_user_key,
post_sstamp, post_sstamp,
post_email_token, post_email_token,
post_email, post_email,
@@ -1025,6 +1027,23 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, conn: DbConn, nt:
save_result save_result
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct KeyIdData {
user_key_id: KeyId,
}
#[post("/accounts/key-management/user-key-id", data = "<data>")]
async fn post_user_key(data: Json<KeyIdData>, headers: Headers, conn: DbConn) -> EmptyResult {
let mut user = headers.user;
if user.key_id.is_some() {
err_code!("Unexpected data", Status::UnprocessableEntity.code);
}
user.key_id = Some(data.into_inner().user_key_id);
user.save(&conn).await
}
#[post("/accounts/security-stamp", data = "<data>")] #[post("/accounts/security-stamp", data = "<data>")]
async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult { async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner(); let data: PasswordOrOtpData = data.into_inner();
+47 -10
View File
@@ -6,6 +6,7 @@ use rocket::{
Route, Route,
form::{Form, FromForm}, form::{Form, FromForm},
fs::TempFile, fs::TempFile,
http::Status,
serde::json::Json, serde::json::Json,
}; };
use serde_json::Value; use serde_json::Value;
@@ -21,8 +22,8 @@ use crate::{
DbConn, DbPool, DbConn, DbPool,
models::{ models::{
Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup, Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup,
CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, Membership, CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, KeyId,
MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId, Membership, MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId,
}, },
}, },
util::{NumberOrString, deser_opt_nonempty_str, save_temp_file}, util::{NumberOrString, deser_opt_nonempty_str, save_temp_file},
@@ -161,6 +162,12 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
let policies_json: Vec<Value> = let policies_json: Vec<Value> =
OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &conn).await.iter().map(OrgPolicy::to_json).collect(); OrgPolicy::find_confirmed_by_user(&headers.user.uuid, &conn).await.iter().map(OrgPolicy::to_json).collect();
let policies_new_json: Vec<Value> = OrgPolicy::find_accepted_and_confirmed_by_user(&headers.user.uuid, &conn)
.await
.iter()
.map(OrgPolicy::to_json)
.collect();
let domains_json = if data.exclude_domains { let domains_json = if data.exclude_domains {
Value::Null Value::Null
} else { } else {
@@ -193,11 +200,13 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
"folders": folders_json, "folders": folders_json,
"collections": collections_json, "collections": collections_json,
"policies": policies_json, "policies": policies_json,
"policiesNew": policies_new_json,
"ciphers": ciphers_json, "ciphers": ciphers_json,
"domains": domains_json, "domains": domains_json,
"sends": sends_json, "sends": sends_json,
"userDecryption": { "userDecryption": {
"masterPasswordUnlock": master_password_unlock, "masterPasswordUnlock": master_password_unlock,
"userKeyId": headers.user.key_id,
}, },
"object": "sync" "object": "sync"
}))) })))
@@ -260,12 +269,19 @@ pub struct CipherData {
key: Option<String>, key: Option<String>,
pub encrypted_for: UserId, // Added in web-v2025.6.0
// Added in web-v2025.8.1, Optional for compat
pub encrypted_by_key_id: Option<KeyId>,
/* /*
Login = 1, Login = 1,
SecureNote = 2, SecureNote = 2,
Card = 3, Card = 3,
Identity = 4, Identity = 4,
SshKey = 5 SshKey = 5
BankAccount = 6
DriversLicense = 7
Passport = 8
*/ */
pub r#type: i32, pub r#type: i32,
pub name: String, pub name: String,
@@ -278,6 +294,9 @@ pub struct CipherData {
card: Option<Value>, card: Option<Value>,
identity: Option<Value>, identity: Option<Value>,
ssh_key: Option<Value>, ssh_key: Option<Value>,
bank_account: Option<Value>,
drivers_license: Option<Value>,
passport: Option<Value>,
favorite: Option<bool>, favorite: Option<bool>,
reprompt: Option<i32>, reprompt: Option<i32>,
@@ -333,6 +352,10 @@ async fn post_ciphers_create(
) -> JsonResult { ) -> JsonResult {
let mut data: ShareCipherData = data.into_inner(); let mut data: ShareCipherData = data.into_inner();
if data.cipher.encrypted_for != headers.user.uuid {
err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
}
// This check is usually only needed in update_cipher_from_data(), but we // This check is usually only needed in update_cipher_from_data(), but we
// need it here as well to avoid creating an empty cipher in the call to // need it here as well to avoid creating an empty cipher in the call to
// cipher.save() below. // cipher.save() below.
@@ -362,6 +385,17 @@ async fn post_ciphers_create(
async fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { async fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
let mut data: CipherData = data.into_inner(); let mut data: CipherData = data.into_inner();
if data.encrypted_for != headers.user.uuid {
err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
}
if let Some(cipher_key_id) = &data.encrypted_by_key_id
&& let Some(user_key_id) = &headers.user.key_id
&& cipher_key_id != user_key_id
{
err_code!("Invalid key cipher", Status::UnprocessableEntity.code);
}
// The web/browser clients set this field to null as expected, but the // The web/browser clients set this field to null as expected, but the
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`, // mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
// which results in a warning message being logged. This field isn't // which results in a warning message being logged. This field isn't
@@ -510,6 +544,9 @@ pub async fn update_cipher_from_data(
3 => data.card, 3 => data.card,
4 => data.identity, 4 => data.identity,
5 => data.ssh_key, 5 => data.ssh_key,
6 => data.bank_account,
7 => data.drivers_license,
8 => data.passport,
_ => err!("Invalid type"), _ => err!("Invalid type"),
}; };
@@ -2143,8 +2180,15 @@ impl CipherSyncData {
} }
} }
// Generate a HashMap with the Organization UUID as key and the Membership record
let members: HashMap<OrganizationId, Membership> = Membership::find_confirmed_by_user(user_id, conn)
.await
.into_iter()
.map(|m| (m.org_uuid.clone(), m))
.collect();
// Generate a list of Cipher UUID's containing a Vec with one or more Attachment records // Generate a list of Cipher UUID's containing a Vec with one or more Attachment records
let orgs = Membership::get_orgs_by_user(user_id, conn).await; let orgs: Vec<OrganizationId> = members.keys().cloned().collect();
let attachments = Attachment::find_all_by_user_and_orgs(user_id, &orgs, conn).await; let attachments = Attachment::find_all_by_user_and_orgs(user_id, &orgs, conn).await;
let mut cipher_attachments: HashMap<CipherId, Vec<Attachment>> = HashMap::with_capacity(attachments.len()); let mut cipher_attachments: HashMap<CipherId, Vec<Attachment>> = HashMap::with_capacity(attachments.len());
for attachment in attachments { for attachment in attachments {
@@ -2159,13 +2203,6 @@ impl CipherSyncData {
cipher_collections.entry(cipher).or_default().push(collection); cipher_collections.entry(cipher).or_default().push(collection);
} }
// Generate a HashMap with the Organization UUID as key and the Membership record
let members: HashMap<OrganizationId, Membership> = Membership::find_confirmed_by_user(user_id, conn)
.await
.into_iter()
.map(|m| (m.org_uuid.clone(), m))
.collect();
// Generate a HashMap with the User_Collections UUID as key and the CollectionUser record // Generate a HashMap with the User_Collections UUID as key and the CollectionUser record
let user_collections: HashMap<CollectionId, CollectionUser> = CollectionUser::find_by_user(user_id, conn) let user_collections: HashMap<CollectionId, CollectionUser> = CollectionUser::find_by_user(user_id, conn)
.await .await
+20
View File
@@ -199,6 +199,26 @@ async fn post_events_collect(data: Json<Vec<EventCollection>>, headers: Headers,
.await; .await;
} }
} }
// Only the vault notification banner click is accepted from clients. The rest of
// the 1500..=1599 range is written server-side and must not be forgeable by a client.
t if t == EventType::OrganizationUserNotificationBannerActionClicked as i32 => {
if let Some(org_id) = &event.organization_id
&& let Some(membership) =
Membership::find_confirmed_by_user_and_org(&headers.user.uuid, org_id, &conn).await
{
log_event_impl(
event.r#type,
&membership.uuid,
org_id,
&headers.user.uuid,
headers.device.atype,
Some(event_date),
&headers.ip.ip,
&conn,
)
.await;
}
}
_ => { _ => {
// The cipher determines the organization the event is logged to, so make sure the // The cipher determines the organization the event is logged to, so make sure the
// user can actually access it instead of trusting the provided cipher uuid. // user can actually access it instead of trusting the provided cipher uuid.
+16 -9
View File
@@ -415,8 +415,9 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
let col_users = CollectionUser::find_by_organization_swap_user_uuid_with_member_uuid(&org_id, &conn).await; let col_users = CollectionUser::find_by_organization_swap_user_uuid_with_member_uuid(&org_id, &conn).await;
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission // Generate a HashMap to get the correct MembershipType per user to determine the manage permission
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser // We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
// This lists other members for admins, so it must not depend on the membership status
let membership_type: HashMap<MembershipId, i32> = let membership_type: HashMap<MembershipId, i32> =
Membership::find_confirmed_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect(); Membership::find_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
// check if current user has full access to the organization (either directly or via any group) // check if current user has full access to the organization (either directly or via any group)
let has_full_access_to_org = member.has_full_access() let has_full_access_to_org = member.has_full_access()
@@ -818,11 +819,9 @@ async fn get_org_collection_detail(
// Generate a HashMap to get the correct MembershipType per user to determine the manage permission // Generate a HashMap to get the correct MembershipType per user to determine the manage permission
// We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser // We use the uuid instead of the user_uuid here, since that is what is used in CollectionUser
let membership_type: HashMap<MembershipId, i32> = Membership::find_confirmed_by_org(&org_id, &conn) // This lists other members for admins, so it must not depend on the membership status
.await let membership_type: HashMap<MembershipId, i32> =
.into_iter() Membership::find_by_org(&org_id, &conn).await.into_iter().map(|m| (m.uuid, m.atype)).collect();
.map(|m| (m.uuid, m.atype))
.collect();
let users: Vec<Value> = let users: Vec<Value> =
CollectionUser::find_by_org_and_coll_swap_user_uuid_with_member_uuid(&org_id, &collection.uuid, &conn) CollectionUser::find_by_org_and_coll_swap_user_uuid_with_member_uuid(&org_id, &collection.uuid, &conn)
@@ -960,8 +959,9 @@ async fn get_members(
let mut users_json = Vec::new(); let mut users_json = Vec::new();
for u in Membership::find_by_org(&org_id, &conn).await { for u in Membership::find_by_org(&org_id, &conn).await {
// The user can be a manager instead of an admin, but we've checked above that they have full access
users_json.push( users_json.push(
u.to_json_user_details( u.to_json_details_for_admin(
data.include_collections.unwrap_or(false), data.include_collections.unwrap_or(false),
data.include_groups.unwrap_or(false), data.include_groups.unwrap_or(false),
&conn, &conn,
@@ -1075,7 +1075,7 @@ async fn send_invite(
&& data.permissions.get("deleteAnyCollection") == Some(&json!(true)) && data.permissions.get("deleteAnyCollection") == Some(&json!(true))
&& data.permissions.get("createNewCollections") == Some(&json!(true))); && data.permissions.get("createNewCollections") == Some(&json!(true)));
let mut user_created: bool = false; let mut user_created: bool;
for email in &data.emails { for email in &data.emails {
let mut member_status = MembershipStatus::Invited as i32; let mut member_status = MembershipStatus::Invited as i32;
let user = match User::find_by_mail(email, &conn).await { let user = match User::find_by_mail(email, &conn).await {
@@ -1110,6 +1110,7 @@ async fn send_invite(
member_status = MembershipStatus::Accepted as i32; member_status = MembershipStatus::Accepted as i32;
} }
} }
user_created = false;
user user
} }
}; };
@@ -1515,7 +1516,9 @@ async fn get_user(
// In this case, when groups are requested we also need to include collections. // In this case, when groups are requested we also need to include collections.
// Else these will not be shown in the interface, and could lead to missing collections when saved. // Else these will not be shown in the interface, and could lead to missing collections when saved.
let include_groups = data.include_groups.unwrap_or(false); let include_groups = data.include_groups.unwrap_or(false);
Ok(Json(user.to_json_user_details(data.include_collections.unwrap_or(include_groups), include_groups, &conn).await)) Ok(Json(
user.to_json_details_for_admin(data.include_collections.unwrap_or(include_groups), include_groups, &conn).await,
))
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -1834,6 +1837,10 @@ async fn post_org_import(
if org_id != headers.membership.org_uuid { if org_id != headers.membership.org_uuid {
err!("Organization not found", "Organization id's do not match"); err!("Organization not found", "Organization id's do not match");
} }
// OrgMemberHeaders also allows invited and accepted members, which are not allowed to import
if headers.membership.status != MembershipStatus::Confirmed as i32 {
err!("You need to be a Member of the Organization to call this endpoint")
}
let data: ImportData = data.into_inner(); let data: ImportData = data.into_inner();
// Validate the import before continuing // Validate the import before continuing
+1
View File
@@ -89,6 +89,7 @@ fn vaultwarden_css() -> EtagCached<Css<String>> {
// Else, there is either no cache, or reload_templates is true and we need to rebuild the CSS // Else, there is either no cache, or reload_templates is true and we need to rebuild the CSS
let css_options = json!({ let css_options = json!({
"email_change_allowed": CONFIG.email_change_allowed(),
"emergency_access_allowed": CONFIG.emergency_access_allowed(), "emergency_access_allowed": CONFIG.emergency_access_allowed(),
"load_user_scss": true, "load_user_scss": true,
"mail_2fa_enabled": CONFIG._enable_email_2fa(), "mail_2fa_enabled": CONFIG._enable_email_2fa(),
+6
View File
@@ -1427,20 +1427,26 @@ pub const SUPPORTED_FEATURE_FLAGS: &[&str] = &[
"pm-5594-safari-account-switching", "pm-5594-safari-account-switching",
"pm-32413-multi-client-password-management", "pm-32413-multi-client-password-management",
// Autofill Team // Autofill Team
"undetermined-cipher-scenario-logic",
"enable-basic-auth-response",
"ssh-agent", "ssh-agent",
"ssh-agent-v2", "ssh-agent-v2",
// Key Management Team // Key Management Team
"ssh-key-vault-item", "ssh-key-vault-item",
"pm-25373-windows-biometrics-v2", "pm-25373-windows-biometrics-v2",
"pm-26340-linux-biometrics-v2", "pm-26340-linux-biometrics-v2",
"windows-native-credential-sync",
// Mobile Team // Mobile Team
"anon-addy-self-host-alias", "anon-addy-self-host-alias",
"simple-login-self-host-alias", "simple-login-self-host-alias",
"mutual-tls", "mutual-tls",
"cxp-import-mobile", "cxp-import-mobile",
"cxp-export-mobile", "cxp-export-mobile",
"pm-34171-card-scanner",
// Platform Team // Platform Team
"pm-30529-webauthn-related-origins", "pm-30529-webauthn-related-origins",
// Vault Team
"pm-32009-new-item-types",
]; ];
impl Config { impl Config {
+20 -1
View File
@@ -657,16 +657,27 @@ impl Cipher {
} }
async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &DbConn) -> Vec<(bool, bool, bool)> { async fn get_user_collections_access_flags(&self, user_uuid: &UserId, conn: &DbConn) -> Vec<(bool, bool, bool)> {
let user_uuid = user_uuid.to_string();
conn.run(move |conn| { conn.run(move |conn| {
// Check whether this cipher is in any collections accessible to the // Check whether this cipher is in any collections accessible to the
// user. If so, retrieve the access flags for each collection. // user. If so, retrieve the access flags for each collection.
// The user must have a confirmed membership in the cipher's
// organization, since users_collections rows are kept when a
// membership is revoked or not confirmed yet.
ciphers::table ciphers::table
.filter(ciphers::uuid.eq(&self.uuid)) .filter(ciphers::uuid.eq(&self.uuid))
.inner_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid))) .inner_join(ciphers_collections::table.on(ciphers::uuid.eq(ciphers_collections::cipher_uuid)))
.inner_join(
users_organizations::table.on(ciphers::organization_uuid
.eq(users_organizations::org_uuid.nullable())
.and(users_organizations::user_uuid.eq(user_uuid))
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
)
.inner_join( .inner_join(
users_collections::table.on(ciphers_collections::collection_uuid users_collections::table.on(ciphers_collections::collection_uuid
.eq(users_collections::collection_uuid) .eq(users_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_uuid))), // Only allow collection access via the confirmed membership.
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))),
) )
.select((users_collections::read_only, users_collections::hide_passwords, users_collections::manage)) .select((users_collections::read_only, users_collections::hide_passwords, users_collections::manage))
.load::<(bool, bool, bool)>(conn) .load::<(bool, bool, bool)>(conn)
@@ -697,6 +708,9 @@ impl Cipher {
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))), .and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
) )
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
// Only allow group access via a confirmed membership, since
// groups_users rows are kept when a membership is revoked.
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.select((collections_groups::read_only, collections_groups::hide_passwords, collections_groups::manage)) .select((collections_groups::read_only, collections_groups::hide_passwords, collections_groups::manage))
.load::<(bool, bool, bool)>(conn) .load::<(bool, bool, bool)>(conn)
.expect("Error getting group access restrictions") .expect("Error getting group access restrictions")
@@ -989,6 +1003,7 @@ impl Cipher {
.eq(ciphers_collections::collection_uuid) .eq(ciphers_collections::collection_uuid)
.and(collections_groups::groups_uuid.eq(groups::uuid))), .and(collections_groups::groups_uuid.eq(groups::uuid))),
) )
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_organizations::access_all users_organizations::access_all
.eq(true) // User has access all .eq(true) // User has access all
@@ -1020,6 +1035,7 @@ impl Cipher {
.eq(ciphers_collections::collection_uuid) .eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_uuid.clone()))), .and(users_collections::user_uuid.eq(user_uuid.clone()))),
) )
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_organizations::access_all users_organizations::access_all
.eq(true) // User has access all .eq(true) // User has access all
@@ -1064,6 +1080,7 @@ impl Cipher {
.eq(ciphers_collections::collection_uuid) .eq(ciphers_collections::collection_uuid)
.and(collections_groups::groups_uuid.eq(groups::uuid))), .and(collections_groups::groups_uuid.eq(groups::uuid))),
) )
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_organizations::access_all users_organizations::access_all
.eq(true) // User has access all .eq(true) // User has access all
@@ -1096,6 +1113,7 @@ impl Cipher {
.eq(ciphers_collections::collection_uuid) .eq(ciphers_collections::collection_uuid)
.and(users_collections::user_uuid.eq(user_uuid.clone()))), .and(users_collections::user_uuid.eq(user_uuid.clone()))),
) )
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_organizations::access_all users_organizations::access_all
.eq(true) // User has access all .eq(true) // User has access all
@@ -1147,6 +1165,7 @@ impl Cipher {
.or_filter(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner .or_filter(users_organizations::atype.le(MembershipType::Admin as i32)) // User is admin or owner
.or_filter(groups::access_all.eq(true)) //Access via group .or_filter(groups::access_all.eq(true)) //Access via group
.or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group .or_filter(collections_groups::collections_uuid.is_not_null()) //Access via group
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.select(ciphers_collections::all_columns) .select(ciphers_collections::all_columns)
.distinct() .distinct()
.load::<(CipherId, CollectionId)>(conn) .load::<(CipherId, CollectionId)>(conn)
+17 -2
View File
@@ -367,6 +367,7 @@ impl Collection {
.and(collections_groups::collections_uuid.eq(collections::uuid))), .and(collections_groups::collections_uuid.eq(collections::uuid))),
) )
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_collections::collection_uuid users_collections::collection_uuid
.eq(uuid) .eq(uuid)
@@ -406,6 +407,7 @@ impl Collection {
.and(users_organizations::user_uuid.eq(user_uuid))), .and(users_organizations::user_uuid.eq(user_uuid))),
) )
.filter(collections::uuid.eq(uuid)) .filter(collections::uuid.eq(uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter(users_collections::collection_uuid.eq(uuid).or( .filter(users_collections::collection_uuid.eq(uuid).or(
// Directly accessed collection // Directly accessed collection
users_organizations::access_all.eq(true).or( users_organizations::access_all.eq(true).or(
@@ -430,7 +432,8 @@ impl Collection {
.inner_join( .inner_join(
users_organizations::table.on(collections::org_uuid users_organizations::table.on(collections::org_uuid
.eq(users_organizations::org_uuid) .eq(users_organizations::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid.clone()))), .and(users_organizations::user_uuid.eq(user_uuid.clone()))
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
) )
.left_join( .left_join(
users_collections::table.on(users_collections::collection_uuid users_collections::table.on(users_collections::collection_uuid
@@ -476,7 +479,8 @@ impl Collection {
.inner_join( .inner_join(
users_organizations::table.on(collections::org_uuid users_organizations::table.on(collections::org_uuid
.eq(users_organizations::org_uuid) .eq(users_organizations::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid.clone()))), .and(users_organizations::user_uuid.eq(user_uuid.clone()))
.and(users_organizations::status.eq(MembershipStatus::Confirmed as i32))),
) )
.left_join( .left_join(
users_collections::table.on(users_collections::collection_uuid users_collections::table.on(users_collections::collection_uuid
@@ -527,6 +531,7 @@ impl Collection {
.and(collections_groups::collections_uuid.eq(collections::uuid))), .and(collections_groups::collections_uuid.eq(collections::uuid))),
) )
.filter(collections::uuid.eq(&self.uuid)) .filter(collections::uuid.eq(&self.uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_collections::collection_uuid users_collections::collection_uuid
.eq(&self.uuid) .eq(&self.uuid)
@@ -586,6 +591,7 @@ impl Collection {
.and(collections_groups::collections_uuid.eq(collections::uuid))), .and(collections_groups::collections_uuid.eq(collections::uuid))),
) )
.filter(collections::uuid.eq(&uuid)) .filter(collections::uuid.eq(&uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_collections::collection_uuid users_collections::collection_uuid
.eq(&uuid) .eq(&uuid)
@@ -650,6 +656,7 @@ impl Collection {
.and(collections_groups::collections_uuid.eq(collections::uuid))), .and(collections_groups::collections_uuid.eq(collections::uuid))),
) )
.filter(collections::org_uuid.eq(&org_uuid)) .filter(collections::org_uuid.eq(&org_uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
// Manage permission on a collection assigned directly or via a group. // Manage permission on a collection assigned directly or via a group.
users_collections::manage.eq(true).or(collections_groups::manage.eq(true)), users_collections::manage.eq(true).or(collections_groups::manage.eq(true)),
@@ -819,10 +826,18 @@ impl CollectionUser {
.await .await
} }
// Only returns the collections of organizations the user is a confirmed member of
pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> { pub async fn find_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
conn.run(move |conn| { conn.run(move |conn| {
users_collections::table users_collections::table
.inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid)))
.inner_join(
users_organizations::table.on(users_organizations::org_uuid
.eq(collections::org_uuid)
.and(users_organizations::user_uuid.eq(users_collections::user_uuid))),
)
.filter(users_collections::user_uuid.eq(user_uuid)) .filter(users_collections::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.select(users_collections::all_columns) .select(users_collections::all_columns)
.load::<Self>(conn) .load::<Self>(conn)
.expect("Error loading users_collections") .expect("Error loading users_collections")
+1 -1
View File
@@ -113,7 +113,7 @@ pub enum EventType {
OrganizationUserAdminResetTwoFactor = 1519, OrganizationUserAdminResetTwoFactor = 1519,
// OrganizationUserRevoked_TwoFactorNonCompliance = 1520, // OrganizationUserRevoked_TwoFactorNonCompliance = 1520,
// OrganizationUserRevoked_SingleOrganizationNonCompliance = 1521, // OrganizationUserRevoked_SingleOrganizationNonCompliance = 1521,
// OrganizationUserNotificationBannerActionClicked = 1522, OrganizationUserNotificationBannerActionClicked = 1522,
// Organization // Organization
OrganizationUpdated = 1600, OrganizationUpdated = 1600,
+6 -1
View File
@@ -13,7 +13,7 @@ use crate::{
}; };
use macros::UuidFromParam; use macros::UuidFromParam;
use super::{CollectionId, Membership, MembershipId, OrganizationId, User, UserId}; use super::{CollectionId, Membership, MembershipId, MembershipStatus, OrganizationId, User, UserId};
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
#[diesel(table_name = groups)] #[diesel(table_name = groups)]
@@ -249,6 +249,7 @@ impl Group {
.and(groups::organizations_uuid.eq(users_organizations::org_uuid))), .and(groups::organizations_uuid.eq(users_organizations::org_uuid))),
) )
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter(groups::access_all.eq(true)) .filter(groups::access_all.eq(true))
.select(groups::organizations_uuid) .select(groups::organizations_uuid)
.distinct() .distinct()
@@ -268,6 +269,9 @@ impl Group {
.and(users_organizations::org_uuid.eq(groups::organizations_uuid))), .and(users_organizations::org_uuid.eq(groups::organizations_uuid))),
) )
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
// Only allow full access via a confirmed membership, since
// groups_users rows are kept when a membership is revoked.
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter(groups::organizations_uuid.eq(org_uuid)) .filter(groups::organizations_uuid.eq(org_uuid))
.filter(groups::access_all.eq(true)) .filter(groups::access_all.eq(true))
.select(groups::access_all) .select(groups::access_all)
@@ -384,6 +388,7 @@ impl CollectionGroup {
.and(collections::org_uuid.eq(groups::organizations_uuid))), .and(collections::org_uuid.eq(groups::organizations_uuid))),
) )
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.select(collections_groups::all_columns) .select(collections_groups::all_columns)
.load::<Self>(conn) .load::<Self>(conn)
.expect("Error loading user collection groups") .expect("Error loading user collection groups")
+1 -1
View File
@@ -39,4 +39,4 @@ pub use self::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth};
pub use self::two_factor::{TwoFactor, TwoFactorType}; pub use self::two_factor::{TwoFactor, TwoFactorType};
pub use self::two_factor_duo_context::TwoFactorDuoContext; pub use self::two_factor_duo_context::TwoFactorDuoContext;
pub use self::two_factor_incomplete::TwoFactorIncomplete; pub use self::two_factor_incomplete::TwoFactorIncomplete;
pub use self::user::{Invitation, SsoUser, User, UserId, UserKdfType, UserStampException}; pub use self::user::{Invitation, KeyId, SsoUser, User, UserId, UserKdfType, UserStampException};
+21
View File
@@ -49,6 +49,7 @@ pub enum OrgPolicyType {
// AutotypeDefaultSetting = 17, // Not supported yet // AutotypeDefaultSetting = 17, // Not supported yet
// AutoConfirm = 18, // Not supported (not implemented yet) // AutoConfirm = 18, // Not supported (not implemented yet)
// BlockClaimedDomainAccountCreation = 19, // Not supported (Not AGPLv3 Licensed) // BlockClaimedDomainAccountCreation = 19, // Not supported (Not AGPLv3 Licensed)
OrganizationUserNotification = 20,
} }
// https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5 // https://github.com/bitwarden/server/blob/9ebe16587175b1c0e9208f84397bb75d0d595510/src/Core/AdminConsole/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs#L5
@@ -186,6 +187,26 @@ impl OrgPolicy {
.await .await
} }
pub async fn find_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
conn.run(move |conn| {
org_policies::table
.inner_join(
users_organizations::table.on(users_organizations::org_uuid
.eq(org_policies::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid))),
)
.filter(
users_organizations::status
.eq(MembershipStatus::Accepted as i32)
.or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)),
)
.select(org_policies::all_columns)
.load::<Self>(conn)
.expect("Error loading org_policy")
})
.await
}
pub async fn find_by_org_and_type( pub async fn find_by_org_and_type(
org_uuid: &OrganizationId, org_uuid: &OrganizationId,
policy_type: OrgPolicyType, policy_type: OrgPolicyType,
+37 -58
View File
@@ -1,7 +1,4 @@
use std::{ use std::cmp::Ordering;
cmp::Ordering,
collections::{HashMap, HashSet},
};
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use derive_more::{AsRef, Deref, Display, From}; use derive_more::{AsRef, Deref, Display, From};
@@ -24,8 +21,8 @@ use crate::{
use macros::UuidFromParam; use macros::UuidFromParam;
use super::{ use super::{
Cipher, CipherId, Collection, CollectionGroup, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy, Cipher, CipherId, Collection, CollectionId, CollectionUser, Group, GroupId, GroupUser, OrgPolicy, OrgPolicyType,
OrgPolicyType, TwoFactor, User, UserId, TwoFactor, User, UserId,
}; };
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@@ -536,7 +533,13 @@ impl Membership {
}) })
} }
pub async fn to_json_user_details(&self, include_collections: bool, include_groups: bool, conn: &DbConn) -> Value { // Used by admins to view other members, so nothing here may depend on this member's status
pub async fn to_json_details_for_admin(
&self,
include_collections: bool,
include_groups: bool,
conn: &DbConn,
) -> Value {
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap(); let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
// Because BitWarden want the status to be -1 for revoked users we need to catch that here. // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
@@ -559,52 +562,23 @@ impl Membership {
// Check if a user is in a group which has access to all collections // Check if a user is in a group which has access to all collections
// If that is the case, we should not return individual collections! // If that is the case, we should not return individual collections!
// This is used by admins to view other members, so it must not depend on the membership status
let full_access_group = let full_access_group =
CONFIG.org_groups_enabled() && Group::is_in_full_access_group(&self.user_uuid, &self.org_uuid, conn).await; CONFIG.org_groups_enabled() && GroupUser::has_full_access_by_member(&self.org_uuid, &self.uuid, conn).await;
// If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self // If collections are to be included, only include them if the user does not have full access via a group or defined to the user it self
// Only the collections assigned directly are returned, the ones assigned via a group are returned via a special group endpoint
let collections: Vec<Value> = if include_collections && !(full_access_group || self.access_all) { let collections: Vec<Value> = if include_collections && !(full_access_group || self.access_all) {
// Get all collections for the user here already to prevent more queries CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
let cu: HashMap<CollectionId, CollectionUser> =
CollectionUser::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
.await
.into_iter()
.map(|cu| (cu.collection_uuid.clone(), cu))
.collect();
// Get all collection groups for this user to prevent there inclusion
let cg: HashSet<CollectionId> = CollectionGroup::find_by_user(&self.user_uuid, conn)
.await .await
.into_iter() .into_iter()
.map(|cg| cg.collections_uuid) .map(|cu| {
.collect(); json!({
"id": cu.collection_uuid,
Collection::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn) "readOnly": cu.read_only,
.await "hidePasswords": cu.hide_passwords,
.into_iter() "manage": cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords),
.filter_map(|c| { })
let (read_only, hide_passwords, manage) = if self.has_full_access() {
(false, false, self.atype >= MembershipType::Manager)
} else if let Some(cu) = cu.get(&c.uuid) {
(
cu.read_only,
cu.hide_passwords,
cu.manage || (self.atype == MembershipType::Manager && !cu.read_only && !cu.hide_passwords),
)
// If previous checks failed it might be that this user has access via a group, but we should not return those elements here
// Those are returned via a special group endpoint
} else if cg.contains(&c.uuid) {
return None;
} else {
(true, true, false)
};
Some(json!({
"id": c.uuid,
"readOnly": read_only,
"hidePasswords": hide_passwords,
"manage": manage,
}))
}) })
.collect() .collect()
} else { } else {
@@ -844,6 +818,21 @@ impl Membership {
.await .await
} }
pub async fn find_accepted_and_confirmed_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
conn.run(move |conn| {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(
users_organizations::status
.eq(MembershipStatus::Accepted as i32)
.or(users_organizations::status.eq(MembershipStatus::Confirmed as i32)),
)
.load::<Self>(conn)
.unwrap_or_default()
})
.await
}
pub async fn find_invited_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> { pub async fn find_invited_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<Self> {
conn.run(move |conn| { conn.run(move |conn| {
users_organizations::table users_organizations::table
@@ -1017,17 +1006,6 @@ impl Membership {
.await .await
} }
pub async fn get_orgs_by_user(user_uuid: &UserId, conn: &DbConn) -> Vec<OrganizationId> {
conn.run(move |conn| {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.select(users_organizations::org_uuid)
.load::<OrganizationId>(conn)
.unwrap_or_default()
})
.await
}
pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> { pub async fn find_by_user_and_policy(user_uuid: &UserId, policy_type: OrgPolicyType, conn: &DbConn) -> Vec<Self> {
conn.run(move |conn| { conn.run(move |conn| {
users_organizations::table users_organizations::table
@@ -1111,6 +1089,7 @@ impl Membership {
.and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))), .and(ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()))),
) )
.filter(users_organizations::user_uuid.eq(user_uuid)) .filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(MembershipStatus::Confirmed as i32))
.filter( .filter(
users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]), users_organizations::atype.eq_any(vec![MembershipType::Owner as i32, MembershipType::Admin as i32]),
) )
+30
View File
@@ -69,6 +69,8 @@ pub struct User {
pub avatar_color: Option<String>, pub avatar_color: Option<String>,
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore. pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
pub key_id: Option<KeyId>,
} }
#[derive(Identifiable, Queryable, Insertable)] #[derive(Identifiable, Queryable, Insertable)]
@@ -154,6 +156,8 @@ impl User {
avatar_color: None, avatar_color: None,
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore. external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
key_id: None,
} }
} }
@@ -259,6 +263,11 @@ impl User {
orgs_json.push(c.to_json(conn).await); orgs_json.push(c.to_json(conn).await);
} }
let mut orgs_new_json = Vec::new();
for c in Membership::find_accepted_and_confirmed_by_user(&self.uuid, conn).await {
orgs_new_json.push(c.to_json(conn).await);
}
let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).await.is_empty(); let twofactor_enabled = !TwoFactor::find_by_user(&self.uuid, conn).await.is_empty();
// TODO: Might want to save the status field in the DB // TODO: Might want to save the status field in the DB
@@ -299,6 +308,7 @@ impl User {
"privateKey": self.private_key, "privateKey": self.private_key,
"securityStamp": self.security_stamp, "securityStamp": self.security_stamp,
"organizations": orgs_json, "organizations": orgs_json,
"organizationsNew": orgs_new_json,
"providers": [], "providers": [],
"providerOrganizations": [], "providerOrganizations": [],
"forcePasswordReset": false, "forcePasswordReset": false,
@@ -527,6 +537,26 @@ impl Invitation {
#[from(forward)] #[from(forward)]
pub struct UserId(String); pub struct UserId(String);
#[derive(
Clone,
Debug,
DieselNewType,
FromForm,
PartialEq,
Eq,
Hash,
Serialize,
Deserialize,
AsRef,
Deref,
Display,
From,
UuidFromParam,
)]
#[deref(forward)]
#[from(forward)]
pub struct KeyId(String);
impl SsoUser { impl SsoUser {
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
db_run! { conn: db_run! { conn:
+1
View File
@@ -217,6 +217,7 @@ table! {
api_key -> Nullable<Text>, api_key -> Nullable<Text>,
avatar_color -> Nullable<Text>, avatar_color -> Nullable<Text>,
external_id -> Nullable<Text>, external_id -> Nullable<Text>,
key_id -> Nullable<Text>,
} }
} }
+9 -9
View File
@@ -466,7 +466,7 @@ mod tests {
#[test] #[test]
fn dotted_decimal_loopback_normalizes() { fn dotted_decimal_loopback_normalizes() {
let ip = parse_to_ip("127.0.0.1").unwrap(); let ip = parse_to_ip("127.0.0.1").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
@@ -474,21 +474,21 @@ mod tests {
fn single_decimal_loopback_normalizes() { fn single_decimal_loopback_normalizes() {
// 127.0.0.1 == 2130706433 // 127.0.0.1 == 2130706433
let ip = parse_to_ip("2130706433").unwrap(); let ip = parse_to_ip("2130706433").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
#[test] #[test]
fn hex_loopback_normalizes() { fn hex_loopback_normalizes() {
let ip = parse_to_ip("0x7f000001").unwrap(); let ip = parse_to_ip("0x7f000001").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
#[test] #[test]
fn dotted_hex_loopback_normalizes() { fn dotted_hex_loopback_normalizes() {
let ip = parse_to_ip("0x7f.0.0.1").unwrap(); let ip = parse_to_ip("0x7f.0.0.1").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
@@ -496,14 +496,14 @@ mod tests {
fn octal_loopback_normalizes() { fn octal_loopback_normalizes() {
// 017700000001 == 127.0.0.1 // 017700000001 == 127.0.0.1
let ip = parse_to_ip("017700000001").unwrap(); let ip = parse_to_ip("017700000001").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
#[test] #[test]
fn dotted_octal_loopback_normalizes() { fn dotted_octal_loopback_normalizes() {
let ip = parse_to_ip("0177.0.0.01").unwrap(); let ip = parse_to_ip("0177.0.0.01").unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))); assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
assert!(!is_global_hardcoded(ip)); assert!(!is_global_hardcoded(ip));
} }
@@ -535,19 +535,19 @@ mod tests {
#[test] #[test]
fn get_valid_host_normalizes_decimal_int() { fn get_valid_host_normalizes_decimal_int() {
let h = get_valid_host("2130706433").expect("valid"); let h = get_valid_host("2130706433").expect("valid");
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1))); assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
} }
#[test] #[test]
fn get_valid_host_normalizes_hex() { fn get_valid_host_normalizes_hex() {
let h = get_valid_host("0x7f000001").expect("valid"); let h = get_valid_host("0x7f000001").expect("valid");
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1))); assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
} }
#[test] #[test]
fn get_valid_host_normalizes_octal() { fn get_valid_host_normalizes_octal() {
let h = get_valid_host("017700000001").expect("valid"); let h = get_valid_host("017700000001").expect("valid");
assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::new(127, 0, 0, 1))); assert!(matches!(h, Host::Ipv4(ip) if ip == Ipv4Addr::LOCALHOST));
} }
// === // ===
+14 -2
View File
@@ -4,10 +4,10 @@
* *
* To rebuild or modify this file with the latest versions of the included * To rebuild or modify this file with the latest versions of the included
* software please visit: * software please visit:
* https://datatables.net/download/#bs5/dt-3.0.3 * https://datatables.net/download/#bs5/dt-3.0.4
* *
* Included libraries: * Included libraries:
* DataTables 3.0.3 * DataTables 3.0.4
*/ */
/*! DataTables Bootstrap 5 integration /*! DataTables Bootstrap 5 integration
@@ -636,6 +636,18 @@ table.dataTable.table-sm > thead > tr td.dt-type-date .dt-column-order,
table.dataTable.table-sm > thead > tr td.dt-type-numeric .dt-column-order { table.dataTable.table-sm > thead > tr td.dt-type-numeric .dt-column-order {
left: 0.25rem; left: 0.25rem;
} }
table.dataTable.table-sm > thead > tr th.dt-left .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-head-left .dt-column-order,
table.dataTable.table-sm > thead > tr td.dt-left .dt-column-order,
table.dataTable.table-sm > thead > tr td.dt-head-left .dt-column-order {
left: auto;
right: 0.25rem;
}
table.dataTable.table-sm > thead > tr th.dt-right .dt-column-order, table.dataTable.table-sm > thead > tr th.dt-head-right .dt-column-order,
table.dataTable.table-sm > thead > tr td.dt-right .dt-column-order,
table.dataTable.table-sm > thead > tr td.dt-head-right .dt-column-order {
left: 0.25rem;
right: auto;
}
div.dt-scroll-head table.table-bordered { div.dt-scroll-head table.table-bordered {
border-bottom-width: 0; border-bottom-width: 0;
+2250 -2240
View File
File diff suppressed because it is too large. Load diff
@@ -220,6 +220,13 @@ auth-input-password > form > bit-form-field:nth-child(4) {
@extend %vw-hide; @extend %vw-hide;
} }
{{/unless}} {{/unless}}
{{#unless email_change_allowed}}
/* Hide the whole `Change email` section, including its heading, if not allowed */
div:has(> app-change-email) {
@extend %vw-hide;
}
{{/unless}}
/**** End Dynamic Vaultwarden Changes ****/ /**** End Dynamic Vaultwarden Changes ****/
/**** Include a special user stylesheet for custom changes ****/ /**** Include a special user stylesheet for custom changes ****/
{{#if load_user_scss}} {{#if load_user_scss}}
+7 -7
View File
@@ -943,15 +943,15 @@ mod tests {
use std::net::IpAddr; use std::net::IpAddr;
#[test] #[test]
#[ignore] #[ignore = "exhaustive IPv4 check is too slow for the regular test suite"]
fn test_ipv4_global() { fn test_ipv4_global() {
for a in 0..u8::MAX { for a in 0..u8::MAX {
println!("Iter: {}/255", a); println!("Iter: {a}/255");
for b in 0..u8::MAX { for b in 0..u8::MAX {
for c in 0..u8::MAX { for c in 0..u8::MAX {
for d in 0..u8::MAX { for d in 0..u8::MAX {
let ip = IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d)); let ip = IpAddr::V4(std::net::Ipv4Addr::new(a, b, c, d));
assert_eq!(ip.is_global(), is_global_hardcoded(ip), "IP mismatch: {}", ip) assert_eq!(ip.is_global(), is_global_hardcoded(ip), "IP mismatch: {ip}");
} }
} }
} }
@@ -959,15 +959,15 @@ mod tests {
} }
#[test] #[test]
#[ignore] #[ignore = "random IPv6 check is too slow for the regular test suite"]
fn test_ipv6_global() { fn test_ipv6_global() {
use rand::Rng; use rand::RngExt;
std::thread::scope(|s| { std::thread::scope(|s| {
for t in 0..16 { for t in 0..16 {
let handle = s.spawn(move || { let _handle = s.spawn(move || {
let mut v = [0u8; 16]; let mut v = [0u8; 16];
let mut rng = rand::thread_rng(); let mut rng = rand::rng();
for i in 0..20 { for i in 0..20 {
println!("Thread {t} Iter: {i}/50"); println!("Thread {t} Iter: {i}/50");