diff --git a/.github/workflows/fix_lint.yaml b/.github/workflows/fix_lint.yaml index 3e518c6267..e0817698f4 100644 --- a/.github/workflows/fix_lint.yaml +++ b/.github/workflows/fix_lint.yaml @@ -9,7 +9,9 @@ on: env: # We use nightly so that `fmt` correctly groups together imports, and # clippy correctly fixes up the benchmarks. - RUST_VERSION: nightly-2025-06-24 + # + # Note: This should match the nightly rust version in `tests.yml`. + RUST_VERSION: nightly-2025-03-27 jobs: fixup: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ecdd88815..aa6250612b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,16 @@ concurrency: env: RUST_VERSION: 1.87.0 + # This nightly is roughly the last to be branded 1.87.0. + # + # We know this, as 1.87.0 was branched from master on 2025-03-28. Shortly + # afterwards, nightlies were then branded as 1.88.0. See + # https://releases.rs/docs/1.87.0/ for where we get the dates. + # + # Technically the last 1.87.0 nightly was 2025-03-29, but that all depends on + # at which time of day they cut the release and when the nightly is built. + # It's safer for future releases to just do the day before. + RUST_NIGHTLY_VERSION: nightly-2025-03-27 # last nightly before 1.88.0 jobs: # Job to detect what has changed so we don't run e.g. Rust checks on PRs that @@ -240,7 +250,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: - toolchain: nightly-2026-02-01 + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} components: clippy - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 @@ -296,7 +306,7 @@ jobs: with: # We use nightly so that we can use some unstable options that we use in # `.rustfmt.toml`. - toolchain: nightly-2025-04-23 + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} components: rustfmt - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 @@ -677,7 +687,12 @@ jobs: - changes cargo-test: - if: ${{ needs.changes.outputs.rust == 'true' }} + # We need to explicitly specify `!cancelled() && !failure()` as otherwise + # GitHub will implicitly add `success()`, which will be `false` when any job + # in `needs`, or its dependents, is skipped. Most notably, jobs like + # `lint-readme`, a dependency of `linting-done`, are unlikely to run very + # often - and would result in this job being skipped. + if: ${{ !cancelled() && !failure() && needs.changes.outputs.rust == 'true' }} runs-on: ubuntu-latest needs: - linting-done @@ -697,7 +712,8 @@ jobs: # We want to ensure that the cargo benchmarks still compile, which requires a # nightly compiler. cargo-bench: - if: ${{ needs.changes.outputs.rust == 'true' }} + # See `cargo-test` above for why we need to specify `!cancelled() && !failure()`. + if: ${{ !cancelled() && !failure() && needs.changes.outputs.rust == 'true' }} runs-on: ubuntu-latest needs: - linting-done @@ -709,7 +725,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master with: - toolchain: nightly-2022-12-01 + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - run: cargo bench --no-run diff --git a/CHANGES.md b/CHANGES.md index 70a9b60e4f..10d8dcc928 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,50 @@ +# Synapse 1.156.0rc1 (2026-06-30) + +## Features + +- Expose [MSC4354 Sticky Events](https://github.com/matrix-org/matrix-spec-proposals/pull/4354) over [MSC4186 (Simplified) Sliding Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4186). ([\#19591](https://github.com/element-hq/synapse/issues/19591)) +- Stabilize support for sending ephemeral events to application services, as per [MSC2409](https://github.com/matrix-org/matrix-spec-proposals/pull/2409). Contributed by @jason-famedly @ Famedly. ([\#19758](https://github.com/element-hq/synapse/issues/19758)) +- Include `allowed_room_ids` in the `/summary` client-server API response for rooms with restricted join rules, as required by Matrix 1.15. + Contributed by @FrenchGithubUser @Famedly. ([\#19762](https://github.com/element-hq/synapse/issues/19762)) +- [MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): Allow authentication on delayed event management endpoints (such as `/restart`) to bypass ratelimits for unauthenticated requests based on the client IP address. ([\#19794](https://github.com/element-hq/synapse/issues/19794)) +- Add new metric `synapse_non_deactivated_user_count` which tracks the number of non-deactivated users in the database, split by `app_service`. ([\#19848](https://github.com/element-hq/synapse/issues/19848)) +- The `GET /_matrix/client/unstable/org.matrix.msc1763/retention/configuration` endpoint is now provided when retention + is enabled and `experimental_features.msc1763_enabled` is enabled, based on + [MSC1763](https://github.com/matrix-org/matrix-spec-proposals/pull/1763). ([\#19853](https://github.com/element-hq/synapse/issues/19853)) +- Add experimental support for [MSC4491: Invite reasons in room creation](https://github.com/matrix-org/matrix-spec-proposals/pull/4491). ([\#19874](https://github.com/element-hq/synapse/issues/19874)) + +## Bugfixes + +- Provide remote servers a way to find out about an event created during the remote join handshake. Contributed by @FrenchGithubUser and @jason-famedly @ Famedly. ([\#19390](https://github.com/element-hq/synapse/issues/19390), [\#19855](https://github.com/element-hq/synapse/issues/19855), [\#19856](https://github.com/element-hq/synapse/issues/19856)) +- Advertise `org.matrix.msc4143` in `unstable_features` when `msc4143_enabled` is set. ([\#19646](https://github.com/element-hq/synapse/issues/19646)) +- Fix a long-standing bug where the badge notification count for a room could become permanently inflated if a read receipt was sent before the room's notification counts were first summarised. ([\#19785](https://github.com/element-hq/synapse/issues/19785)) +- Fix startup listener logging to report the actual bound TCP port, so listeners configured with port `0` no longer log `Synapse now listening on TCP port 0`. ([\#19810](https://github.com/element-hq/synapse/issues/19810)) +- Fix notification counts being inflated after a `/purge_history` when notifications had already been rotated into the summary table. ([\#19834](https://github.com/element-hq/synapse/issues/19834)) +- Fix `/sync` caching transient errors for the `sync_response_cache_duration`. ([\#19845](https://github.com/element-hq/synapse/issues/19845)) +- Fix local events being deleted by the [Purge History admin API](https://element-hq.github.io/synapse/v1.155/admin_api/purge_history_api.html) despite `delete_local_events` being set to false, in room versions other than 1 and 2. ([\#19850](https://github.com/element-hq/synapse/issues/19850)) +- Fix a bug where a user's dehydrated device ([MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814)) was deleted when their device list was synced from Matrix Authentication Service (e.g. upon logging out their last device), breaking offline key delivery. ([\#19892](https://github.com/element-hq/synapse/issues/19892)) + +## Improved Documentation + +- Update `auto_join_rooms` config documentation to cover requirements for auto-joining invite-only rooms. ([\#19660](https://github.com/element-hq/synapse/issues/19660)) +- Add stable endpoint for [MSC3266: Room summary API](https://github.com/matrix-org/matrix-spec-proposals/pull/3266) into worker docs. Contributed by @olmari. ([\#19788](https://github.com/element-hq/synapse/issues/19788)) +- Tweak wording of Rust crate dependency update policy. ([\#19829](https://github.com/element-hq/synapse/issues/19829)) +- Fixed the Admin API user endpoint documentation examples to use JSON booleans (true/false) instead of numeric (0/1) values. ([\#19847](https://github.com/element-hq/synapse/issues/19847)) + +## Internal Changes + +- Make `simple_select_one_onecol_txn()` more helpful by naming the table of the select - as all other query wrapper functions already did. ([\#19869](https://github.com/element-hq/synapse/issues/19869)) +- Refactor `get_user_which_could_invite` logic to reuse `get_users_which_can_issue_invite`. Contributed by Noah Markert. ([\#19732](https://github.com/element-hq/synapse/issues/19732)) +- Fix a flaky test (`twisted.protocols.amp.TooLong` error under `trial -jN`) caused by an oversized debug log line. ([\#19832](https://github.com/element-hq/synapse/issues/19832)) +- Upload Complement test logs as CI artifacts instead of printing the raw output to the build log. ([\#19840](https://github.com/element-hq/synapse/issues/19840)) +- Fix release script considering any workflow completion as successful. ([\#19843](https://github.com/element-hq/synapse/issues/19843)) +- Force keyword-args for clear `default_config(server_name="test")` usage in test utilities. ([\#19849](https://github.com/element-hq/synapse/issues/19849)) +- Add `.ruff_cache/` directory to `.gitignore`. ([\#19854](https://github.com/element-hq/synapse/issues/19854)) +- Bump `poetry` in CI from `2.2.1` to `2.4.1`. ([\#19866](https://github.com/element-hq/synapse/issues/19866), [\#19877](https://github.com/element-hq/synapse/issues/19877)) +- Split out `deferred` and `tokio_runtime` to their own Rust modules. ([\#19868](https://github.com/element-hq/synapse/issues/19868)) +- Prevent the `cargo-test` and `cargo-bench` CI jobs from being skipped, even on PRs that have Rust changes. ([\#19883](https://github.com/element-hq/synapse/issues/19883)) + + # Synapse 1.155.0 (2026-06-16) ## End of Life of Debian 12 Bookworm diff --git a/changelog.d/19390.bugfix b/changelog.d/19390.bugfix deleted file mode 100644 index 706f779174..0000000000 --- a/changelog.d/19390.bugfix +++ /dev/null @@ -1 +0,0 @@ -Provide remote servers a way to find out about an event created during the remote join handshake. Contributed by @FrenchGithubUser and @jason-famedly @ Famedly. diff --git a/changelog.d/19591.feature b/changelog.d/19591.feature deleted file mode 100644 index f780030807..0000000000 --- a/changelog.d/19591.feature +++ /dev/null @@ -1 +0,0 @@ -Expose [MSC4354 Sticky Events](https://github.com/matrix-org/matrix-spec-proposals/pull/4354) over [MSC4186 (Simplified) Sliding Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4186). \ No newline at end of file diff --git a/changelog.d/19646.bugfix b/changelog.d/19646.bugfix deleted file mode 100644 index 5c8d505e87..0000000000 --- a/changelog.d/19646.bugfix +++ /dev/null @@ -1 +0,0 @@ -Advertise `org.matrix.msc4143` in `unstable_features` when `msc4143_enabled` is set. diff --git a/changelog.d/19660.doc b/changelog.d/19660.doc deleted file mode 100644 index f03bc6090c..0000000000 --- a/changelog.d/19660.doc +++ /dev/null @@ -1 +0,0 @@ -Update `auto_join_rooms` config documentation to cover requirements for auto-joining invite-only rooms. diff --git a/changelog.d/19732.misc b/changelog.d/19732.misc deleted file mode 100644 index 99fca0a018..0000000000 --- a/changelog.d/19732.misc +++ /dev/null @@ -1 +0,0 @@ -Refactor `get_user_which_could_invite` logic to reuse `get_users_which_can_issue_invite`. Contributed by Noah Markert. diff --git a/changelog.d/19758.feature b/changelog.d/19758.feature deleted file mode 100644 index 5a360c7695..0000000000 --- a/changelog.d/19758.feature +++ /dev/null @@ -1 +0,0 @@ -Stabilize support for sending ephemeral events to application services, as per [MSC2409](https://github.com/matrix-org/matrix-spec-proposals/pull/2409). Contributed by @jason-famedly @ Famedly. diff --git a/changelog.d/19762.feature b/changelog.d/19762.feature deleted file mode 100644 index 543db3184b..0000000000 --- a/changelog.d/19762.feature +++ /dev/null @@ -1,2 +0,0 @@ -Include `allowed_room_ids` in the `/summary` client-server API response for rooms with restricted join rules, as required by Matrix 1.15. -Contributed by @FrenchGithubUser @Famedly. diff --git a/changelog.d/19785.bugfix b/changelog.d/19785.bugfix deleted file mode 100644 index 643481ee27..0000000000 --- a/changelog.d/19785.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix a long-standing bug where the badge notification count for a room could become permanently inflated if a read receipt was sent before the room's notification counts were first summarised. diff --git a/changelog.d/19788.doc b/changelog.d/19788.doc deleted file mode 100644 index bfdd3a3d37..0000000000 --- a/changelog.d/19788.doc +++ /dev/null @@ -1 +0,0 @@ -Add stable endpoint for [MSC3266: Room summary API](https://github.com/matrix-org/matrix-spec-proposals/pull/3266) into worker docs. Contributed by @olmari. \ No newline at end of file diff --git a/changelog.d/19794.feature b/changelog.d/19794.feature deleted file mode 100644 index be373f9a77..0000000000 --- a/changelog.d/19794.feature +++ /dev/null @@ -1 +0,0 @@ -[MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): Allow authentication on delayed event management endpoints (such as `/restart`) to bypass ratelimits for unauthenticated requests based on the client IP address. diff --git a/changelog.d/19810.bugfix b/changelog.d/19810.bugfix deleted file mode 100644 index 72592532f8..0000000000 --- a/changelog.d/19810.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix startup listener logging to report the actual bound TCP port, so listeners configured with port `0` no longer log `Synapse now listening on TCP port 0`. diff --git a/changelog.d/19829.doc b/changelog.d/19829.doc deleted file mode 100644 index 44dbc0eff4..0000000000 --- a/changelog.d/19829.doc +++ /dev/null @@ -1 +0,0 @@ -Tweak wording of Rust crate dependency update policy. \ No newline at end of file diff --git a/changelog.d/19832.misc b/changelog.d/19832.misc deleted file mode 100644 index 23c63fa10c..0000000000 --- a/changelog.d/19832.misc +++ /dev/null @@ -1 +0,0 @@ -Fix a flaky test (`twisted.protocols.amp.TooLong` error under `trial -jN`) caused by an oversized debug log line. diff --git a/changelog.d/19834.bugfix b/changelog.d/19834.bugfix deleted file mode 100644 index 097a09bac6..0000000000 --- a/changelog.d/19834.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix notification counts being inflated after a `/purge_history` when notifications had already been rotated into the summary table. diff --git a/changelog.d/19840.misc b/changelog.d/19840.misc deleted file mode 100644 index db56b65764..0000000000 --- a/changelog.d/19840.misc +++ /dev/null @@ -1 +0,0 @@ -Upload Complement test logs as CI artifacts instead of printing the raw output to the build log. diff --git a/changelog.d/19843.misc b/changelog.d/19843.misc deleted file mode 100644 index 633c11edb7..0000000000 --- a/changelog.d/19843.misc +++ /dev/null @@ -1 +0,0 @@ -Fix release script considering any workflow completion as successful. \ No newline at end of file diff --git a/changelog.d/19845.bugfix b/changelog.d/19845.bugfix deleted file mode 100644 index c2009f0749..0000000000 --- a/changelog.d/19845.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix `/sync` caching transient errors for the `sync_response_cache_duration`. \ No newline at end of file diff --git a/changelog.d/19847.doc b/changelog.d/19847.doc deleted file mode 100644 index 225b03d7b3..0000000000 --- a/changelog.d/19847.doc +++ /dev/null @@ -1 +0,0 @@ -Fixed the Admin API user endpoint documentation examples to use JSON booleans (true/false) instead of numeric (0/1) values. diff --git a/changelog.d/19848.feature b/changelog.d/19848.feature deleted file mode 100644 index a7b891e547..0000000000 --- a/changelog.d/19848.feature +++ /dev/null @@ -1 +0,0 @@ -Add new metric `synapse_non_deactivated_user_count` which tracks the number of non-deactivated users in the database, split by `app_service`. diff --git a/changelog.d/19849.misc b/changelog.d/19849.misc deleted file mode 100644 index 08a3bb0e96..0000000000 --- a/changelog.d/19849.misc +++ /dev/null @@ -1 +0,0 @@ -Force keyword-args for clear `default_config(server_name="test")` usage in test utilities. diff --git a/changelog.d/19850.bugfix b/changelog.d/19850.bugfix deleted file mode 100644 index 357c2ee3f3..0000000000 --- a/changelog.d/19850.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix local events being deleted by the [Purge History admin API](https://element-hq.github.io/synapse/v1.155/admin_api/purge_history_api.html) despite `delete_local_events` being set to false, in room versions other than 1 and 2. \ No newline at end of file diff --git a/changelog.d/19853.feature b/changelog.d/19853.feature deleted file mode 100644 index b11eaa16d7..0000000000 --- a/changelog.d/19853.feature +++ /dev/null @@ -1,3 +0,0 @@ -The `GET /_matrix/client/unstable/org.matrix.msc1763/retention/configuration` endpoint is now provided when retention -is enabled and `experimental_features.msc1763_enabled` is enabled, based on -[MSC1763](https://github.com/matrix-org/matrix-spec-proposals/pull/1763). diff --git a/changelog.d/19854.misc b/changelog.d/19854.misc deleted file mode 100644 index 501745ebb1..0000000000 --- a/changelog.d/19854.misc +++ /dev/null @@ -1 +0,0 @@ -Add `.ruff_cache/` directory to `.gitignore`. diff --git a/changelog.d/19855.bugfix b/changelog.d/19855.bugfix deleted file mode 100644 index 706f779174..0000000000 --- a/changelog.d/19855.bugfix +++ /dev/null @@ -1 +0,0 @@ -Provide remote servers a way to find out about an event created during the remote join handshake. Contributed by @FrenchGithubUser and @jason-famedly @ Famedly. diff --git a/changelog.d/19856.bugfix b/changelog.d/19856.bugfix deleted file mode 100644 index 706f779174..0000000000 --- a/changelog.d/19856.bugfix +++ /dev/null @@ -1 +0,0 @@ -Provide remote servers a way to find out about an event created during the remote join handshake. Contributed by @FrenchGithubUser and @jason-famedly @ Famedly. diff --git a/changelog.d/19866.misc b/changelog.d/19866.misc deleted file mode 100644 index 7bf704e84c..0000000000 --- a/changelog.d/19866.misc +++ /dev/null @@ -1 +0,0 @@ -Bump `poetry` in CI from `2.2.1` to `2.4.1`. \ No newline at end of file diff --git a/changelog.d/19868.misc b/changelog.d/19868.misc deleted file mode 100644 index cd182ea15c..0000000000 --- a/changelog.d/19868.misc +++ /dev/null @@ -1 +0,0 @@ -Split out `deferred` and `tokio_runtime` to their own Rust modules. diff --git a/changelog.d/19869.bugfix b/changelog.d/19869.bugfix deleted file mode 100644 index 2772db850f..0000000000 --- a/changelog.d/19869.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make simple_select_one_onecol_txn() more helpful by naming the table of the select - as all other query wrapper functions already did. diff --git a/changelog.d/19874.feature b/changelog.d/19874.feature deleted file mode 100644 index 57238e820f..0000000000 --- a/changelog.d/19874.feature +++ /dev/null @@ -1 +0,0 @@ -Add experimental support for [MSC4491: Invite reasons in room creation](https://github.com/matrix-org/matrix-spec-proposals/pull/4491). \ No newline at end of file diff --git a/changelog.d/19877.misc b/changelog.d/19877.misc deleted file mode 100644 index 7bf704e84c..0000000000 --- a/changelog.d/19877.misc +++ /dev/null @@ -1 +0,0 @@ -Bump `poetry` in CI from `2.2.1` to `2.4.1`. \ No newline at end of file diff --git a/changelog.d/19896.misc b/changelog.d/19896.misc new file mode 100644 index 0000000000..2c6a8740ef --- /dev/null +++ b/changelog.d/19896.misc @@ -0,0 +1 @@ +Change the [MSC3814](https://github.com/matrix-org/matrix-spec-proposals/pull/3814) dehydrated device `/events` endpoint from `POST` to `GET`. diff --git a/debian/changelog b/debian/changelog index 224916d2f6..a60203b68b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ -matrix-synapse-py3 (1.155.0+nmu1) UNRELEASED; urgency=medium +matrix-synapse-py3 (1.156.0~rc1) stable; urgency=medium * Bump poetry from 2.2.1 to 2.4.1. + * New Synapse release 1.156.0rc1. - -- Synapse Packaging team Wed, 24 Jun 2026 13:10:00 +0100 + -- Synapse Packaging team Tue, 30 Jun 2026 14:37:46 +0100 matrix-synapse-py3 (1.155.0) stable; urgency=medium diff --git a/pyproject.toml b/pyproject.toml index 316850bd22..1e31b23d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "matrix-synapse" -version = "1.155.0" +version = "1.156.0rc1" description = "Homeserver for the Matrix decentralised comms protocol" readme = "README.rst" authors = [ diff --git a/rust/src/events/formats/v4.rs b/rust/src/events/formats/v4.rs index 22a1a677f2..08e5189275 100644 --- a/rust/src/events/formats/v4.rs +++ b/rust/src/events/formats/v4.rs @@ -37,7 +37,7 @@ use crate::{ json::AllowMissing, }; -/// Version-specific fields for room version 11. +/// Version-specific fields for room version 12+. #[derive(Serialize, Deserialize)] pub struct EventFormatV4 { #[serde( @@ -57,6 +57,8 @@ impl EventFormatV4 { bail!("v4 events must not have an explicit event_id"); } + validate_optional_room_id(self.room_id.as_ref_opt(), common_fields)?; + Ok(()) } diff --git a/rust/src/events/formats/vmsc4242.rs b/rust/src/events/formats/vmsc4242.rs index 45d9a25068..4e13e6f36b 100644 --- a/rust/src/events/formats/vmsc4242.rs +++ b/rust/src/events/formats/vmsc4242.rs @@ -33,7 +33,7 @@ use pyo3::PyResult; use serde::{Deserialize, Serialize}; use crate::events::constants::event_type::M_ROOM_CREATE; -use crate::events::formats::v4::get_room_id_for_optional_room_id; +use crate::events::formats::v4::{get_room_id_for_optional_room_id, validate_optional_room_id}; use crate::events::formats::EventCommonFields; use crate::events::Event; use crate::json::AllowMissing; @@ -62,6 +62,8 @@ impl EventFormatVMSC4242 { bail!("MSC4242 events must not have an explicit event_id"); } + validate_optional_room_id(self.room_id.as_ref_opt(), common_fields)?; + Ok(()) } diff --git a/rust/src/events/utils.rs b/rust/src/events/utils.rs index b58edc9352..668e415d5b 100644 --- a/rust/src/events/utils.rs +++ b/rust/src/events/utils.rs @@ -367,9 +367,14 @@ mod tests { } #[test] - /// Tests to ensure events with overly large values for `depth` are handled appropriately. - /// This was added in room version 6 . - fn test_calculate_event_id_big_int_old_rooms() { + /// This is a bit funky, but we test that relaxed `depth` rules are in + /// place, even in room versions that enforce strict canonical JSON, as we + /// still need to load invalid events from the database even in newer room + /// versions. See https://github.com/element-hq/synapse/pull/19816. + /// + /// We still enforce canonicaljson when creating *new* events (see + /// `EventValidator` on the python side). + fn test_calculate_event_id_big_int_is_relaxed() { let original = json!( { "auth_events":[ @@ -407,12 +412,10 @@ mod tests { ); // These should succeed. - let _event_id = calculate_event_id(&original, &RoomVersion::V3).unwrap(); - let _event_id = calculate_event_id(&original, &RoomVersion::V4).unwrap(); - let _event_id = calculate_event_id(&original, &RoomVersion::V5).unwrap(); - - // These should not succeed. let versions = [ + RoomVersion::V3, + RoomVersion::V4, + RoomVersion::V5, RoomVersion::V6, RoomVersion::V7, RoomVersion::V8, @@ -422,7 +425,7 @@ mod tests { RoomVersion::V12, ]; for version in versions { - let _event_id = calculate_event_id(&original, &version).unwrap_err(); + let _event_id = calculate_event_id(&original, &version).unwrap(); } } diff --git a/rust/src/json.rs b/rust/src/json.rs index 3e833c6707..2a788e5e61 100644 --- a/rust/src/json.rs +++ b/rust/src/json.rs @@ -105,8 +105,6 @@ pub mod allow_missing { #[cfg(test)] mod tests { - use std::assert_matches; - use serde::{Deserialize, Serialize}; use super::*; @@ -126,12 +124,12 @@ mod tests { let json = r#"{"value":42}"#; let deserialized: TestStruct = serde_json::from_str(json).unwrap(); assert!(deserialized.value.is_some()); - assert_matches!(deserialized.value, AllowMissing::Some(42)); + assert!(matches!(deserialized.value, AllowMissing::Some(42))); let json = r#"{}"#; let deserialized: TestStruct = serde_json::from_str(json).unwrap(); assert!(deserialized.value.is_absent()); - assert_matches!(deserialized.value, AllowMissing::Absent); + assert!(matches!(deserialized.value, AllowMissing::Absent)); } #[test] @@ -203,16 +201,16 @@ mod tests { let json = r#"{"value":42}"#; let deserialized: TestStructOption = serde_json::from_str(json).unwrap(); assert!(deserialized.value.is_some()); - assert_matches!(deserialized.value, AllowMissing::Some(Some(42))); + assert!(matches!(deserialized.value, AllowMissing::Some(Some(42)))); let json = r#"{"value":null}"#; let deserialized: TestStructOption = serde_json::from_str(json).unwrap(); assert!(deserialized.value.is_some()); - assert_matches!(deserialized.value, AllowMissing::Some(None)); + assert!(matches!(deserialized.value, AllowMissing::Some(None))); let json = r#"{}"#; let deserialized: TestStructOption = serde_json::from_str(json).unwrap(); assert!(deserialized.value.is_absent()); - assert_matches!(deserialized.value, AllowMissing::Absent); + assert!(matches!(deserialized.value, AllowMissing::Absent)); } } diff --git a/schema/synapse-config.schema.yaml b/schema/synapse-config.schema.yaml index b64419b4c9..8a889339b4 100644 --- a/schema/synapse-config.schema.yaml +++ b/schema/synapse-config.schema.yaml @@ -1,5 +1,5 @@ $schema: https://element-hq.github.io/synapse/latest/schema/v1/meta.schema.json -$id: https://element-hq.github.io/synapse/schema/synapse/v1.155/synapse-config.schema.json +$id: https://element-hq.github.io/synapse/schema/synapse/v1.156/synapse-config.schema.json type: object properties: modules: diff --git a/synapse/rest/client/devices.py b/synapse/rest/client/devices.py index 0231ed374d..76043e6120 100644 --- a/synapse/rest/client/devices.py +++ b/synapse/rest/client/devices.py @@ -33,6 +33,7 @@ from synapse.http.servlet import ( RestServlet, parse_and_validate_json_object_from_request, parse_integer, + parse_string, ) from synapse.http.site import SynapseRequest from synapse.rest.client._base import client_patterns, interactive_auth_handler @@ -249,17 +250,49 @@ class DehydratedDeviceEventsServlet(RestServlet): self.auth = hs.get_auth() self.store = hs.get_datastores().main + async def on_GET( + self, request: SynapseRequest, device_id: str + ) -> tuple[int, JsonDict]: + requester = await self.auth.get_user_by_req(request) + + next_batch = parse_string(request, "next_batch") + limit = parse_integer(request, "limit", 100) + + msgs = await self.message_handler.get_events_for_dehydrated_device( + requester=requester, + device_id=device_id, + since_token=next_batch, + limit=limit, + ) + + return 200, msgs + class PostBody(RequestBodyModel): + """ + This is deprecated: you should use GET instead. + + The POST version is provided temporarily for backwards compatibility + with a previous unstable draft of MSC3814. + """ + next_batch: StrictStr | None = None async def on_POST( self, request: SynapseRequest, device_id: str ) -> tuple[int, JsonDict]: + """ + This is deprecated: you should use GET instead. + + The POST version is provided temporarily for backwards compatibility + with a previous unstable draft of MSC3814. + """ + requester = await self.auth.get_user_by_req(request) next_batch = parse_and_validate_json_object_from_request( request, self.PostBody ).next_batch + limit = parse_integer(request, "limit", 100) msgs = await self.message_handler.get_events_for_dehydrated_device( diff --git a/synapse/rest/synapse/mas/devices.py b/synapse/rest/synapse/mas/devices.py index 9d94a67675..b5d19d3cec 100644 --- a/synapse/rest/synapse/mas/devices.py +++ b/synapse/rest/synapse/mas/devices.py @@ -196,6 +196,16 @@ class MasSyncDevicesResource(MasBaseResource): current_devices_list = set(current_devices.keys()) target_device_list = set(body.devices) + # Exclude the dehydrated device (MSC3814): it has no MAS session, so MAS + # never lists it in the target set and the reconciliation below would + # otherwise treat it as extra and delete it. This mirrors the admin + # devices API and MAS's own legacy device-sync path, which both skip it. + dehydrated_device = await self.device_handler.get_dehydrated_device( + user_id=str(user_id) + ) + if dehydrated_device is not None: + current_devices_list.discard(dehydrated_device[0]) + to_add = target_device_list - current_devices_list to_delete = current_devices_list - target_device_list diff --git a/tests/events/test_validator.py b/tests/events/test_validator.py index 082ae04a4c..2b1ee931ba 100644 --- a/tests/events/test_validator.py +++ b/tests/events/test_validator.py @@ -11,7 +11,12 @@ # See the GNU Affero General Public License for more details: # . # -from synapse.api.room_versions import RoomVersions +from synapse.api.errors import Codes, SynapseError +from synapse.api.room_versions import ( + KNOWN_ROOM_VERSIONS, + EventFormatVersions, + RoomVersions, +) from synapse.events import make_event_from_dict from synapse.events.validator import EventValidator @@ -45,3 +50,46 @@ class EventValidatorTestCase(HomeserverTestCase): ) EventValidator().validate_new(event, self.hs.config) + + def test_validate_new_rejects_big_depth_for_strict_canonicaljson_rooms( + self, + ) -> None: + """ + Test that `EventValidator.validate_new` rejects events with integers outside the + canonical JSON range, in room versions which enforce it (v6+). + """ + for room_version in KNOWN_ROOM_VERSIONS.values(): + with self.subTest(room_version=room_version.identifier): + event_dict = { + "room_id": "!room:test", + "type": "m.room.message", + "sender": "@alice:example.com", + "content": { + "msgtype": "m.text", + "body": "hello", + }, + "auth_events": [], + "prev_events": [], + "hashes": {"sha256": "aGVsbG8="}, + "signatures": {}, + "depth": 2**53, + "origin_server_ts": 1000, + } + + if room_version.event_format == EventFormatVersions.ROOM_V1_V2: + event_dict["event_id"] = "$event:test" + + event = make_event_from_dict( + event_dict, + room_version=room_version, + ) + + # Check if this room version enforces strict canonical json. + if room_version.strict_canonicaljson: + with self.assertRaises(SynapseError) as cm: + EventValidator().validate_new(event, self.hs.config) + + self.assertEqual(cm.exception.errcode, Codes.BAD_JSON) + self.assertEqual(cm.exception.msg, "JSON integer out of range") + else: + EventValidator().validate_new(event, self.hs.config) diff --git a/tests/rest/client/test_devices.py b/tests/rest/client/test_devices.py index 2cf293a962..bdf9ad1786 100644 --- a/tests/rest/client/test_devices.py +++ b/tests/rest/client/test_devices.py @@ -224,9 +224,8 @@ class DehydratedDeviceTestCase(unittest.HomeserverTestCase): # make sure we can fetch the message with our dehydrated device id channel = self.make_request( - "POST", + "GET", f"_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/{device_id}/events", - content={}, access_token=token, shorthand=False, ) @@ -236,9 +235,8 @@ class DehydratedDeviceTestCase(unittest.HomeserverTestCase): # fetch messages again and make sure that the message was not deleted channel = self.make_request( - "POST", + "GET", f"_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/{device_id}/events", - content={}, access_token=token, shorthand=False, ) @@ -248,11 +246,9 @@ class DehydratedDeviceTestCase(unittest.HomeserverTestCase): # make sure fetching messages with next batch token works - there are no unfetched # messages so we should receive an empty array - content = {"next_batch": next_batch_token} channel = self.make_request( - "POST", - f"_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/{device_id}/events", - content=content, + "GET", + f"_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/{device_id}/events?next_batch={next_batch_token}", access_token=token, shorthand=False, ) diff --git a/tests/rest/synapse/mas/test_devices.py b/tests/rest/synapse/mas/test_devices.py index 6b7596f1c6..e9035f4c0b 100644 --- a/tests/rest/synapse/mas/test_devices.py +++ b/tests/rest/synapse/mas/test_devices.py @@ -546,6 +546,48 @@ class MasSyncDevicesResource(BaseTestCase): devices = self.get_success(store.get_devices_by_user(str(self.alice_user_id))) self.assertEqual(set(devices.keys()), {"DEVICE1"}) + def test_sync_devices_preserves_dehydrated_device(self) -> None: + # Store a dehydrated device (MSC3814). It has no MAS session, so it is + # never part of the target device list, but it must survive a sync. + device_handler = self.hs.get_device_handler() + dehydrated_device_id = self.get_success( + device_handler.store_dehydrated_device( + user_id=str(self.alice_user_id), + device_id=None, + device_data={"device_data": {"foo": "bar"}}, + initial_device_display_name="dehydrated device", + keys_for_device={}, + ) + ) + + # Sync down to a single real device. The dehydrated device is not in the + # target list, but must not be deleted. + channel = self.make_request( + "POST", + "/_synapse/mas/sync_devices", + shorthand=False, + access_token=self.SHARED_SECRET, + content={ + "localpart": "alice", + "devices": ["DEVICE1"], + }, + ) + + self.assertEqual(channel.code, 200, channel.json_body) + self.assertEqual(channel.json_body, {}) + + # The real devices are reconciled, but the dehydrated device survives. + store = self.hs.get_datastores().main + devices = self.get_success(store.get_devices_by_user(str(self.alice_user_id))) + self.assertEqual(set(devices.keys()), {"DEVICE1", dehydrated_device_id}) + + # And it is still registered as the dehydrated device. + dehydrated = self.get_success( + device_handler.get_dehydrated_device(str(self.alice_user_id)) + ) + assert dehydrated is not None + self.assertEqual(dehydrated[0], dehydrated_device_id) + def test_sync_devices_add_and_delete(self) -> None: # Sync with a mix of additions and deletions channel = self.make_request(