diff --git a/Cargo.lock b/Cargo.lock index 7da803618..3cf2206b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1003,6 +1003,7 @@ dependencies = [ "rand 0.10.1", "reqwest", "ruma", + "ruminuwuity", "serde", "serde_html_form", "serde_json", @@ -1076,7 +1077,7 @@ dependencies = [ "tikv-jemallocator", "tokio", "tokio-metrics", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "tracing", "tracing-core", "tracing-subscriber", @@ -1183,6 +1184,7 @@ dependencies = [ "regex", "reqwest", "ruma", + "ruminuwuity", "rustyline-async", "sd-notify", "serde", @@ -1282,16 +1284,6 @@ dependencies = [ "typewit", ] -[[package]] -name = "continuwuity-admin-api" -version = "0.1.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" -dependencies = [ - "ruma-common", - "serde", - "serde_json", -] - [[package]] name = "convert_case" version = "0.10.0" @@ -1718,16 +1710,6 @@ dependencies = [ "litrs", ] -[[package]] -name = "draupnir-antispam" -version = "0.1.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" -dependencies = [ - "ruma-common", - "serde", - "serde_json", -] - [[package]] name = "dtor" version = "0.6.0" @@ -2883,30 +2865,20 @@ dependencies = [ [[package]] name = "js_option" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68421373957a1593a767013698dbf206e2b221eefe97a44d98d18672ff38423c" +checksum = "c7dd3e281add16813cf673bf74a32249b0aa0d1c8117519a17b3ada5e8552b3c" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "konst" -version = "0.3.16" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" dependencies = [ "const_panic", - "konst_kernel", - "typewit", -] - -[[package]] -name = "konst_kernel" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" -dependencies = [ "typewit", ] @@ -3237,16 +3209,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "meowlnir-antispam" -version = "0.1.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" -dependencies = [ - "ruma-common", - "serde", - "serde_json", -] - [[package]] name = "mime" version = "0.3.17" @@ -4274,6 +4236,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -4283,7 +4247,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.5", ] @@ -4298,6 +4262,16 @@ dependencies = [ "rand_core 0.10.0", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -4360,7 +4334,7 @@ dependencies = [ "paste", "profiling", "rand 0.9.3", - "rand_chacha", + "rand_chacha 0.9.0", "simd_helpers", "thiserror 2.0.18", "v_frame", @@ -4527,31 +4501,27 @@ dependencies = [ [[package]] name = "ruma" -version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.14.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "assign", - "continuwuity-admin-api", - "draupnir-antispam", "js_int", "js_option", - "meowlnir-antispam", "ruma-appservice-api", "ruma-client-api", "ruma-common", "ruma-events", "ruma-federation-api", - "ruma-identifiers-validation", - "ruma-identity-service-api", "ruma-push-gateway-api", "ruma-signatures", + "ruma-state-res", "web-time", ] [[package]] name = "ruma-appservice-api" -version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.14.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "js_int", "ruma-common", @@ -4562,13 +4532,12 @@ dependencies = [ [[package]] name = "ruma-client-api" -version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.22.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "as_variant", "assign", "bytes", - "date_header", "http", "js_int", "js_option", @@ -4585,12 +4554,13 @@ dependencies = [ [[package]] name = "ruma-common" -version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.17.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "as_variant", "base64 0.22.1", "bytes", + "date_header", "form_urlencoded", "getrandom 0.2.17", "http", @@ -4598,14 +4568,13 @@ dependencies = [ "js_int", "konst", "percent-encoding", - "rand 0.10.1", + "rand 0.8.5", "regex", "ruma-identifiers-validation", "ruma-macros", "serde", "serde_html_form", "serde_json", - "smallvec", "thiserror 2.0.18", "time", "tracing", @@ -4613,37 +4582,34 @@ dependencies = [ "uuid", "web-time", "wildmatch", + "zeroize", ] [[package]] name = "ruma-events" -version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.32.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "as_variant", "indexmap", "js_int", "js_option", - "percent-encoding", "pulldown-cmark", - "regex", "ruma-common", - "ruma-identifiers-validation", "ruma-macros", "serde", "serde_json", - "smallvec", "thiserror 2.0.18", "tracing", - "url", "web-time", "wildmatch", + "zeroize", ] [[package]] name = "ruma-federation-api" -version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.13.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "bytes", "headers", @@ -4653,9 +4619,10 @@ dependencies = [ "js_int", "memchr", "mime", - "rand 0.10.1", + "rand 0.8.5", "ruma-common", "ruma-events", + "ruma-signatures", "serde", "serde_json", "thiserror 2.0.18", @@ -4664,28 +4631,19 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" -version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.12.0" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "js_int", "thiserror 2.0.18", ] -[[package]] -name = "ruma-identity-service-api" -version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" -dependencies = [ - "js_int", - "ruma-common", - "serde", -] - [[package]] name = "ruma-macros" -version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.17.1" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ + "as_variant", "cfg-if", "proc-macro-crate", "proc-macro2", @@ -4693,13 +4651,13 @@ dependencies = [ "ruma-identifiers-validation", "serde", "syn", - "toml 0.8.23", + "toml 1.1.2+spec-1.1.0", ] [[package]] name = "ruma-push-gateway-api" -version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.13.0" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "js_int", "ruma-common", @@ -4710,21 +4668,45 @@ dependencies = [ [[package]] name = "ruma-signatures" -version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=1415caf8a32af4d943580c5ea4e12be1974593c2#1415caf8a32af4d943580c5ea4e12be1974593c2" +version = "0.19.0" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" dependencies = [ "base64 0.22.1", "ed25519-dalek", + "memchr", "pkcs8", - "rand 0.10.1", - "rand_core 0.6.4", + "rand 0.8.5", "ruma-common", "serde_json", "sha2", - "subslice", "thiserror 2.0.18", ] +[[package]] +name = "ruma-state-res" +version = "0.15.0" +source = "git+https://github.com/ruma/ruma.git?rev=e2e5fece57800a2559ab028fd775a7978f3725e9#e2e5fece57800a2559ab028fd775a7978f3725e9" +dependencies = [ + "js_int", + "ruma-common", + "ruma-events", + "ruma-signatures", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ruminuwuity" +version = "0.5.7-alpha.1" +dependencies = [ + "ruma", + "serde", + "serde_json", + "wildmatch", +] + [[package]] name = "rust-librocksdb-sys" version = "0.42.0+10.10.1" @@ -5436,15 +5418,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "subslice" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a8e4809a3bb02de01f1f7faf1ba01a83af9e8eabcd4d31dd6e413d14d56aae" -dependencies = [ - "memchr", -] - [[package]] name = "subtle" version = "2.6.1" @@ -5773,6 +5746,19 @@ dependencies = [ "winnow 0.7.15", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -6071,15 +6057,6 @@ name = "typewit" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc19094686c694eb41b3b99dcc2f2975d4b078512fa22ae6c63f7ca318bdcff7" -dependencies = [ - "typewit_proc_macros", -] - -[[package]] -name = "typewit_proc_macros" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" [[package]] name = "uname" diff --git a/Cargo.toml b/Cargo.toml index a4c35f591..99124e8a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,9 +47,9 @@ default-features = false features = ["features"] [workspace.dependencies.toml] -version = "0.9.5" +version = "1.0.0" default-features = false -features = ["parse"] +features = ["parse", "serde"] [workspace.dependencies.sanitize-filename] version = "0.6.0" @@ -342,49 +342,40 @@ version = "0.1.2" # Used for matrix spec type definitions and helpers [workspace.dependencies.ruma] -git = "https://forgejo.ellis.link/continuwuation/ruwuma" -#branch = "conduwuit-changes" -rev = "1415caf8a32af4d943580c5ea4e12be1974593c2" +# version = "0.14.1" +git = "https://github.com/ruma/ruma.git" +rev = "e2e5fece57800a2559ab028fd775a7978f3725e9" features = [ - "compat", - "rand", "appservice-api-c", "client-api", "federation-api", - "markdown", "push-gateway-api-c", - "unstable-exhaustive-types", + "state-res", + "rand", + "markdown", "ring-compat", "compat-upload-signatures", - "identifiers-validation", - "unstable-unspecified", "unstable-msc2448", "unstable-msc2666", "unstable-msc2867", "unstable-msc2870", - "unstable-msc3026", "unstable-msc3061", "unstable-msc3814", "unstable-msc3245", - "unstable-msc3266", "unstable-msc3381", # polls "unstable-msc3489", # beacon / live location - "unstable-msc3575", "unstable-msc3930", # polls push rules "unstable-msc4075", "unstable-msc4095", "unstable-msc4121", "unstable-msc4125", - "unstable-msc4155", "unstable-msc4186", "unstable-msc4203", # sending to-device events to appservices - "unstable-msc4210", # remove legacy mentions - "unstable-extensible-events", - "unstable-pdu", - "unstable-msc4155", + "unstable-msc4310", + "unstable-msc4380", "unstable-msc4143", # livekit well_known response - "unstable-msc4284", "unstable-msc4439", # pgp_key in .well_known/matrix/support + "unstable-extensible-events", ] [workspace.dependencies.rust-rocksdb] @@ -657,6 +648,10 @@ default-features = false package = "conduwuit" path = "src/main" +[workspace.dependencies.ruminuwuity] +package = "ruminuwuity" +path = "src/ruminuwuity" + ############################################################################### # # Release profiles diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml index c35f398d6..70751e671 100644 --- a/src/api/Cargo.toml +++ b/src/api/Cargo.toml @@ -93,6 +93,7 @@ log.workspace = true rand.workspace = true reqwest.workspace = true ruma.workspace = true +ruminuwuity.workspace = true serde_html_form.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/src/api/admin/rooms/ban.rs b/src/api/admin/rooms/ban.rs index 4c6d3c85c..66abe5d64 100644 --- a/src/api/admin/rooms/ban.rs +++ b/src/api/admin/rooms/ban.rs @@ -2,9 +2,10 @@ use conduwuit::{Err, Result, info, utils::ReadyExt, warn}; use futures::{FutureExt, StreamExt}; use ruma::{ - OwnedRoomAliasId, continuwuity_admin_api::rooms, + OwnedRoomAliasId, events::room::message::RoomMessageEventContent, }; +use ruminuwuity::admin::continuwuity::rooms; use crate::{Ruma, client::leave_room}; @@ -36,7 +37,6 @@ pub(crate) async fn ban_room( .rooms .state_cache .room_members(&body.room_id) - .map(ToOwned::to_owned) .ready_filter(|user| services.globals.user_is_local(user)) .boxed(); let mut evicted = Vec::new(); diff --git a/src/api/admin/rooms/list.rs b/src/api/admin/rooms/list.rs index c05dba4ab..a91a626b4 100644 --- a/src/api/admin/rooms/list.rs +++ b/src/api/admin/rooms/list.rs @@ -1,7 +1,8 @@ use axum::extract::State; use conduwuit::{Err, Result}; use futures::StreamExt; -use ruma::{OwnedRoomId, continuwuity_admin_api::rooms}; +use ruma::OwnedRoomId; +use ruminuwuity::admin::continuwuity::rooms; use crate::Ruma; @@ -22,7 +23,7 @@ pub(crate) async fn list_rooms( .metadata .iter_ids() .filter_map(|room_id| async move { - if !services.rooms.metadata.is_banned(room_id).await { + if !services.rooms.metadata.is_banned(&room_id).await { Some(room_id.to_owned()) } else { None diff --git a/src/api/client/account/mod.rs b/src/api/client/account/mod.rs index 57a592e63..353d4e350 100644 --- a/src/api/client/account/mod.rs +++ b/src/api/client/account/mod.rs @@ -87,7 +87,7 @@ pub(crate) async fn get_register_available_route( return Err!(Request(Exclusive("Username is reserved by an appservice."))); } - Ok(get_username_availability::v3::Response { available: true }) + Ok(get_username_availability::v3::Response::new(true)) } /// # `POST /_matrix/client/r0/account/password` @@ -194,7 +194,7 @@ pub(crate) async fn change_password_route( .await; } - Ok(change_password::v3::Response {}) + Ok(change_password::v3::Response::new()) } /// # `POST /_matrix/client/v3/account/password/email/requestToken` diff --git a/src/api/client/account/register.rs b/src/api/client/account/register.rs index 3fd7e4418..9b45cc111 100644 --- a/src/api/client/account/register.rs +++ b/src/api/client/account/register.rs @@ -218,14 +218,7 @@ pub(crate) async fn register_route( .await?; // Generate new device id if the user didn't specify one - let no_device = body.inhibit_login - || body - .appservice_info - .as_ref() - .is_some_and(|aps| aps.registration.device_management); - - let (token, device) = if !no_device { - // Don't create a device for inhibited logins + let (token, device) = if !body.inhibit_login { let device_id = if is_guest { None } else { body.device_id.clone() } .unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into()); @@ -243,11 +236,12 @@ pub(crate) async fn register_route( Some(client.to_string()), ) .await?; - debug_info!(%user_id, %device_id, "User account was created"); (Some(new_token), Some(device_id)) } else { + // Don't create a device for inhibited logins (None, None) }; + debug_info!(%user_id, %device_id, "User account was created"); // If the user registered with an email, associate it with their account. if let Some(identity) = identity diff --git a/src/api/client/account_data.rs b/src/api/client/account_data.rs index 8546dcb61..1cd376399 100644 --- a/src/api/client/account_data.rs +++ b/src/api/client/account_data.rs @@ -40,7 +40,7 @@ pub(crate) async fn set_global_account_data_route( ) .await?; - Ok(set_global_account_data::v3::Response {}) + Ok(set_global_account_data::v3::Response::new()) } /// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}` @@ -65,7 +65,7 @@ pub(crate) async fn set_room_account_data_route( ) .await?; - Ok(set_room_account_data::v3::Response {}) + Ok(set_room_account_data::v3::Response::new()) } /// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}` @@ -119,7 +119,7 @@ async fn set_account_data( event_type_s: &str, data: &RawJsonValue, ) -> Result { - if event_type_s == RoomAccountDataEventType::FullyRead.to_cow_str() { + if event_type_s == "m.fully_read" { return Err!(Request(BadJson( "This endpoint cannot be used for marking a room as fully read (setting \ m.fully_read)" diff --git a/src/api/client/admin/suspend.rs b/src/api/client/admin/suspend.rs index bab1cb5a7..478a5d3db 100644 --- a/src/api/client/admin/suspend.rs +++ b/src/api/client/admin/suspend.rs @@ -1,7 +1,7 @@ use axum::extract::State; use conduwuit::{Err, Result}; use futures::future::{join, join3}; -use ruma::api::client::admin::{get_suspended, set_suspended}; +use ruminuwuity::admin::{get_suspended, set_suspended}; use crate::Ruma; diff --git a/src/api/client/appservice.rs b/src/api/client/appservice.rs index eb6b3312a..962cbe4c2 100644 --- a/src/api/client/appservice.rs +++ b/src/api/client/appservice.rs @@ -47,5 +47,5 @@ pub(crate) async fn appservice_ping( .await? .expect("We already validated if an appservice URL exists above"); - Ok(request_ping::v1::Response { duration: timer.elapsed() }) + Ok(request_ping::v1::Response::new(timer.elapsed())) } diff --git a/src/api/client/backup.rs b/src/api/client/backup.rs index a3038f261..5ee3c48c2 100644 --- a/src/api/client/backup.rs +++ b/src/api/client/backup.rs @@ -28,7 +28,7 @@ pub(crate) async fn create_backup_version_route( .key_backups .create_backup(body.sender_user(), &body.algorithm)?; - Ok(create_backup_version::v3::Response { version }) + Ok(create_backup_version::v3::Response::new(version)) } /// # `PUT /_matrix/client/r0/room_keys/version/{version}` @@ -44,7 +44,7 @@ pub(crate) async fn update_backup_version_route( .update_backup(body.sender_user(), &body.version, &body.algorithm) .await?; - Ok(update_backup_version::v3::Response {}) + Ok(update_backup_version::v3::Response::new()) } /// # `GET /_matrix/client/r0/room_keys/version` @@ -105,7 +105,7 @@ pub(crate) async fn delete_backup_version_route( .delete_backup(body.sender_user(), &body.version) .await; - Ok(delete_backup_version::v3::Response {}) + Ok(delete_backup_version::v3::Response::new()) } /// # `PUT /_matrix/client/r0/room_keys/keys` @@ -292,7 +292,7 @@ pub(crate) async fn get_backup_keys_route( .get_all(body.sender_user(), &body.version) .await; - Ok(get_backup_keys::v3::Response { rooms }) + Ok(get_backup_keys::v3::Response::new(rooms)) } /// # `GET /_matrix/client/r0/room_keys/keys/{roomId}` @@ -307,7 +307,7 @@ pub(crate) async fn get_backup_keys_for_room_route( .get_room(body.sender_user(), &body.version, &body.room_id) .await; - Ok(get_backup_keys_for_room::v3::Response { sessions }) + Ok(get_backup_keys_for_room::v3::Response::new(sessions)) } /// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` @@ -325,7 +325,7 @@ pub(crate) async fn get_backup_keys_for_session_route( err!(Request(NotFound(debug_error!("Backup key not found for this user's session.")))) })?; - Ok(get_backup_keys_for_session::v3::Response { key_data }) + Ok(get_backup_keys_for_session::v3::Response::new(key_data)) } /// # `DELETE /_matrix/client/r0/room_keys/keys` @@ -342,7 +342,7 @@ pub(crate) async fn delete_backup_keys_route( let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; - Ok(delete_backup_keys::v3::Response { count, etag }) + Ok(delete_backup_keys::v3::Response::new(etag, count)) } /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}` @@ -359,7 +359,7 @@ pub(crate) async fn delete_backup_keys_for_room_route( let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; - Ok(delete_backup_keys_for_room::v3::Response { count, etag }) + Ok(delete_backup_keys_for_room::v3::Response::new(etag, count)) } /// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` @@ -376,7 +376,7 @@ pub(crate) async fn delete_backup_keys_for_session_route( let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await?; - Ok(delete_backup_keys_for_session::v3::Response { count, etag }) + Ok(delete_backup_keys_for_session::v3::Response::new(etag, count)) } async fn get_count_etag( diff --git a/src/api/client/capabilities.rs b/src/api/client/capabilities.rs index 405d11a0e..b2d537c27 100644 --- a/src/api/client/capabilities.rs +++ b/src/api/client/capabilities.rs @@ -56,5 +56,5 @@ pub(crate) async fn get_capabilities_route( capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?; } - Ok(get_capabilities::v3::Response { capabilities }) + Ok(get_capabilities::v3::Response::new(capabilities)) } diff --git a/src/api/client/dehydrated_device.rs b/src/api/client/dehydrated_device.rs index e665ec417..9ea7885e2 100644 --- a/src/api/client/dehydrated_device.rs +++ b/src/api/client/dehydrated_device.rs @@ -33,7 +33,7 @@ pub(crate) async fn put_dehydrated_device_route( .set_dehydrated_device(sender_user, body.body) .await?; - Ok(put_dehydrated_device::Response { device_id }) + Ok(put_dehydrated_device::Response::new(device_id)) } /// # `DELETE /_matrix/client/../dehydrated_device` @@ -51,7 +51,7 @@ pub(crate) async fn delete_dehydrated_device_route( services.users.remove_device(sender_user, &device_id).await; - Ok(delete_dehydrated_device::Response { device_id }) + Ok(delete_dehydrated_device::Response::new(device_id)) } /// # `GET /_matrix/client/../dehydrated_device` @@ -67,10 +67,7 @@ pub(crate) async fn get_dehydrated_device_route( let device = services.users.get_dehydrated_device(sender_user).await?; - Ok(get_dehydrated_device::Response { - device_id: device.device_id, - device_data: device.device_data, - }) + Ok(get_dehydrated_device::Response::new(device.device_id, device.device_data)) } /// # `GET /_matrix/client/../dehydrated_device/{device_id}/events` diff --git a/src/api/client/device.rs b/src/api/client/device.rs index aea7b6737..932ada3ce 100644 --- a/src/api/client/device.rs +++ b/src/api/client/device.rs @@ -25,7 +25,7 @@ pub(crate) async fn get_devices_route( .collect() .await; - Ok(get_devices::v3::Response { devices }) + Ok(get_devices::v3::Response::new(devices)) } /// # `GET /_matrix/client/r0/devices/{deviceId}` @@ -41,7 +41,7 @@ pub(crate) async fn get_device_route( .await .map_err(|_| err!(Request(NotFound("Device not found."))))?; - Ok(get_device::v3::Response { device }) + Ok(get_device::v3::Response::new(device)) } /// # `PUT /_matrix/client/r0/devices/{deviceId}` @@ -73,19 +73,15 @@ pub(crate) async fn update_device_route( .update_device_metadata(sender_user, &body.device_id, &device) .await?; - Ok(update_device::v3::Response {}) + Ok(update_device::v3::Response::new()) }, | Err(_) => { let Some(appservice) = appservice else { return Err!(Request(NotFound("Device not found."))); }; - if !appservice.registration.device_management { - return Err!(Request(NotFound("Device not found."))); - } debug!( - "Creating new device for {sender_user} from appservice {} as MSC4190 is enabled \ - and device ID does not exist", + "Creating new device for {sender_user} from appservice {} as device ID does not exist", appservice.registration.id ); @@ -102,7 +98,7 @@ pub(crate) async fn update_device_route( ) .await?; - return Ok(update_device::v3::Response {}); + return Ok(update_device::v3::Response::new()); }, } } @@ -124,17 +120,16 @@ pub(crate) async fn delete_device_route( let sender_user = body.sender_user(); let appservice = body.appservice_info.as_ref(); - if appservice.is_some_and(|appservice| appservice.registration.device_management) { + if appservice.is_some() { debug!( - "Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \ - enabled" + "Skipping UIAA for {sender_user} as this is from an appservice" ); services .users .remove_device(sender_user, &body.device_id) .await; - return Ok(delete_device::v3::Response {}); + return Ok(delete_device::v3::Response::new()); } // Prompt the user to confirm with their password using UIAA diff --git a/src/api/client/directory.rs b/src/api/client/directory.rs index 86321f66b..a93e97693 100644 --- a/src/api/client/directory.rs +++ b/src/api/client/directory.rs @@ -17,8 +17,7 @@ future::{join, join4, join5}, }; use ruma::{ - OwnedRoomId, RoomId, ServerName, UInt, UserId, - api::{ + OwnedRoomId, RoomId, ServerName, UInt, UserId, api::{ client::{ directory::{ get_public_rooms, get_public_rooms_filtered, get_room_visibility, @@ -27,17 +26,14 @@ room, }, federation, - }, - directory::{Filter, PublicRoomJoinRule, PublicRoomsChunk, RoomNetwork, RoomTypeFilter}, - events::{ + }, directory::{Filter, PublicRoomsChunk, RoomNetwork, RoomTypeFilter}, events::{ StateEventType, room::{ create::RoomCreateEventContent, join_rules::{JoinRule, RoomJoinRulesEventContent}, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, }, - }, - uint, + }, room::JoinRuleKind, uint }; use tokio::join; @@ -425,7 +421,7 @@ async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> Public .state_accessor .room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "") .map_ok(|c: RoomJoinRulesEventContent| match c.join_rule { - | JoinRule::Public => PublicRoomJoinRule::Public, + | JoinRule::Public => JoinRuleKind::Public, | JoinRule::Knock => "knock".into(), | JoinRule::KnockRestricted(_) => "knock_restricted".into(), | _ => "invite".into(), diff --git a/src/api/client/media.rs b/src/api/client/media.rs index c3ea1ec93..ef7f848ec 100644 --- a/src/api/client/media.rs +++ b/src/api/client/media.rs @@ -13,7 +13,7 @@ }; use reqwest::Url; use ruma::{ - Mxc, UserId, + UserId, api::client::{ authenticated_media::{ get_content, get_content_as_filename, get_content_thumbnail, get_media_config, diff --git a/src/api/client/membership/ban.rs b/src/api/client/membership/ban.rs index 8e46e4a90..331aa1a86 100644 --- a/src/api/client/membership/ban.rs +++ b/src/api/client/membership/ban.rs @@ -24,30 +24,29 @@ pub(crate) async fn ban_user_route( return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); } - let state_lock = services.rooms.state.mutex.lock(&body.room_id).await; + let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await; - let current_member_content = services + let mut content = services .rooms .state_accessor .get_member(&body.room_id, &body.user_id) .await .unwrap_or_else(|_| RoomMemberEventContent::new(MembershipState::Ban)); + content.membership = MembershipState::Ban; + content.reason = body.reason.clone(); + content.displayname = None; + content.avatar_url = None; + content.is_direct = None; + content.join_authorized_via_users_server = None; + content.third_party_invite = None; + // TODO(upstream): MSC4293 + services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent { - membership: MembershipState::Ban, - reason: body.reason.clone(), - displayname: None, // display name may be offensive - avatar_url: None, // avatar may be offensive - is_direct: None, - join_authorized_via_users_server: None, - third_party_invite: None, - redact_events: body.redact_events, - ..current_member_content - }), + PduBuilder::state(body.user_id.to_string(), &content), sender_user, Some(&body.room_id), &state_lock, diff --git a/src/api/client/membership/invite.rs b/src/api/client/membership/invite.rs index e3ea03b1d..c38ac463b 100644 --- a/src/api/client/membership/invite.rs +++ b/src/api/client/membership/invite.rs @@ -8,12 +8,10 @@ use futures::FutureExt; use ruma::{ RoomId, UserId, - api::{client::membership::invite_user, federation::membership::create_invite}, - events::{ - invite_permission_config::FilterLevel, - room::member::{MembershipState, RoomMemberEventContent}, - }, + api::{client::membership::invite_user, federation::membership::{RawStrippedState, create_invite}}, + events::room::member::{MembershipState, RoomMemberEventContent}, }; +use ruminuwuity::invite_permission_config::FilterLevel; use service::Services; use super::banned_room_check; @@ -59,7 +57,7 @@ pub(crate) async fn invite_user_route( if !matches!(sender_filter_level, FilterLevel::Allow) { // drop invites if the sender has the recipient filtered - return Ok(invite_user::v3::Response {}); + return Ok(invite_user::v3::Response::new()); } if let Ok(target_user_membership) = services @@ -101,7 +99,7 @@ pub(crate) async fn invite_user_route( .boxed() .await?; - Ok(invite_user::v3::Response {}) + Ok(invite_user::v3::Response::new()) }, | _ => { Err!(Request(NotFound("User not found."))) @@ -141,12 +139,11 @@ pub(crate) async fn invite_helper( let (pdu, pdu_json, invite_room_state) = { let state_lock = services.rooms.state.mutex.lock(room_id).await; - let content = RoomMemberEventContent { - avatar_url: services.users.avatar_url(recipient_user).await.ok(), - is_direct: Some(is_direct), - reason, - ..RoomMemberEventContent::new(MembershipState::Invite) - }; + let mut content = RoomMemberEventContent::new(MembershipState::Invite); + content.displayname = services.users.displayname(recipient_user).await.ok(); + content.avatar_url = services.users.avatar_url(recipient_user).await.ok(); + content.is_direct = Some(is_direct); + content.reason = reason; let (pdu, pdu_json) = services .rooms @@ -159,7 +156,15 @@ pub(crate) async fn invite_helper( ) .await?; - let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await; + #[allow(deprecated)] + let invite_room_state = services + .rooms + .state + .summary_stripped(&pdu, room_id) + .await + .into_iter() + .map(|event| RawStrippedState::Stripped(event)) + .collect(); drop(state_lock); @@ -168,24 +173,26 @@ pub(crate) async fn invite_helper( let room_version_id = services.rooms.state.get_room_version(room_id).await?; + let mut request = create_invite::v2::Request::new( + room_id.to_owned(), + (*pdu.event_id).to_owned(), + room_version_id.clone(), + services + .sending + .convert_to_outgoing_federation_event(pdu_json.clone()) + .await, + invite_room_state, + ); + request.via = services + .rooms + .state_cache + .servers_route_via(room_id) + .await + .ok(); + let response = services .sending - .send_federation_request(recipient_user.server_name(), create_invite::v2::Request { - room_id: room_id.to_owned(), - event_id: (*pdu.event_id).to_owned(), - room_version: room_version_id.clone(), - event: services - .sending - .convert_to_outgoing_federation_event(pdu_json.clone()) - .await, - invite_room_state, - via: services - .rooms - .state_cache - .servers_route_via(room_id) - .await - .ok(), - }) + .send_federation_request(recipient_user.server_name(), request) .await?; // We do not add the event_id field to the pdu here because of signature and @@ -229,14 +236,12 @@ pub(crate) async fn invite_helper( let state_lock = services.rooms.state.mutex.lock(room_id).await; - let content = RoomMemberEventContent { - displayname: services.users.displayname(recipient_user).await.ok(), - avatar_url: services.users.avatar_url(recipient_user).await.ok(), - blurhash: services.users.blurhash(recipient_user).await.ok(), - is_direct: Some(is_direct), - reason, - ..RoomMemberEventContent::new(MembershipState::Invite) - }; + let mut content = RoomMemberEventContent::new(MembershipState::Invite); + content.displayname = services.users.displayname(recipient_user).await.ok(); + content.avatar_url = services.users.avatar_url(recipient_user).await.ok(); + content.blurhash = services.users.blurhash(recipient_user).await.ok(); + content.is_direct = Some(is_direct); + content.reason = reason; services .rooms diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index 4c3a53d02..10004c2a2 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -89,7 +89,6 @@ pub(crate) async fn join_room_by_id_route( .rooms .state_cache .servers_invite_via(&body.room_id) - .map(ToOwned::to_owned) .collect() .await; @@ -168,7 +167,6 @@ pub(crate) async fn join_room_by_id_or_alias_route( .rooms .state_cache .servers_invite_via(&room_id) - .map(ToOwned::to_owned) .collect::>() .await, ); @@ -212,8 +210,7 @@ pub(crate) async fn join_room_by_id_or_alias_route( let addl_via_servers = services .rooms .state_cache - .servers_invite_via(&room_id) - .map(ToOwned::to_owned); + .servers_invite_via(&room_id); let addl_state_servers = services .rooms @@ -226,7 +223,7 @@ pub(crate) async fn join_room_by_id_or_alias_route( .iter() .map(|event| event.get_field("sender")) .filter_map(FlatOk::flat_ok) - .map(|user: &UserId| user.server_name().to_owned()) + .map(|user: OwnedUserId| user.server_name().to_owned()) .stream() .chain(addl_via_servers) .collect() @@ -252,7 +249,7 @@ pub(crate) async fn join_room_by_id_or_alias_route( .boxed() .await?; - Ok(join_room_by_id_or_alias::v3::Response { room_id: join_room_response.room_id }) + Ok(join_room_by_id_or_alias::v3::Response::new(join_room_response.room_id)) } pub async fn join_room_by_id_helper( @@ -283,7 +280,7 @@ pub async fn join_room_by_id_helper( .await { debug_warn!("{sender_user} is already joined in {room_id}"); - return Ok(join_room_by_id::v3::Response { room_id: room_id.into() }); + return Ok(join_room_by_id::v3::Response::new(room_id.to_owned())); } if let Err(e) = services @@ -423,16 +420,17 @@ async fn join_room_by_id_helper_remote( .expect("Timestamp is valid js_int value"), ), ); + + let mut join_content = RoomMemberEventContent::new(MembershipState::Join); + join_content.displayname = services.users.displayname(sender_user).await.ok(); + join_content.avatar_url = services.users.avatar_url(sender_user).await.ok(); + join_content.blurhash = services.users.blurhash(sender_user).await.ok(); + join_content.reason = reason; + join_content.join_authorized_via_users_server = join_authorized_via_users_server.clone(); + join_event_stub.insert( "content".to_owned(), - to_canonical_value(RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - reason, - join_authorized_via_users_server: join_authorized_via_users_server.clone(), - ..RoomMemberEventContent::new(MembershipState::Join) - }) + to_canonical_value(join_content) .expect("event is valid, we just created it"), ); @@ -462,15 +460,10 @@ async fn join_room_by_id_helper_remote( let mut join_event = join_event_stub; info!("Asking {remote_server} for send_join in room {room_id}"); - let send_join_request = federation::membership::create_join_event::v2::Request { - room_id: room_id.to_owned(), - event_id: event_id.clone(), - omit_members: false, - pdu: services + let send_join_request = federation::membership::create_join_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services .sending .convert_to_outgoing_federation_event(join_event.clone()) - .await, - }; + .await); let send_join_response = match services .sending @@ -638,7 +631,7 @@ async fn join_room_by_id_helper_remote( }; let auth_check = state_res::event_auth::auth_check( - &state_res::RoomVersion::new(&room_version_id)?, + &room_version_id.rules().unwrap(), &parsed_join_pdu, None, // TODO: third party invite |k, s| state_fetch(k.clone(), s.into()), @@ -759,14 +752,12 @@ async fn join_room_by_id_helper_local( } } - let content = RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - reason: reason.clone(), - join_authorized_via_users_server: auth_user, - ..RoomMemberEventContent::new(MembershipState::Join) - }; + let mut content = RoomMemberEventContent::new(MembershipState::Join); + content.displayname = services.users.displayname(sender_user).await.ok(); + content.avatar_url = services.users.avatar_url(sender_user).await.ok(); + content.blurhash = services.users.blurhash(sender_user).await.ok(); + content.reason = reason.clone(); + content.join_authorized_via_users_server = auth_user; // Try normal join first let Err(error) = services @@ -822,15 +813,15 @@ async fn make_join_request( "Asking {remote_server} for make_join (attempt {make_join_counter}/{})", servers.len() ); + + let mut request = federation::membership::prepare_join_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned()); + request.ver = services.server.supported_room_versions().collect(); + let make_join_response = services .sending .send_federation_request( remote_server, - federation::membership::prepare_join_event::v1::Request { - room_id: room_id.to_owned(), - user_id: sender_user.to_owned(), - ver: services.server.supported_room_versions().collect(), - }, + request ) .await; diff --git a/src/api/client/membership/kick.rs b/src/api/client/membership/kick.rs index 934385454..ee166aa57 100644 --- a/src/api/client/membership/kick.rs +++ b/src/api/client/membership/kick.rs @@ -18,9 +18,9 @@ pub(crate) async fn kick_user_route( if services.users.is_suspended(sender_user).await? { return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); } - let state_lock = services.rooms.state.mutex.lock(&body.room_id).await; + let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await; - let Ok(event) = services + let Ok(mut event) = services .rooms .state_accessor .get_member(&body.room_id, &body.user_id) @@ -41,18 +41,17 @@ pub(crate) async fn kick_user_route( ))); } + event.membership = MembershipState::Leave; + event.reason = body.reason.clone(); + event.is_direct = None; + event.join_authorized_via_users_server = None; + event.third_party_invite = None; + services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent { - membership: MembershipState::Leave, - reason: body.reason.clone(), - is_direct: None, - join_authorized_via_users_server: None, - third_party_invite: None, - ..event - }), + PduBuilder::state(body.user_id.to_string(), &event), sender_user, Some(&body.room_id), &state_lock, diff --git a/src/api/client/membership/knock.rs b/src/api/client/membership/knock.rs index e589aa11c..31290f9a8 100644 --- a/src/api/client/membership/knock.rs +++ b/src/api/client/membership/knock.rs @@ -15,20 +15,16 @@ }; use futures::{FutureExt, StreamExt}; use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, RoomId, - RoomVersionId, UserId, - api::{ + CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, RoomVersionId, UserId, api::{ client::knock::knock_room, federation::{self}, - }, - canonical_json::to_canonical_value, - events::{ + }, canonical_json::to_canonical_value, events::{ StateEventType, room::{ join_rules::{AllowRule, JoinRule}, member::{MembershipState, RoomMemberEventContent}, }, - }, + } }; use service::{ Services, @@ -73,7 +69,6 @@ pub(crate) async fn knock_room_route( .rooms .state_cache .servers_invite_via(&room_id) - .map(ToOwned::to_owned) .collect::>() .await, ); @@ -116,8 +111,7 @@ pub(crate) async fn knock_room_route( let addl_via_servers = services .rooms .state_cache - .servers_invite_via(&room_id) - .map(ToOwned::to_owned); + .servers_invite_via(&room_id); let addl_state_servers = services .rooms @@ -130,7 +124,7 @@ pub(crate) async fn knock_room_route( .iter() .map(|event| event.get_field("sender")) .filter_map(FlatOk::flat_ok) - .map(|user: &UserId| user.server_name().to_owned()) + .map(|user: OwnedUserId| user.server_name().to_owned()) .stream() .chain(addl_via_servers) .collect() @@ -188,7 +182,7 @@ async fn knock_room_by_id_helper( .await { debug_warn!("{sender_user} is already knocked in {room_id}"); - return Ok(knock_room::v3::Response { room_id: room_id.into() }); + return Ok(knock_room::v3::Response::new(room_id.into())); } if let Ok(membership) = services @@ -353,13 +347,11 @@ async fn knock_room_helper_local( return Err!(Request(Forbidden("This room does not support knocking."))); } - let content = RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - reason: reason.clone(), - ..RoomMemberEventContent::new(MembershipState::Knock) - }; + let mut content = RoomMemberEventContent::new(MembershipState::Knock); + content.displayname = services.users.displayname(sender_user).await.ok(); + content.avatar_url = services.users.avatar_url(sender_user).await.ok(); + content.blurhash = services.users.blurhash(sender_user).await.ok(); + content.reason = reason.clone(); // Try normal knock first let Err(error) = services @@ -424,13 +416,7 @@ async fn knock_room_helper_local( ); knock_event_stub.insert( "content".to_owned(), - to_canonical_value(RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - reason, - ..RoomMemberEventContent::new(MembershipState::Knock) - }) + to_canonical_value(content) .expect("event is valid, we just created it"), ); @@ -451,14 +437,14 @@ async fn knock_room_helper_local( let knock_event = knock_event_stub; info!("Asking {remote_server} for send_knock in room {room_id}"); - let send_knock_request = federation::knock::send_knock::v1::Request { - room_id: room_id.to_owned(), - event_id: event_id.clone(), - pdu: services + let send_knock_request = federation::membership::create_knock_event::v1::Request::new( + room_id.to_owned(), + event_id.clone(), + services .sending .convert_to_outgoing_federation_event(knock_event.clone()) .await, - }; + ); services .sending @@ -545,15 +531,16 @@ async fn knock_room_helper_remote( .expect("Timestamp is valid js_int value"), ), ); + + let mut knock_content = RoomMemberEventContent::new(MembershipState::Knock); + knock_content.displayname = services.users.displayname(sender_user).await.ok(); + knock_content.avatar_url = services.users.avatar_url(sender_user).await.ok(); + knock_content.blurhash = services.users.blurhash(sender_user).await.ok(); + knock_content.reason = reason; + knock_event_stub.insert( "content".to_owned(), - to_canonical_value(RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - reason, - ..RoomMemberEventContent::new(MembershipState::Knock) - }) + to_canonical_value(knock_content) .expect("event is valid, we just created it"), ); @@ -574,18 +561,18 @@ async fn knock_room_helper_remote( let knock_event = knock_event_stub; info!("Asking {remote_server} for send_knock in room {room_id}"); - let send_knock_request = federation::knock::send_knock::v1::Request { - room_id: room_id.to_owned(), - event_id: event_id.clone(), - pdu: services + let request = federation::membership::create_knock_event::v1::Request::new( + room_id.to_owned(), + event_id.clone(), + services .sending .convert_to_outgoing_federation_event(knock_event.clone()) - .await, - }; + .await + ); let send_knock_response = services .sending - .send_federation_request(&remote_server, send_knock_request) + .send_federation_request(&remote_server, request) .await?; info!("send_knock finished"); @@ -604,7 +591,20 @@ async fn knock_room_helper_remote( let state = send_knock_response .knock_room_state .iter() - .map(|event| serde_json::from_str::(event.clone().into_json().get())) + .map(|event| { + #[allow(deprecated)] + let raw_value = match event { + federation::membership::RawStrippedState::Stripped(raw_state) => { + &raw_state.clone().into_json() + }, + federation::membership::RawStrippedState::Pdu(raw_value) => { + raw_value + }, + _ => panic!("unknown raw stripped state type"), + }; + + serde_json::from_str::(raw_value.get()) + }) .filter_map(Result::ok); let mut state_map: HashMap = HashMap::new(); @@ -709,7 +709,7 @@ async fn make_knock_request( sender_user: &UserId, room_id: &RoomId, servers: &[OwnedServerName], -) -> Result<(federation::knock::create_knock_event_template::v1::Response, OwnedServerName)> { +) -> Result<(federation::membership::prepare_knock_event::v1::Response, OwnedServerName)> { let mut make_knock_response_and_server = Err!(BadServerResponse("No server available to assist in knocking.")); @@ -722,15 +722,14 @@ async fn make_knock_request( info!("Asking {remote_server} for make_knock ({make_knock_counter})"); + let mut request = federation::membership::prepare_knock_event::v1::Request::new(room_id.to_owned(), sender_user.to_owned()); + request.ver = services.server.supported_room_versions().collect(); + let make_knock_response = services .sending .send_federation_request( remote_server, - federation::knock::create_knock_event_template::v1::Request { - room_id: room_id.to_owned(), - user_id: sender_user.to_owned(), - ver: services.server.supported_room_versions().collect(), - }, + request, ) .await; diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index db98907ef..211a61f87 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -45,8 +45,7 @@ pub async fn leave_all_rooms(services: &Services, user_id: &UserId) { let rooms_joined = services .rooms .state_cache - .rooms_joined(user_id) - .map(ToOwned::to_owned); + .rooms_joined(user_id); let rooms_invited = services .rooms @@ -142,18 +141,17 @@ pub async fn leave_room( .await; match user_member_event_content { - | Ok(content) => { + | Ok(mut content) => { + content.membership = MembershipState::Leave; + content.reason = reason; + content.join_authorized_via_users_server = None; + content.is_direct = None; + services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(user_id.to_string(), &RoomMemberEventContent { - membership: MembershipState::Leave, - reason, - join_authorized_via_users_server: None, - is_direct: None, - ..content - }), + PduBuilder::state(user_id.to_string(), &content), user_id, Some(room_id), &state_lock, @@ -226,7 +224,6 @@ pub async fn remote_leave_room( .rooms .state_cache .servers_invite_via(room_id) - .map(ToOwned::to_owned) .collect::>() .await, ); @@ -260,7 +257,7 @@ pub async fn remote_leave_room( .filter_map(|event| event.get_field("sender").ok().flatten()) .filter_map(|sender: &str| UserId::parse(sender).ok()) .filter_map(|sender| { - if !services.globals.user_is_local(sender) { + if !services.globals.user_is_local(&sender) { Some(sender.server_name().to_owned()) } else { None @@ -289,10 +286,7 @@ pub async fn remote_leave_room( .sending .send_federation_request( remote_server.as_ref(), - federation::membership::prepare_leave_event::v1::Request { - room_id: room_id.to_owned(), - user_id: user_id.to_owned(), - }, + federation::membership::prepare_leave_event::v1::Request::new(room_id.to_owned(), user_id.to_owned()) ) .await; @@ -393,14 +387,10 @@ pub async fn remote_leave_room( .sending .send_federation_request( &remote_server, - federation::membership::create_leave_event::v2::Request { - room_id: room_id.to_owned(), - event_id: event_id.clone(), - pdu: services + federation::membership::create_leave_event::v2::Request::new(room_id.to_owned(), event_id.clone(), services .sending .convert_to_outgoing_federation_event(leave_event.clone()) - .await, - }, + .await), ) .await?; diff --git a/src/api/client/membership/members.rs b/src/api/client/membership/members.rs index 05ba1c431..72bcdbd8b 100644 --- a/src/api/client/membership/members.rs +++ b/src/api/client/membership/members.rs @@ -9,7 +9,7 @@ use futures::{FutureExt, StreamExt, future::join}; use ruma::{ api::client::membership::{ - get_member_events::{self, v3::MembershipEventFilter}, + get_member_events::{self}, joined_members::{self, v3::RoomMember}, }, events::{ @@ -43,8 +43,7 @@ pub(crate) async fn get_member_events_route( return Err!(Request(Forbidden("You don't have permission to view this room."))); } - Ok(get_member_events::v3::Response { - chunk: services + let chunk = services .rooms .state_accessor .room_state_full(&body.room_id) @@ -55,8 +54,9 @@ pub(crate) async fn get_member_events_route( .map(Event::into_format) .collect() .boxed() - .await, - }) + .await; + + Ok(get_member_events::v3::Response::new(chunk)) } /// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members` @@ -78,70 +78,44 @@ pub(crate) async fn joined_members_route( return Err!(Request(Forbidden("You don't have permission to view this room."))); } - Ok(joined_members::v3::Response { - joined: services + let joined = services .rooms .state_cache .room_members(&body.room_id) - .map(ToOwned::to_owned) .broad_then(|user_id| async move { + let mut member = RoomMember::new(); let (display_name, avatar_url) = join( services.users.displayname(&user_id).ok(), services.users.avatar_url(&user_id).ok(), ) .await; + member.display_name = display_name; + member.avatar_url = avatar_url; - (user_id, RoomMember { display_name, avatar_url }) + (user_id, member) }) .collect() - .await, - }) + .await; + + Ok(joined_members::v3::Response::new(joined)) } fn membership_filter( pdu: Pdu, - for_membership: Option<&MembershipEventFilter>, - not_membership: Option<&MembershipEventFilter>, + membership_state_filter: Option<&MembershipState>, + not_membership_state_filter: Option<&MembershipState>, ) -> Option { - let membership_state_filter = match for_membership { - | Some(MembershipEventFilter::Ban) => MembershipState::Ban, - | Some(MembershipEventFilter::Invite) => MembershipState::Invite, - | Some(MembershipEventFilter::Knock) => MembershipState::Knock, - | Some(MembershipEventFilter::Leave) => MembershipState::Leave, - | Some(_) | None => MembershipState::Join, - }; - - let not_membership_state_filter = match not_membership { - | Some(MembershipEventFilter::Ban) => MembershipState::Ban, - | Some(MembershipEventFilter::Invite) => MembershipState::Invite, - | Some(MembershipEventFilter::Join) => MembershipState::Join, - | Some(MembershipEventFilter::Knock) => MembershipState::Knock, - | Some(_) | None => MembershipState::Leave, - }; - let evt_membership = pdu.get_content::().ok()?.membership; - if for_membership.is_some() && not_membership.is_some() { - if membership_state_filter != evt_membership - || not_membership_state_filter == evt_membership - { - None - } else { - Some(pdu) - } - } else if for_membership.is_some() && not_membership.is_none() { - if membership_state_filter != evt_membership { - None - } else { - Some(pdu) - } - } else if not_membership.is_some() && for_membership.is_none() { - if not_membership_state_filter == evt_membership { - None - } else { - Some(pdu) - } - } else { - Some(pdu) + if let Some(membership_state_filter) = membership_state_filter + && *membership_state_filter != evt_membership { + return None; } + + if let Some(not_membership_state_filter) = not_membership_state_filter + && *not_membership_state_filter == evt_membership { + return None; + } + + return Some(pdu); } diff --git a/src/api/client/membership/mod.rs b/src/api/client/membership/mod.rs index 6f1bb3e78..ad38eabd8 100644 --- a/src/api/client/membership/mod.rs +++ b/src/api/client/membership/mod.rs @@ -47,15 +47,14 @@ pub(crate) async fn joined_rooms_route( State(services): State, body: Ruma, ) -> Result { - Ok(joined_rooms::v3::Response { - joined_rooms: services + let joined_rooms = services .rooms .state_cache .rooms_joined(body.sender_user()) - .map(ToOwned::to_owned) .collect() - .await, - }) + .await; + + Ok(joined_rooms::v3::Response::new(joined_rooms)) } /// Checks if the room is banned in any way possible and the sender user is not diff --git a/src/api/client/membership/unban.rs b/src/api/client/membership/unban.rs index 20a28268a..3ddd60b3e 100644 --- a/src/api/client/membership/unban.rs +++ b/src/api/client/membership/unban.rs @@ -18,9 +18,9 @@ pub(crate) async fn unban_user_route( if services.users.is_suspended(sender_user).await? { return Err!(Request(UserSuspended("You cannot perform this action while suspended."))); } - let state_lock = services.rooms.state.mutex.lock(&body.room_id).await; + let state_lock = services.rooms.state.mutex.lock(body.room_id.as_str()).await; - let current_member_content = services + let mut current_member_content = services .rooms .state_accessor .get_member(&body.room_id, &body.user_id) @@ -34,18 +34,17 @@ pub(crate) async fn unban_user_route( ))); } + current_member_content.membership = MembershipState::Leave; + current_member_content.reason = body.reason.clone(); + current_member_content.join_authorized_via_users_server = None; + current_member_content.third_party_invite = None; + current_member_content.is_direct = None; + services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(body.user_id.to_string(), &RoomMemberEventContent { - membership: MembershipState::Leave, - reason: body.reason.clone(), - join_authorized_via_users_server: None, - third_party_invite: None, - is_direct: None, - ..current_member_content - }), + PduBuilder::state(body.user_id.to_string(), ¤t_member_content), sender_user, Some(&body.room_id), &state_lock, diff --git a/src/api/client/report.rs b/src/api/client/report.rs index f6137ba0b..e582ea886 100644 --- a/src/api/client/report.rs +++ b/src/api/client/report.rs @@ -7,7 +7,7 @@ use ruma::{ EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::{ - report_user, + reporting::report_user, room::{report_content, report_room}, }, events::{Mentions, room::message::RoomMessageEventContent}, diff --git a/src/api/client/room/aliases.rs b/src/api/client/room/aliases.rs index 0b072b745..b7de681ac 100644 --- a/src/api/client/room/aliases.rs +++ b/src/api/client/room/aliases.rs @@ -26,13 +26,12 @@ pub(crate) async fn get_room_aliases_route( return Err!(Request(Forbidden("You don't have permission to view this room.",))); } - Ok(aliases::v3::Response { - aliases: services + let aliases = services .rooms .alias .local_aliases_for_room(&body.room_id) - .map(ToOwned::to_owned) .collect() - .await, - }) + .await; + + Ok(aliases::v3::Response::new(aliases)) } diff --git a/src/api/client/room/create.rs b/src/api/client/room/create.rs index 448dd5c44..7051e081b 100644 --- a/src/api/client/room/create.rs +++ b/src/api/client/room/create.rs @@ -2,18 +2,15 @@ use axum::extract::State; use conduwuit::{ - Err, Result, RoomVersion, debug, debug_info, debug_warn, err, info, + Err, Result, debug, debug_info, debug_warn, err, info, matrix::{StateKey, pdu::PduBuilder}, trace, warn, }; use conduwuit_service::{Services, appservice::RegistrationInfo}; use futures::FutureExt; use ruma::{ - CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, - api::client::room::{self, create_room}, - events::{ + CanonicalJsonObject, Int, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, RoomId, RoomVersionId, api::client::room::{self, create_room}, events::{ TimelineEventType, - invite_permission_config::FilterLevel, room::{ canonical_alias::RoomCanonicalAliasEventContent, create::RoomCreateEventContent, @@ -25,10 +22,9 @@ power_levels::RoomPowerLevelsEventContent, topic::RoomTopicEventContent, }, - }, - int, - serde::{JsonObject, Raw}, + }, int, room_version_rules::RoomIdFormatVersion, serde::{JsonObject, Raw} }; +use ruminuwuity::invite_permission_config::FilterLevel; use serde_json::{json, value::to_raw_value}; use crate::{Ruma, client::invite_helper}; @@ -81,15 +77,11 @@ pub(crate) async fn create_room_route( }, | None => services.server.config.default_room_version.clone(), }; - let room_features = RoomVersion::new(&room_version)?; + let room_version_rules = room_version.rules().unwrap(); - let room_id: Option = if !room_features.room_ids_as_hashes { - match &body.room_id { - | Some(custom_room_id) => Some(custom_room_id_check(&services, custom_room_id)?), - | None => Some(RoomId::new(services.globals.server_name())), - } - } else { - None + let room_id: Option = match room_version_rules.room_id_format { + RoomIdFormatVersion::V1 => Some(RoomId::new_v1(services.globals.server_name())), + _ => None, }; // check if room ID doesn't already exist instead of erroring on auth check @@ -167,7 +159,7 @@ pub(crate) async fn create_room_route( use RoomVersionId::*; let mut content = content - .deserialize_as::() + .deserialize_as_unchecked::() .map_err(|e| { err!(Request(BadJson(error!( "Failed to deserialise content as canonical JSON: {e}" @@ -201,8 +193,7 @@ pub(crate) async fn create_room_route( let content = match room_version { | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => RoomCreateEventContent::new_v1(sender_user.to_owned()), - | V11 => RoomCreateEventContent::new_v11(), - | _ => RoomCreateEventContent::new_v12(), + | _ => RoomCreateEventContent::new_v11(), }; let mut content = serde_json::from_str::(to_raw_value(&content)?.get())?; @@ -257,30 +248,23 @@ pub(crate) async fn create_room_route( }, }; drop(state_lock); - if let Some(expected_room_id) = body.room_id.as_ref() { - if expected_room_id.as_str() != room_id.as_str() { - return Err!(Request(InvalidParam( - "Custom room ID {expected_room_id} does not match the generated room ID \ - {room_id}.", - ))); - } - } debug!("Room created with ID {room_id}"); - let state_lock = services.rooms.state.mutex.lock(&room_id).await; + let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await; // 2. Let the room creator join + + let mut join_event = RoomMemberEventContent::new(MembershipState::Join); + join_event.displayname = services.users.displayname(sender_user).await.ok(); + join_event.avatar_url = services.users.avatar_url(sender_user).await.ok(); + join_event.blurhash = services.users.blurhash(sender_user).await.ok(); + join_event.is_direct = Some(body.is_direct); + debug_info!("Joining {sender_user} to room {room_id}"); services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent { - displayname: services.users.displayname(sender_user).await.ok(), - avatar_url: services.users.avatar_url(sender_user).await.ok(), - blurhash: services.users.blurhash(sender_user).await.ok(), - is_direct: Some(body.is_direct), - ..RoomMemberEventContent::new(MembershipState::Join) - }), + PduBuilder::state(sender_user.to_string(), &join_event), sender_user, Some(&room_id), &state_lock, @@ -306,7 +290,7 @@ pub(crate) async fn create_room_route( let mut creators: Vec = vec![sender_user.to_owned()]; // Do we care about additional_creators? - if room_features.explicitly_privilege_room_creators { + if room_version_rules.explicitly_privilege_room_creators { // Have they been specified? if let Some(additional_creators) = create_content.get("additional_creators") { // Are they a real array? @@ -667,60 +651,3 @@ async fn room_alias_check( Ok(full_room_alias) } - -/// if a room is being created with a custom room ID, run our checks against it -fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result { - // apply forbidden room alias checks to custom room IDs too - if services - .globals - .forbidden_alias_names() - .is_match(custom_room_id) - { - return Err!(Request(Unknown("Custom room ID is forbidden."))); - } - - if custom_room_id.contains(':') { - return Err!(Request(InvalidParam( - "Custom room ID contained `:` which is not allowed. Please note that this expects a \ - localpart, not the full room ID.", - ))); - } else if custom_room_id.contains(char::is_whitespace) { - return Err!(Request(InvalidParam( - "Custom room ID contained spaces which is not valid." - ))); - } - - let server_name = services.globals.server_name(); - let mut room_id = custom_room_id.to_owned(); - if custom_room_id.contains(':') { - if !custom_room_id.starts_with('!') { - return Err!(Request(InvalidParam( - "Custom room ID contains an unexpected `:` which is not allowed.", - ))); - } - } else if custom_room_id.starts_with('!') { - return Err!(Request(InvalidParam( - "Room ID is prefixed with !, but is not fully qualified. You likely did not want \ - this.", - ))); - } else { - room_id = format!("!{custom_room_id}:{server_name}"); - } - OwnedRoomId::parse(room_id) - .map_err(Into::into) - .and_then(|full_room_id| { - if full_room_id - .server_name() - .expect("failed to extract server name from room ID") - != server_name - { - Err!(Request(InvalidParam("Custom room ID must be on this server.",))) - } else { - Ok(full_room_id) - } - }) - .inspect(|full_room_id| { - debug_info!(%full_room_id, "Full custom room ID"); - }) - .inspect_err(|e| warn!(?e, %custom_room_id, "Failed to create room with custom room ID",)) -} diff --git a/src/api/router/handler.rs b/src/api/router/handler.rs index ab013945f..6d535c4c3 100644 --- a/src/api/router/handler.rs +++ b/src/api/router/handler.rs @@ -44,15 +44,16 @@ impl RumaHandler<($($tx,)* Ruma,)> for Fun $( $tx: FromRequestParts + Send + Sync + 'static, )* { fn add_routes(&'static self, router: Router) -> Router { - Req::METADATA - .history + use ruma::api::path_builder::PathBuilder; + + Req::PATH_BUILDER .all_paths() .fold(router, |router, path| self.add_route(router, path)) } fn add_route(&'static self, router: Router, path: &str) -> Router { let action = |$($tx,)* req| self($($tx,)* req).map_ok(RumaResponse); - let method = method_to_filter(&Req::METADATA.method); + let method = method_to_filter(&Req::METHOD); router.route(path, on(method, action)) } } diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index 04b89d5ce..7659b7071 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -57,10 +57,6 @@ pub enum Error { #[error(transparent)] Json(#[from] serde_json::Error), #[error(transparent)] - JsParseInt(#[from] ruma::JsParseIntError), // js_int re-export - #[error(transparent)] - JsTryFromInt(#[from] ruma::JsTryFromIntError), // js_int re-export - #[error(transparent)] Path(#[from] axum::extract::rejection::PathRejection), #[error("Mutex poisoned: {0}")] Poison(Cow<'static, str>), @@ -90,7 +86,7 @@ pub enum Error { // ruma/conduwuit #[error("Arithmetic operation failed: {0}")] Arithmetic(Cow<'static, str>), - #[error("{0}: {1}")] + #[error("{0:?}: {1}")] BadRequest(ruma::api::client::error::ErrorKind, &'static str), //TODO: remove #[error("{0}")] BadServerResponse(Cow<'static, str>), @@ -120,7 +116,7 @@ pub enum Error { Mxid(#[from] ruma::IdParseError), #[error("from {0}: {1}")] Redaction(ruma::OwnedServerName, ruma::canonical_json::RedactionError), - #[error("{0}: {1}")] + #[error("{0:?}: {1}")] Request(ruma::api::client::error::ErrorKind, Cow<'static, str>, http::StatusCode), #[error(transparent)] Ruma(#[from] ruma::api::client::error::Error), @@ -167,13 +163,13 @@ pub fn message(&self) -> String { /// Returns the Matrix error code / error kind #[inline] pub fn kind(&self) -> ruma::api::client::error::ErrorKind { - use ruma::api::client::error::ErrorKind::{FeatureDisabled, Unknown}; + use ruma::api::client::error::ErrorKind::{Unknown, Unrecognized}; match self { | Self::Federation(_, error) | Self::Ruma(error) => response::ruma_error_kind(error).clone(), | Self::BadRequest(kind, ..) | Self::Request(kind, ..) => kind.clone(), - | Self::FeatureDisabled(..) => FeatureDisabled, + | Self::FeatureDisabled(..) => Unrecognized, | _ => Unknown, } } diff --git a/src/core/error/response.rs b/src/core/error/response.rs index d1f09a998..8ccc465a3 100644 --- a/src/core/error/response.rs +++ b/src/core/error/response.rs @@ -4,7 +4,7 @@ use ruma::api::{ OutgoingResponse, client::{ - error::{ErrorBody, ErrorKind}, + error::{ErrorBody, ErrorKind, StandardErrorBody}, uiaa::UiaaResponse, }, }; @@ -51,15 +51,9 @@ fn from(error: Error) -> Self { return Self::AuthResponse(uiaainfo); } - let body = ErrorBody::Standard { - kind: error.kind(), - message: error.message(), - }; + let body = ErrorBody::Standard(StandardErrorBody::new(error.kind(), error.message())); - Self::MatrixError(ruma::api::client::error::Error { - status_code: error.status_code(), - body, - }) + Self::MatrixError(ruma::api::client::error::Error::new(error.status_code(), body)) } } @@ -85,7 +79,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode { | Unrecognized => StatusCode::METHOD_NOT_ALLOWED, // 404 - | NotFound | NotImplemented | FeatureDisabled | SenderIgnored { .. } => + | NotFound => StatusCode::NOT_FOUND, // 403 @@ -93,7 +87,6 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode { | ThreepidAuthFailed | UserDeactivated | ThreepidDenied - | InviteBlocked | WrongRoomKeysVersion { .. } | UserSuspended | Forbidden { .. } => StatusCode::FORBIDDEN, @@ -108,7 +101,7 @@ pub(super) fn bad_request_code(kind: &ErrorKind) -> StatusCode { } pub(super) fn ruma_error_message(error: &ruma::api::client::error::Error) -> String { - if let ErrorBody::Standard { message, .. } = &error.body { + if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &error.body { return message.clone(); } diff --git a/src/core/info/room_version.rs b/src/core/info/room_version.rs index 4ce492e5f..65d55bb08 100644 --- a/src/core/info/room_version.rs +++ b/src/core/info/room_version.rs @@ -2,7 +2,7 @@ use std::iter::once; -use ruma::{RoomVersionId, api::client::discovery::get_capabilities::RoomVersionStability}; +use ruma::{RoomVersionId, api::client::discovery::get_capabilities::v3::RoomVersionStability}; use crate::{at, is_equal_to}; diff --git a/src/core/matrix/event/filter.rs b/src/core/matrix/event/filter.rs index aa537d68d..e7207939e 100644 --- a/src/core/matrix/event/filter.rs +++ b/src/core/matrix/event/filter.rs @@ -67,7 +67,7 @@ fn matches_sender(event: &E, filter: &RoomEventFilter) -> bool { } fn matches_type(event: &E, filter: &RoomEventFilter) -> bool { - let kind = event.kind().to_cow_str(); + let kind = event.kind().to_string(); if filter.not_types.iter().any(is_equal_to!(&kind)) { return false; diff --git a/src/core/matrix/event/id.rs b/src/core/matrix/event/id.rs index e9d868b10..653c86f65 100644 --- a/src/core/matrix/event/id.rs +++ b/src/core/matrix/event/id.rs @@ -1,7 +1,7 @@ use ruma::{CanonicalJsonObject, OwnedEventId, RoomVersionId}; use serde_json::value::RawValue as RawJsonValue; -use crate::{Result, err}; +use crate::{Err, Result, err}; /// Generates a correct eventId for the incoming pdu. /// @@ -24,7 +24,10 @@ pub fn gen_event_id( value: &CanonicalJsonObject, room_version_id: &RoomVersionId, ) -> Result { - let reference_hash = ruma::signatures::reference_hash(value, room_version_id)?; + let Some(rules) = room_version_id.rules() else { + return Err!("Cannot generate event ID for unknown room version {room_version_id}") + }; + let reference_hash = ruma::signatures::reference_hash(value, &rules)?; let event_id: OwnedEventId = format!("${reference_hash}").try_into()?; Ok(event_id) diff --git a/src/core/matrix/event/type_ext.rs b/src/core/matrix/event/type_ext.rs index 9b824d41f..9c7b28429 100644 --- a/src/core/matrix/event/type_ext.rs +++ b/src/core/matrix/event/type_ext.rs @@ -21,12 +21,12 @@ fn with_state_key(self, state_key: impl Into) -> (StateEventType, Stat impl TypeExt for TimelineEventType { fn with_state_key(self, state_key: impl Into) -> (StateEventType, StateKey) { - (self.into(), state_key.into()) + (self.to_string().into(), state_key.into()) } } impl TypeExt for &TimelineEventType { fn with_state_key(self, state_key: impl Into) -> (StateEventType, StateKey) { - (self.clone().into(), state_key.into()) + (self.clone().to_string().into(), state_key.into()) } } diff --git a/src/core/matrix/mod.rs b/src/core/matrix/mod.rs index b38d4c9aa..2fe86e277 100644 --- a/src/core/matrix/mod.rs +++ b/src/core/matrix/mod.rs @@ -4,8 +4,9 @@ pub mod pdu; pub mod state_key; pub mod state_res; +pub mod versions; pub use event::{Event, TypeExt as EventTypeExt}; pub use pdu::{Pdu, PduBuilder, PduCount, PduEvent, PduId, RawPduId, ShortId}; pub use state_key::StateKey; -pub use state_res::{RoomVersion, StateMap, TypeStateKey}; +pub use state_res::{StateMap, TypeStateKey}; \ No newline at end of file diff --git a/src/core/matrix/pdu/builder.rs b/src/core/matrix/pdu/builder.rs index 5aa0c9caf..38ba3f44f 100644 --- a/src/core/matrix/pdu/builder.rs +++ b/src/core/matrix/pdu/builder.rs @@ -2,7 +2,7 @@ use ruma::{ MilliSecondsSinceUnixEpoch, OwnedEventId, - events::{EventContent, MessageLikeEventType, StateEventType, TimelineEventType}, + events::{MessageLikeEventContent, StateEventContent, TimelineEventType}, }; use serde::Deserialize; use serde_json::value::{RawValue as RawJsonValue, to_raw_value}; @@ -33,7 +33,7 @@ pub struct Builder { impl Builder { pub fn state(state_key: S, content: &T) -> Self where - T: EventContent, + T: StateEventContent, S: Into, { Self { @@ -47,7 +47,7 @@ pub fn state(state_key: S, content: &T) -> Self pub fn timeline(content: &T) -> Self where - T: EventContent, + T: MessageLikeEventContent, { Self { event_type: content.event_type().into(), diff --git a/src/core/matrix/pdu/redact.rs b/src/core/matrix/pdu/redact.rs index 896e03f85..a7965ccf8 100644 --- a/src/core/matrix/pdu/redact.rs +++ b/src/core/matrix/pdu/redact.rs @@ -1,16 +1,20 @@ use ruma::{RoomVersionId, canonical_json::redact_content_in_place}; use serde_json::{Value as JsonValue, json, value::to_raw_value}; -use crate::{Error, Result, err, implement}; +use crate::{Err, Error, Result, err, implement}; #[implement(super::Pdu)] pub fn redact(&mut self, room_version_id: &RoomVersionId, reason: JsonValue) -> Result { + let Some(rules) = room_version_id.rules() else { + return Err!("Cannot redact event for unknown room version {room_version_id}") + }; + self.unsigned = None; let mut content = serde_json::from_str(self.content.get()) .map_err(|e| err!(Request(BadJson("Failed to deserialize content into type: {e}"))))?; - redact_content_in_place(&mut content, room_version_id, self.kind.to_string()) + redact_content_in_place(&mut content, &rules.redaction, self.kind.to_string()) .map_err(|e| Error::Redaction(self.sender.server_name().to_owned(), e))?; let reason = serde_json::to_value(reason).expect("Failed to preserialize reason"); diff --git a/src/core/matrix/state_res/benches.rs b/src/core/matrix/state_res/benches.rs index de62f2660..992f9cf6b 100644 --- a/src/core/matrix/state_res/benches.rs +++ b/src/core/matrix/state_res/benches.rs @@ -318,7 +318,7 @@ fn set_up( .iter() .map(|ev| { ( - (ev.event_type().clone().into(), ev.state_key().unwrap().into()), + (ev.event_type().to_string().into(), ev.state_key().unwrap().into()), ev.event_id().to_owned(), ) }) @@ -328,7 +328,7 @@ fn set_up( .iter() .map(|ev| { ( - (ev.event_type().clone().into(), ev.state_key().unwrap().into()), + (ev.event_type().to_string().into(), ev.state_key().unwrap().into()), ev.event_id().to_owned(), ) }) @@ -338,7 +338,7 @@ fn set_up( .iter() .map(|ev| { ( - (ev.event_type().clone().into(), ev.state_key().unwrap().into()), + (ev.event_type().to_string().into(), ev.state_key().unwrap().into()), ev.event_id().to_owned(), ) }) diff --git a/src/core/matrix/state_res/event_auth.rs b/src/core/matrix/state_res/event_auth.rs index 60e40d4fc..de2b5d3f6 100644 --- a/src/core/matrix/state_res/event_auth.rs +++ b/src/core/matrix/state_res/event_auth.rs @@ -5,16 +5,12 @@ future::{OptionFuture, join, join3}, }; use ruma::{ - Int, OwnedUserId, RoomVersionId, UserId, - events::room::{ + Int, OwnedUserId, RoomVersionId, UserId, events::room::{ create::RoomCreateEventContent, join_rules::{JoinRule, RoomJoinRulesEventContent}, member::{MembershipState, ThirdPartyInvite}, power_levels::RoomPowerLevelsEventContent, - third_party_invite::RoomThirdPartyInviteEventContent, - }, - int, - serde::{Base64, Raw}, + }, int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules}, serde::Raw, }; use serde::{ Deserialize, @@ -28,7 +24,6 @@ deserialize_power_levels, deserialize_power_levels_content_fields, deserialize_power_levels_content_invite, deserialize_power_levels_content_redact, }, - room_version::RoomVersion, }; use crate::{debug, error, trace, warn}; @@ -65,13 +60,13 @@ pub fn auth_types_for_event( sender: &UserId, state_key: Option<&str>, content: &RawJsonValue, - room_version: &RoomVersion, + room_version: &RoomVersionRules, ) -> serde_json::Result> { if kind == &TimelineEventType::RoomCreate { return Ok(vec![]); } - let mut auth_types = if room_version.room_ids_as_hashes { + let mut auth_types = if room_version.room_id_format == RoomIdFormatVersion::V2 { vec![ (StateEventType::RoomPowerLevels, StateKey::new()), (StateEventType::RoomMember, sender.as_str().into()), @@ -120,15 +115,16 @@ struct RoomMemberContentFields { auth_types.push(key); } - if membership == MembershipState::Invite { - if let Some(Ok(t_id)) = content.third_party_invite.map(|t| t.deserialize()) { - let key = - (StateEventType::RoomThirdPartyInvite, t_id.signed.token.into()); - if !auth_types.contains(&key) { - auth_types.push(key); - } - } - } + // TODO: restore this once 3pid support isn't broken + // if membership == MembershipState::Invite { + // if let Some(Ok(t_id)) = content.third_party_invite.map(|t| t.deserialize()) { + // let key = + // (StateEventType::RoomThirdPartyInvite, t_id.signed.token.into()); + // if !auth_types.contains(&key) { + // auth_types.push(key); + // } + // } + // } } } } @@ -155,7 +151,7 @@ struct RoomMemberContentFields { )] #[allow(clippy::suspicious_operation_groupings)] pub async fn auth_check( - room_version: &RoomVersion, + room_version: &RoomVersionRules, incoming_event: &E, current_third_party_invite: Option<&E>, fetch_state: F, @@ -218,13 +214,13 @@ pub async fn auth_check( return Ok(false); } - if room_version.room_ids_as_hashes && incoming_event.room_id().is_some() { + if room_version.room_id_format == RoomIdFormatVersion::V2 && incoming_event.room_id().is_some() { warn!("room create event incorrectly claims to have a room ID when it should not"); return Ok(false); } - if !room_version.use_room_create_sender - && !room_version.explicitly_privilege_room_creators + if !room_version.authorization.use_room_create_sender + && !room_version.authorization.explicitly_privilege_room_creators { // If content has no creator field, reject if content.creator.is_none() { @@ -305,10 +301,10 @@ pub async fn auth_check( let claims_create_event = incoming_event .auth_events() .any(|id| id == room_create_event.event_id()); - if room_version.room_ids_as_hashes && claims_create_event { + if room_version.room_id_format == RoomIdFormatVersion::V2 && claims_create_event { warn!("event incorrectly references m.room.create event in auth events"); return Ok(false); - } else if !room_version.room_ids_as_hashes && !claims_create_event { + } else if !(room_version.room_id_format == RoomIdFormatVersion::V2) && !claims_create_event { // If the create event is not referenced in the event's auth events, and this is // a v11 room, reject warn!( @@ -332,7 +328,7 @@ pub async fn auth_check( // If the create event content has the field m.federate set to false and the // sender domain of the event does not match the sender domain of the create // event, reject. - if !room_version.room_ids_as_hashes + if !(room_version.room_id_format == RoomIdFormatVersion::V2) && !room_create_content.federate && room_create_event.sender().server_name() != incoming_event.sender().server_name() { @@ -345,7 +341,7 @@ pub async fn auth_check( } // Only in some room versions 6 and below - if room_version.special_case_aliases_auth { + if room_version.authorization.special_case_room_aliases { // 4. If type is m.room.aliases if *incoming_event.event_type() == TimelineEventType::RoomAliases { debug!("starting m.room.aliases check"); @@ -486,7 +482,7 @@ pub async fn auth_check( }, | _ => { // If no power level event found the creator gets 100 everyone else gets 0 - let is_creator = if room_version.use_room_create_sender { + let is_creator = if room_version.authorization.use_room_create_sender { room_create_event.sender() == sender } else { #[allow(deprecated)] @@ -497,7 +493,7 @@ pub async fn auth_check( if is_creator { int!(100) } else { int!(0) } }, }; - if room_version.explicitly_privilege_room_creators { + if room_version.authorization.explicitly_privilege_room_creators { // If the user sent the create event, or is listed in additional_creators, just // give them Int::MAX if sender == room_create_event.sender() @@ -559,7 +555,7 @@ pub async fn auth_check( if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels { debug!("starting m.room.power_levels check"); let mut creators = BTreeSet::new(); - if room_version.explicitly_privilege_room_creators { + if room_version.authorization.explicitly_privilege_room_creators { creators.insert(create_event.sender().to_owned()); for creator in room_create_content.additional_creators.iter().flatten() { creators.insert(creator.deserialize()?); @@ -593,7 +589,7 @@ pub async fn auth_check( // the sender of the redaction has the appropriate permissions per the // power levels. - if room_version.extra_redaction_checks + if room_version.authorization.special_case_room_redaction && *incoming_event.event_type() == TimelineEventType::RoomRedaction { let redact_level = match power_levels_event { @@ -618,7 +614,7 @@ pub async fn auth_check( } fn is_creator( - v: &RoomVersion, + v: &RoomVersionRules, c: &BTreeSet, ce: &EV, user_id: &UserId, @@ -627,9 +623,9 @@ fn is_creator( where EV: Event + Send + Sync, { - if v.explicitly_privilege_room_creators { + if v.authorization.explicitly_privilege_room_creators { c.contains(user_id) - } else if v.use_room_create_sender && !have_pls { + } else if v.authorization.use_room_create_sender && !have_pls { ce.sender() == user_id } else if !have_pls { #[allow(deprecated)] @@ -659,7 +655,7 @@ fn is_creator( #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] fn valid_membership_change( - room_version: &RoomVersion, + room_version: &RoomVersionRules, target_user: &UserId, target_user_membership_event: Option<&E>, sender: &UserId, @@ -700,7 +696,7 @@ struct GetThirdPartyInvite { let power_levels: RoomPowerLevelsEventContent = match &power_levels_event { | Some(ev) => from_json_str(ev.content().get())?, - | None => RoomPowerLevelsEventContent::default(), + | None => RoomPowerLevelsEventContent::new(&room_version.authorization), }; let mut sender_power = power_levels @@ -714,7 +710,7 @@ struct GetThirdPartyInvite { let mut creators = BTreeSet::new(); creators.insert(create_room.sender().to_owned()); - if room_version.explicitly_privilege_room_creators { + if room_version.authorization.explicitly_privilege_room_creators { // Explicitly privilege room creators // If the sender sent the create event, or in additional_creators, give them // Int::MAX. Same case for target. @@ -865,7 +861,7 @@ struct GetThirdPartyInvite { trace!(sender=%sender, "sender is invited to room, allowing join"); true }, - | JoinRule::Knock if !room_version.allow_knocking => { + | JoinRule::Knock if !room_version.authorization.knocking => { warn!("Join rule is knock but room version does not allow knocking"); false }, @@ -882,7 +878,7 @@ struct GetThirdPartyInvite { trace!(sender=%sender, "sender is invited or already joined to room, allowing join"); true }, - | JoinRule::KnockRestricted(_) if !room_version.knock_restricted_join_rule => + | JoinRule::KnockRestricted(_) if !room_version.authorization.knock_restricted_join_rule => { warn!( "Join rule is knock_restricted but room version does not support it" @@ -1127,7 +1123,7 @@ struct GetThirdPartyInvite { } allow }, - | MembershipState::Knock if room_version.allow_knocking => { + | MembershipState::Knock if room_version.authorization.knocking => { // 1. If the `join_rule` is anything other than `knock` or `knock_restricted`, // reject. if !matches!(join_rules, JoinRule::KnockRestricted(_) | JoinRule::Knock) { @@ -1136,7 +1132,7 @@ struct GetThirdPartyInvite { ); false } else if matches!(join_rules, JoinRule::KnockRestricted(_)) - && !room_version.knock_restricted_join_rule + && !room_version.authorization.knock_restricted_join_rule { // 2. If the `join_rule` is `knock_restricted`, but the room does not support // `knock_restricted`, reject. @@ -1226,7 +1222,7 @@ fn can_send_event(event: &impl Event, ple: Option<&impl Event>, user_level: Int) /// Confirm that the event sender has the required power levels. #[allow(clippy::cognitive_complexity)] fn check_power_levels( - room_version: &RoomVersion, + room_version: &RoomVersionRules, power_event: &impl Event, previous_power_event: Option<&impl Event>, user_level: Int, @@ -1382,7 +1378,7 @@ fn check_power_levels( } // Notifications, currently there is only @room - if room_version.limit_notifications_power_levels { + if room_version.authorization.limit_notifications_power_levels { let old_level = old_state.notifications.room; let new_level = new_state.notifications.room; if old_level != new_level { @@ -1451,7 +1447,7 @@ fn get_deserialize_levels( /// Does the event redacting come from a user with enough power to redact the /// given event. fn check_redaction( - _room_version: &RoomVersion, + _room_version: &RoomVersionRules, redaction_event: &impl Event, user_level: Int, redact_level: Int, @@ -1501,79 +1497,32 @@ fn get_send_level( } fn verify_third_party_invite( - target_user: Option<&UserId>, - sender: &UserId, - tp_id: &ThirdPartyInvite, - current_third_party_invite: Option<&impl Event>, + _target_user: Option<&UserId>, + _sender: &UserId, + _tp_id: &ThirdPartyInvite, + _current_third_party_invite: Option<&impl Event>, ) -> bool { - // 1. Check for user being banned happens before this is called - // checking for mxid and token keys is done by ruma when deserializing - - // The state key must match the invitee - if target_user != Some(&tp_id.signed.mxid) { - return false; - } - - // If there is no m.room.third_party_invite event in the current room state with - // state_key matching token, reject - #[allow(clippy::manual_let_else)] - let current_tpid = match current_third_party_invite { - | Some(id) => id, - | None => return false, - }; - - if current_tpid.state_key() != Some(&tp_id.signed.token) { - return false; - } - - if sender != current_tpid.sender() { - return false; - } - - // If any signature in signed matches any public key in the - // m.room.third_party_invite event, allow - #[allow(clippy::manual_let_else)] - let tpid_ev = - match from_json_str::(current_tpid.content().get()) { - | Ok(ev) => ev, - | Err(_) => return false, - }; - - #[allow(clippy::manual_let_else)] - let decoded_invite_token = match Base64::parse(&tp_id.signed.token) { - | Ok(tok) => tok, - // FIXME: Log a warning? - | Err(_) => return false, - }; - - // A list of public keys in the public_keys field - for key in tpid_ev.public_keys.unwrap_or_default() { - if key.public_key == decoded_invite_token { - return true; - } - } - - // A single public key in the public_key field - tpid_ev.public_key == decoded_invite_token + // TODO: implement proper verification here + true } #[cfg(test)] mod tests { - use ruma::events::{ + use ruma::{events::{ StateEventType, TimelineEventType, room::{ join_rules::{ - AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent, RoomMembership, + AllowRule, JoinRule, Restricted, RoomJoinRulesEventContent, }, member::{MembershipState, RoomMemberEventContent}, }, - }; + }, room::RoomMembership, room_version_rules::RoomVersionRules}; use serde_json::value::to_raw_value as to_raw_json_value; use crate::{ matrix::{Event, EventTypeExt, Pdu as PduEvent}, state_res::{ - RoomVersion, StateMap, + StateMap, event_auth::valid_membership_change, test_utils::{ INITIAL_EVENTS, INITIAL_EVENTS_CREATE_ROOM, alice, charlie, ella, event_id, @@ -1610,7 +1559,7 @@ fn test_ban_pass() { assert!( valid_membership_change( - &RoomVersion::V6, + &RoomVersionRules::V6, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1655,7 +1604,7 @@ fn test_join_non_creator() { assert!( !valid_membership_change( - &RoomVersion::V6, + &RoomVersionRules::V6, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1700,7 +1649,7 @@ fn test_join_creator() { assert!( valid_membership_change( - &RoomVersion::V6, + &RoomVersionRules::V6, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1745,7 +1694,7 @@ fn test_ban_fail() { assert!( !valid_membership_change( - &RoomVersion::V6, + &RoomVersionRules::V6, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1807,7 +1756,7 @@ fn test_restricted_join_rule() { assert!( valid_membership_change( - &RoomVersion::V9, + &RoomVersionRules::V9, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1825,7 +1774,7 @@ fn test_restricted_join_rule() { assert!( !valid_membership_change( - &RoomVersion::V9, + &RoomVersionRules::V9, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, @@ -1879,7 +1828,7 @@ fn test_knock() { assert!( valid_membership_change( - &RoomVersion::V7, + &RoomVersionRules::V7, target_user, fetch_state(StateEventType::RoomMember, target_user.as_str().into()).as_ref(), sender, diff --git a/src/core/matrix/state_res/mod.rs b/src/core/matrix/state_res/mod.rs index bb6a274f0..4db1996ce 100644 --- a/src/core/matrix/state_res/mod.rs +++ b/src/core/matrix/state_res/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod error; pub mod event_auth; mod power_levels; -mod room_version; +mod serde_backports; #[cfg(test)] mod test_utils; @@ -20,25 +20,21 @@ use futures::{Future, FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future}; use ruma::{ - EventId, Int, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId, + EventId, Int, MilliSecondsSinceUnixEpoch, OwnedEventId, events::{ StateEventType, TimelineEventType, room::member::{MembershipState, RoomMemberEventContent}, }, - int, + int, room_version_rules::{RoomIdFormatVersion, RoomVersionRules, StateResolutionVersion}, }; use serde_json::from_str as from_json_str; pub(crate) use self::error::Error; use self::power_levels::PowerLevelsContentFields; -pub use self::{ - event_auth::{auth_check, auth_types_for_event}, - room_version::RoomVersion, -}; +pub use self::event_auth::{auth_check, auth_types_for_event}; use crate::{ debug, debug_error, err, matrix::{Event, StateKey}, - state_res::room_version::StateResolutionVersion, trace, utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, WidebandExt}, warn, @@ -77,7 +73,7 @@ //#[tracing::instrument(level event_fetch))] #[allow(clippy::cognitive_complexity)] pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, ExistsFut>( - room_version: &RoomVersionId, + room_version: &RoomVersionRules, state_sets: Sets, auth_chain_sets: &'a [HashSet], event_fetch: &Fetch, @@ -94,11 +90,7 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex Pdu: Event + Clone + Send + Sync, for<'b> &'b Pdu: Event + Send, { - use RoomVersionId::*; - let stateres_version = match room_version { - | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 => StateResolutionVersion::V2, - | _ => StateResolutionVersion::V2_1, - }; + let stateres_version = room_version.state_res; debug!(version = ?stateres_version, "State resolution starting"); // Split non-conflicting and conflicting state @@ -115,7 +107,7 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex debug!(count = conflicting.len(), "conflicting events"); trace!(map = ?conflicting, "conflicting events"); let (conflicted_state_subgraph, initial_state) = - if stateres_version == StateResolutionVersion::V2_1 { + if let StateResolutionVersion::V2(v2_rules) = stateres_version && v2_rules.consider_conflicted_state_subgraph { let csg = calculate_conflicted_subgraph(&conflicting, event_fetch) .await .ok_or_else(|| { @@ -166,7 +158,6 @@ pub async fn resolve<'a, Pdu, Sets, SetIter, Hasher, Fetch, FetchFut, Exists, Ex debug!(count = sorted_control_levels.len(), "power events"); trace!(list = ?sorted_control_levels, "sorted power events"); - let room_version = RoomVersion::new(room_version)?; // Sequentially auth check each control event. let resolved_control = iterative_auth_check( &room_version, @@ -596,7 +587,7 @@ async fn get_power_level_for_sender( /// `event_auth::auth_check` function. #[tracing::instrument(level = "trace", skip_all)] async fn iterative_auth_check<'a, E, F, Fut, S>( - room_version: &RoomVersion, + room_version: &RoomVersionRules, events_to_check: S, unconflicted_state: StateMap, fetch_event: &F, @@ -666,7 +657,7 @@ async fn iterative_auth_check<'a, E, F, Fut, S>( trace!(list = ?auth_types, event_id = event.event_id().as_str(), "auth types for event"); let mut auth_state = StateMap::new(); - if room_version.room_ids_as_hashes { + if room_version.room_id_format == RoomIdFormatVersion::V2 { trace!("room version uses hashed IDs, manually fetching create event"); let create_event_id_raw = event.room_id_or_hash().as_str().replace('!', "$"); let create_event_id = EventId::parse(&create_event_id_raw).map_err(|e| { @@ -963,7 +954,7 @@ fn with_state_key(self, state_key: impl Into) -> (StateEventType, Stat impl EventTypeExt for TimelineEventType { fn with_state_key(self, state_key: impl Into) -> (StateEventType, StateKey) { - (self.into(), state_key.into()) + (self.to_string().into(), state_key.into()) } } @@ -983,18 +974,15 @@ mod tests { use maplit::{hashmap, hashset}; use rand::seq::SliceRandom; use ruma::{ - MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId, - events::{ + MilliSecondsSinceUnixEpoch, OwnedEventId, RoomVersionId, events::{ StateEventType, TimelineEventType, room::join_rules::{JoinRule, RoomJoinRulesEventContent}, - }, - int, uint, + }, int, room_version_rules::RoomVersionRules, uint }; use serde_json::{json, value::to_raw_value as to_raw_json_value}; use super::{ StateMap, is_power_event, - room_version::RoomVersion, test_utils::{ INITIAL_EVENTS, TestStore, alice, bob, charlie, do_check, ella, event_id, member_content_ban, member_content_join, room_id, to_init_pdu_event, to_pdu_event, @@ -1004,7 +992,6 @@ mod tests { use crate::{ debug, matrix::{Event, EventTypeExt, Pdu as PduEvent}, - state_res::room_version::StateResolutionVersion, utils::stream::IterStream, }; @@ -1036,7 +1023,7 @@ async fn test_event_sort() { .unwrap(); let resolved_power = super::iterative_auth_check( - &RoomVersion::V6, + &RoomVersionRules::V6, sorted_power_events.iter().map(AsRef::as_ref).stream(), HashMap::new(), // unconflicted events &fetcher, @@ -1437,7 +1424,7 @@ async fn test_event_map_none() { .collect(); let resolved = - match super::resolve(&RoomVersionId::V2, &state_sets, &auth_chain, &fetcher, &exists) + match super::resolve(&RoomVersionRules::V2, &state_sets, &auth_chain, &fetcher, &exists) .await { | Ok(state) => state, @@ -1550,7 +1537,7 @@ async fn ban_with_auth_chains2() { let fetcher = |id: OwnedEventId| ready(ev_map.get(&id).cloned()); let exists = |id: OwnedEventId| ready(ev_map.get(&id).is_some()); let resolved = - match super::resolve(&RoomVersionId::V6, &state_sets, &auth_chain, &fetcher, &exists) + match super::resolve(&RoomVersionRules::V6, &state_sets, &auth_chain, &fetcher, &exists) .await { | Ok(state) => state, diff --git a/src/core/matrix/state_res/power_levels.rs b/src/core/matrix/state_res/power_levels.rs index 19ba8fb93..6d5bdeba0 100644 --- a/src/core/matrix/state_res/power_levels.rs +++ b/src/core/matrix/state_res/power_levels.rs @@ -1,18 +1,13 @@ use std::collections::BTreeMap; use ruma::{ - Int, OwnedUserId, UserId, - events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent}, - power_levels::{NotificationPowerLevels, default_power_level}, - serde::{ - deserialize_v1_powerlevel, vec_deserialize_int_powerlevel_values, - vec_deserialize_v1_powerlevel_values, - }, + Int, OwnedUserId, UserId, events::{TimelineEventType, room::power_levels::RoomPowerLevelsEventContent}, power_levels::{NotificationPowerLevels, default_power_level}, room_version_rules::{AuthorizationRules, RoomVersionRules}, serde::deserialize_v1_powerlevel }; +use super::serde_backports::*; use serde::Deserialize; use serde_json::{Error, from_str as from_json_str}; -use super::{Result, RoomVersion}; +use super::Result; use crate::error; #[derive(Deserialize)] @@ -48,8 +43,8 @@ struct IntRoomPowerLevelsEventContent { notifications: IntNotificationPowerLevels, } -impl From for RoomPowerLevelsEventContent { - fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self { +impl IntRoomPowerLevelsEventContent { + fn to_room_power_levels_content(self, auth_rules: &AuthorizationRules) -> RoomPowerLevelsEventContent { let IntRoomPowerLevelsEventContent { ban, events, @@ -61,9 +56,9 @@ fn from(int_pl: IntRoomPowerLevelsEventContent) -> Self { users, users_default, notifications, - } = int_pl; + } = self; - let mut pl = Self::new(); + let mut pl = RoomPowerLevelsEventContent::new(auth_rules); pl.ban = ban; pl.events = events; pl.events_default = events_default; @@ -101,18 +96,18 @@ fn from(int_notif: IntNotificationPowerLevels) -> Self { #[inline] pub(crate) fn deserialize_power_levels( content: &str, - room_version: &RoomVersion, + room_version: &RoomVersionRules, ) -> Option { - if room_version.integer_power_levels { - deserialize_integer_power_levels(content) + if room_version.authorization.integer_power_levels { + deserialize_integer_power_levels(content, &room_version.authorization) } else { deserialize_legacy_power_levels(content) } } -fn deserialize_integer_power_levels(content: &str) -> Option { +fn deserialize_integer_power_levels(content: &str, auth_rules: &AuthorizationRules) -> Option { match from_json_str::(content) { - | Ok(content) => Some(content.into()), + | Ok(content) => Some(content.to_room_power_levels_content(auth_rules)), | Err(_) => { error!("m.room.power_levels event is not valid with integer values"); None @@ -174,9 +169,9 @@ fn from(pl: IntPowerLevelsContentFields) -> Self { #[inline] pub(crate) fn deserialize_power_levels_content_fields( content: &str, - room_version: &RoomVersion, + room_version: &RoomVersionRules, ) -> Result { - if room_version.integer_power_levels { + if room_version.authorization.integer_power_levels { deserialize_integer_power_levels_content_fields(content) } else { deserialize_legacy_power_levels_content_fields(content) @@ -216,9 +211,9 @@ fn from(pl: IntPowerLevelsContentInvite) -> Self { pub(crate) fn deserialize_power_levels_content_invite( content: &str, - room_version: &RoomVersion, + room_version: &RoomVersionRules, ) -> Result { - if room_version.integer_power_levels { + if room_version.authorization.integer_power_levels { from_json_str::(content).map(Into::into) } else { from_json_str(content) @@ -246,9 +241,9 @@ fn from(pl: IntPowerLevelsContentRedact) -> Self { pub(crate) fn deserialize_power_levels_content_redact( content: &str, - room_version: &RoomVersion, + room_version: &RoomVersionRules, ) -> Result { - if room_version.integer_power_levels { + if room_version.authorization.integer_power_levels { from_json_str::(content).map(Into::into) } else { from_json_str(content) diff --git a/src/core/matrix/state_res/room_version.rs b/src/core/matrix/state_res/room_version.rs deleted file mode 100644 index 87af9f946..000000000 --- a/src/core/matrix/state_res/room_version.rs +++ /dev/null @@ -1,169 +0,0 @@ -use ruma::RoomVersionId; - -use super::{Error, Result}; - -#[derive(Debug)] -#[allow(clippy::exhaustive_enums)] -pub enum RoomDisposition { - /// A room version that has a stable specification. - Stable, - /// A room version that is not yet fully specified. - Unstable, -} - -#[derive(Debug)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub enum EventFormatVersion { - /// $id:server event id format - V1, - /// MSC1659-style $hash event id format: introduced for room v3 - V2, - /// MSC1884-style $hash format: introduced for room v4 - V3, -} - -#[derive(Debug, PartialEq)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub enum StateResolutionVersion { - /// State resolution for rooms at version 1. - V1, - /// State resolution for room at version 2 or later. - V2, - /// State resolution for room at version 12 or later. - V2_1, -} - -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[allow(clippy::struct_excessive_bools)] -pub struct RoomVersion { - /// The stability of this room. - pub disposition: RoomDisposition, - /// The format of the EventId. - pub event_format: EventFormatVersion, - /// Which state resolution algorithm is used. - pub state_res: StateResolutionVersion, - // FIXME: not sure what this one means? - pub enforce_key_validity: bool, - - /// `m.room.aliases` had special auth rules and redaction rules - /// before room version 6. - /// - /// before MSC2261/MSC2432, - pub special_case_aliases_auth: bool, - /// Strictly enforce canonical json, do not allow: - /// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1] - /// * Floats - /// * NaN, Infinity, -Infinity - pub strict_canonicaljson: bool, - /// Verify notifications key while checking m.room.power_levels. - /// - /// bool: MSC2209: Check 'notifications' - pub limit_notifications_power_levels: bool, - /// Extra rules when verifying redaction events. - pub extra_redaction_checks: bool, - /// Allow knocking in event authentication. - /// - /// See [room v7 specification](https://spec.matrix.org/latest/rooms/v7/) - pub allow_knocking: bool, - /// Adds support for the restricted join rule. - /// - /// See: [MSC3289](https://github.com/matrix-org/matrix-spec-proposals/pull/3289) - pub restricted_join_rules: bool, - /// Adds support for the knock_restricted join rule. - /// - /// See: [MSC3787](https://github.com/matrix-org/matrix-spec-proposals/pull/3787) - pub knock_restricted_join_rule: bool, - /// Enforces integer power levels. - /// - /// See: [MSC3667](https://github.com/matrix-org/matrix-spec-proposals/pull/3667) - pub integer_power_levels: bool, - /// Determine the room creator using the `m.room.create` event's `sender`, - /// instead of the event content's `creator` field. - /// - /// See: [MSC2175](https://github.com/matrix-org/matrix-spec-proposals/pull/2175) - pub use_room_create_sender: bool, - /// Whether the room creators are considered superusers. - /// A superuser will always have infinite power levels in the room. - /// - /// See: [MSC4289](https://github.com/matrix-org/matrix-spec-proposals/pull/4289) - pub explicitly_privilege_room_creators: bool, - /// Whether the room's m.room.create event ID is itself the room ID. - /// - /// See: [MSC4291](https://github.com/matrix-org/matrix-spec-proposals/pull/4291) - pub room_ids_as_hashes: bool, -} - -impl RoomVersion { - pub const V1: Self = Self { - disposition: RoomDisposition::Stable, - event_format: EventFormatVersion::V1, - state_res: StateResolutionVersion::V1, - enforce_key_validity: false, - special_case_aliases_auth: true, - strict_canonicaljson: false, - limit_notifications_power_levels: false, - extra_redaction_checks: true, - allow_knocking: false, - restricted_join_rules: false, - knock_restricted_join_rule: false, - integer_power_levels: false, - use_room_create_sender: false, - explicitly_privilege_room_creators: false, - room_ids_as_hashes: false, - }; - pub const V10: Self = Self { - knock_restricted_join_rule: true, - integer_power_levels: true, - ..Self::V9 - }; - pub const V11: Self = Self { - use_room_create_sender: true, - ..Self::V10 - }; - pub const V12: Self = Self { - explicitly_privilege_room_creators: true, - room_ids_as_hashes: true, - ..Self::V11 - }; - pub const V2: Self = Self { - state_res: StateResolutionVersion::V2, - ..Self::V1 - }; - pub const V3: Self = Self { - event_format: EventFormatVersion::V2, - extra_redaction_checks: false, - ..Self::V2 - }; - pub const V4: Self = Self { - event_format: EventFormatVersion::V3, - ..Self::V3 - }; - pub const V5: Self = Self { enforce_key_validity: true, ..Self::V4 }; - pub const V6: Self = Self { - special_case_aliases_auth: false, - strict_canonicaljson: true, - limit_notifications_power_levels: true, - ..Self::V5 - }; - pub const V7: Self = Self { allow_knocking: true, ..Self::V6 }; - pub const V8: Self = Self { restricted_join_rules: true, ..Self::V7 }; - pub const V9: Self = Self::V8; - - pub fn new(version: &RoomVersionId) -> Result { - Ok(match version { - | RoomVersionId::V1 => Self::V1, - | RoomVersionId::V2 => Self::V2, - | RoomVersionId::V3 => Self::V3, - | RoomVersionId::V4 => Self::V4, - | RoomVersionId::V5 => Self::V5, - | RoomVersionId::V6 => Self::V6, - | RoomVersionId::V7 => Self::V7, - | RoomVersionId::V8 => Self::V8, - | RoomVersionId::V9 => Self::V9, - | RoomVersionId::V10 => Self::V10, - | RoomVersionId::V11 => Self::V11, - | RoomVersionId::V12 => Self::V12, - | ver => return Err(Error::Unsupported(format!("found version `{ver}`"))), - }) - } -} diff --git a/src/core/matrix/state_res/serde_backports.rs b/src/core/matrix/state_res/serde_backports.rs new file mode 100644 index 000000000..ac9b4d3bb --- /dev/null +++ b/src/core/matrix/state_res/serde_backports.rs @@ -0,0 +1,120 @@ +//! These functions are copied from an old version of Ruma. power_levels.rs uses them to lazily deserialize power level events. +//! Upstream Ruma uses a much more elegant approach in its state resolution code, which we may want +//! to look into at some point. + +use std::marker::PhantomData; +use std::fmt; + +use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}}; +use ruma::{Int, serde::deserialize_v1_powerlevel}; + +/// Take a Map with values of either an integer number or a string and deserialize +/// those to integer numbers in a Vec of sorted pairs. +/// +/// To be used like this: +/// `#[serde(deserialize_with = "vec_deserialize_v1_powerlevel_values")]` +pub fn vec_deserialize_v1_powerlevel_values<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Ord, +{ + #[repr(transparent)] + struct IntWrap(Int); + + impl<'de> Deserialize<'de> for IntWrap { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserialize_v1_powerlevel(deserializer).map(IntWrap) + } + } + + struct IntMapVisitor { + _phantom: PhantomData, + } + + impl IntMapVisitor { + fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + impl<'de, T> Visitor<'de> for IntMapVisitor + where + T: Deserialize<'de> + Ord, + { + type Value = Vec<(T, Int)>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map with integers or strings as values") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut res = Vec::new(); + if let Some(hint) = map.size_hint() { + res.reserve(hint); + } + + while let Some((k, IntWrap(v))) = map.next_entry()? { + res.push((k, v)); + } + + res.sort_unstable(); + res.dedup_by(|a, b| a.0 == b.0); + + Ok(res) + } + } + + de.deserialize_map(IntMapVisitor::new()) +} + +/// Take a Map with integer values and deserialize those to a Vec of sorted pairs +/// +/// To be used like this: +/// `#[serde(deserialize_with = "vec_deserialize_int_powerlevel_values")]` +pub fn vec_deserialize_int_powerlevel_values<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de> + Ord, +{ + struct IntMapVisitor { + _phantom: PhantomData, + } + + impl IntMapVisitor { + fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + impl<'de, T> Visitor<'de> for IntMapVisitor + where + T: Deserialize<'de> + Ord, + { + type Value = Vec<(T, Int)>; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a map with integers as values") + } + + fn visit_map>(self, mut map: A) -> Result { + let mut res = Vec::new(); + if let Some(hint) = map.size_hint() { + res.reserve(hint); + } + + while let Some(item) = map.next_entry()? { + res.push(item); + } + + res.sort_unstable(); + res.dedup_by(|a, b| a.0 == b.0); + + Ok(res) + } + } + + de.deserialize_map(IntMapVisitor::new()) +} \ No newline at end of file diff --git a/src/core/matrix/state_res/test_utils.rs b/src/core/matrix/state_res/test_utils.rs index 10b79de1e..dad28717e 100644 --- a/src/core/matrix/state_res/test_utils.rs +++ b/src/core/matrix/state_res/test_utils.rs @@ -6,16 +6,13 @@ use futures::future::ready; use ruma::{ - EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures, - UserId, event_id, - events::{ + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, RoomVersionId, ServerSignatures, UserId, event_id, events::{ TimelineEventType, room::{ join_rules::{JoinRule, RoomJoinRulesEventContent}, member::{MembershipState, RoomMemberEventContent}, }, - }, - int, room_id, uint, user_id, + }, int, room_id, room_version_rules::RoomVersionRules, uint, user_id }; use serde_json::{ json, @@ -24,7 +21,7 @@ use super::auth_types_for_event; use crate::{ - Result, RoomVersion, info, + Result, info, matrix::{Event, EventTypeExt, Pdu, StateMap, pdu::EventHash}, }; @@ -134,7 +131,7 @@ pub(crate) async fn do_check( let fetch = |id: OwnedEventId| ready(event_map.get(&id).cloned()); let exists = |id: OwnedEventId| ready(event_map.get(&id).is_some()); let resolved = - super::resolve(&RoomVersionId::V6, state_sets, &auth_chain_sets, &fetch, &exists) + super::resolve(&RoomVersionRules::V6, state_sets, &auth_chain_sets, &fetch, &exists) .await; match resolved { @@ -154,7 +151,7 @@ pub(crate) async fn do_check( fake_event.sender(), fake_event.state_key(), fake_event.content(), - &RoomVersion::V6, + &RoomVersionRules::V6, ) .unwrap(); diff --git a/src/core/matrix/versions.rs b/src/core/matrix/versions.rs new file mode 100644 index 000000000..4524ce1c8 --- /dev/null +++ b/src/core/matrix/versions.rs @@ -0,0 +1,44 @@ +use std::collections::BTreeMap; + +pub fn versions() -> Vec { + vec![ + "r0.0.1".to_owned(), + "r0.1.0".to_owned(), + "r0.2.0".to_owned(), + "r0.3.0".to_owned(), + "r0.4.0".to_owned(), + "r0.5.0".to_owned(), + "r0.6.0".to_owned(), + "r0.6.1".to_owned(), + "v1.1".to_owned(), + "v1.2".to_owned(), + "v1.3".to_owned(), + "v1.4".to_owned(), + "v1.5".to_owned(), + "v1.8".to_owned(), + "v1.11".to_owned(), + "v1.12".to_owned(), + "v1.13".to_owned(), + "v1.14".to_owned(), + ] +} + +pub fn unstable_features() -> BTreeMap { + BTreeMap::from_iter([ + ("org.matrix.e2e_cross_signing".to_owned(), true), + ("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */ + ("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */ + ("org.matrix.msc2836".to_owned(), true), /* threading/threads (https://github.com/matrix-org/matrix-spec-proposals/pull/2836) */ + ("org.matrix.msc2946".to_owned(), true), /* spaces/hierarchy summaries (https://github.com/matrix-org/matrix-spec-proposals/pull/2946) */ + ("org.matrix.msc3026.busy_presence".to_owned(), true), /* busy presence status (https://github.com/matrix-org/matrix-spec-proposals/pull/3026) */ + ("org.matrix.msc3827".to_owned(), true), /* filtering of /publicRooms by room type (https://github.com/matrix-org/matrix-spec-proposals/pull/3827) */ + ("org.matrix.msc3952_intentional_mentions".to_owned(), true), /* intentional mentions (https://github.com/matrix-org/matrix-spec-proposals/pull/3952) */ + ("org.matrix.msc3916.stable".to_owned(), true), /* authenticated media (https://github.com/matrix-org/matrix-spec-proposals/pull/3916) */ + ("org.matrix.msc4180".to_owned(), true), /* stable flag for 3916 (https://github.com/matrix-org/matrix-spec-proposals/pull/4180) */ + ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ + ("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */ + ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ + ("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */ + ("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */ + ]) +} \ No newline at end of file diff --git a/src/core/mod.rs b/src/core/mod.rs index 6dfd61a4b..d33289678 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -9,6 +9,18 @@ pub mod matrix; pub mod metrics; pub mod mods; + +#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))] +pub mod mods { + #[macro_export] + macro_rules! mod_ctor { + () => {}; + } + #[macro_export] + macro_rules! mod_dtor { + () => {}; + } +} pub mod server; pub mod utils; @@ -27,7 +39,7 @@ version::{name, version}, }; pub use matrix::{ - Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res, + Event, EventTypeExt, Pdu, PduCount, PduEvent, PduId, pdu, state_res, }; pub use parking_lot::{Mutex as SyncMutex, RwLock as SyncRwLock}; pub use server::Server; @@ -36,15 +48,3 @@ pub use crate as conduwuit_core; conduwuit_macros::introspect_crate! {} - -#[cfg(any(not(conduwuit_mods), not(feature = "conduwuit_mods")))] -pub mod mods { - #[macro_export] - macro_rules! mod_ctor { - () => {}; - } - #[macro_export] - macro_rules! mod_dtor { - () => {}; - } -} diff --git a/src/database/tests.rs b/src/database/tests.rs index 30562a669..758c0cdc9 100644 --- a/src/database/tests.rs +++ b/src/database/tests.rs @@ -1,10 +1,10 @@ #![allow(clippy::needless_borrows_for_generic_args)] -use std::fmt::Debug; +use std::{borrow::Cow, fmt::Debug}; use conduwuit::{ arrayvec::ArrayVec, - ruma::{EventId, RoomId, UserId, serde::Raw}, + ruma::{EventId, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, room_id, serde::Raw, user_id}, }; use serde::Serialize; @@ -16,15 +16,15 @@ #[test] #[cfg_attr(debug_assertions, should_panic(expected = "serializing string at the top-level"))] fn ser_str() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); let s = serialize_to_vec(&user_id).expect("failed to serialize user_id"); assert_eq!(&s, user_id.as_bytes()); } #[test] fn ser_tuple() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let mut a = user_id.as_bytes().to_vec(); a.push(0xFF); @@ -38,8 +38,8 @@ fn ser_tuple() { #[test] fn ser_tuple_option() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); + let user_id = user_id!("@user:example.com"); let mut a = Vec::::new(); a.push(0xFF); @@ -64,8 +64,8 @@ fn ser_tuple_option() { fn ser_overflow() { const BUFSIZE: usize = 10; - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); assert!(BUFSIZE < user_id.as_str().len() + room_id.as_str().len()); let mut buf = ArrayVec::::new(); @@ -74,48 +74,12 @@ fn ser_overflow() { _ = ser::serialize(&mut buf, val).unwrap(); } -#[test] -fn ser_complex() { - use conduwuit::ruma::Mxc; - - #[derive(Debug, Serialize)] - struct Dim { - width: u32, - height: u32, - } - - let mxc = Mxc { - server_name: "example.com".try_into().unwrap(), - media_id: "AbCdEfGhIjK", - }; - - let dim = Dim { width: 123, height: 456 }; - - let mut a = Vec::new(); - a.extend_from_slice(b"mxc://"); - a.extend_from_slice(mxc.server_name.as_bytes()); - a.extend_from_slice(b"/"); - a.extend_from_slice(mxc.media_id.as_bytes()); - a.push(0xFF); - a.extend_from_slice(&dim.width.to_be_bytes()); - a.extend_from_slice(&dim.height.to_be_bytes()); - a.push(0xFF); - - let d: &[u32] = &[dim.width, dim.height]; - let b = (mxc, d, Interfix); - let b = serialize_to_vec(b).expect("failed to serialize complex"); - - assert_eq!(a, b); -} - #[test] fn ser_json() { use conduwuit::ruma::api::client::filter::FilterDefinition; - let filter = FilterDefinition { - event_fields: Some(vec!["content.body".to_owned()]), - ..Default::default() - }; + let mut filter = FilterDefinition::default(); + filter.event_fields = Some(vec!["content.body".to_owned()]); let serialized = serialize_to_vec(Json(&filter)).expect("failed to serialize value"); @@ -127,10 +91,8 @@ fn ser_json() { fn ser_json_value() { use conduwuit::ruma::api::client::filter::FilterDefinition; - let filter = FilterDefinition { - event_fields: Some(vec!["content.body".to_owned()]), - ..Default::default() - }; + let mut filter = FilterDefinition::default(); + filter.event_fields = Some(vec!["content.body".to_owned()]); let value = serde_json::to_value(filter).expect("failed to serialize to serde_json::value"); let serialized = serialize_to_vec(Json(value)).expect("failed to serialize value"); @@ -166,10 +128,8 @@ struct Foo { fn ser_json_raw() { use conduwuit::ruma::api::client::filter::FilterDefinition; - let filter = FilterDefinition { - event_fields: Some(vec!["content.body".to_owned()]), - ..Default::default() - }; + let mut filter = FilterDefinition::default(); + filter.event_fields = Some(vec!["content.body".to_owned()]); let value = serde_json::value::to_raw_value(&filter).expect("failed to serialize to raw value"); @@ -183,10 +143,8 @@ fn ser_json_raw() { fn ser_json_raw_json() { use conduwuit::ruma::api::client::filter::FilterDefinition; - let filter = FilterDefinition { - event_fields: Some(vec!["content.body".to_owned()]), - ..Default::default() - }; + let mut filter = FilterDefinition::default(); + filter.event_fields = Some(vec!["content.body".to_owned()]); let value = serde_json::value::to_raw_value(&filter).expect("failed to serialize to raw value"); @@ -197,11 +155,11 @@ fn ser_json_raw_json() { #[test] fn de_tuple() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let raw: &[u8] = b"@user:example.com\xFF!room:example.com"; - let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize"); + let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); assert_eq!(b, room_id, "deserialized room_id does not match"); @@ -210,11 +168,11 @@ fn de_tuple() { #[test] #[should_panic(expected = "failed to deserialize")] fn de_tuple_invalid() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let raw: &[u8] = b"@user:example.com\xFF@user:example.com"; - let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize"); + let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); assert_eq!(b, room_id, "deserialized room_id does not match"); @@ -223,10 +181,10 @@ fn de_tuple_invalid() { #[test] #[should_panic(expected = "failed to deserialize")] fn de_tuple_incomplete() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); let raw: &[u8] = b"@user:example.com"; - let (a, _): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize"); + let (a, _): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); } @@ -234,10 +192,10 @@ fn de_tuple_incomplete() { #[test] #[should_panic(expected = "failed to deserialize")] fn de_tuple_incomplete_with_sep() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); let raw: &[u8] = b"@user:example.com\xFF"; - let (a, _): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize"); + let (a, _): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); } @@ -248,11 +206,11 @@ fn de_tuple_incomplete_with_sep() { should_panic(expected = "deserialization failed to consume trailing bytes") )] fn de_tuple_unfinished() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let raw: &[u8] = b"@user:example.com\xFF!room:example.com\xFF@user:example.com"; - let (a, b): (&UserId, &RoomId) = de::from_slice(raw).expect("failed to deserialize"); + let (a, b): (OwnedUserId, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); assert_eq!(b, room_id, "deserialized room_id does not match"); @@ -260,11 +218,11 @@ fn de_tuple_unfinished() { #[test] fn de_tuple_ignore() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let raw: &[u8] = b"@user:example.com\xFF@user2:example.net\xFF!room:example.com"; - let (a, _, c): (&UserId, Ignore, &RoomId) = + let (a, _, c): (OwnedUserId, Ignore, OwnedRoomId) = de::from_slice(raw).expect("failed to deserialize"); assert_eq!(a, user_id, "deserialized user_id does not match"); @@ -360,10 +318,10 @@ fn de_array() { #[test] #[ignore = "Nested sequences are not supported"] fn de_complex() { - type Key<'a> = (&'a UserId, ArrayVec, &'a RoomId); + type Key = (OwnedUserId, ArrayVec, OwnedRoomId); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); + let room_id = room_id!("!room:example.com"); let a: u64 = 123_456; let b: u64 = 987_654; @@ -376,36 +334,36 @@ fn de_complex() { v.extend_from_slice(room_id.as_bytes()); let arr: &[u64] = &[a, b]; - let key = (user_id, arr, room_id); + let key = (user_id.to_owned(), arr, room_id.to_owned()); let s = serialize_to_vec(&key).expect("failed to serialize"); assert_eq!(&s, &v, "serialization does not match"); - let key = (user_id, [a, b].into(), room_id); - let arr: Key<'_> = de::from_slice(&v).expect("failed to deserialize"); + let key = (user_id.to_owned(), [a, b].into(), room_id.to_owned()); + let arr: Key = de::from_slice(&v).expect("failed to deserialize"); assert_eq!(arr, key, "deserialization does not match"); - let arr: Key<'_> = de::from_slice(&s).expect("failed to deserialize"); + let arr: Key = de::from_slice(&s).expect("failed to deserialize"); assert_eq!(arr, key, "deserialization of serialization does not match"); } #[test] fn serde_tuple_option_value_some() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); + let user_id = user_id!("@user:example.com"); let mut aa = Vec::::new(); aa.extend_from_slice(room_id.as_bytes()); aa.push(0xFF); aa.extend_from_slice(user_id.as_bytes()); - let bb: (&RoomId, Option<&UserId>) = (room_id, Some(user_id)); + let bb: (OwnedRoomId, Option) = (room_id.to_owned(), Some(user_id.to_owned())); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (&RoomId, Option<&UserId>) = + let cc: (OwnedRoomId, Option) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(bb.1, cc.1); @@ -414,17 +372,17 @@ fn serde_tuple_option_value_some() { #[test] fn serde_tuple_option_value_none() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); let mut aa = Vec::::new(); aa.extend_from_slice(room_id.as_bytes()); aa.push(0xFF); - let bb: (&RoomId, Option<&UserId>) = (room_id, None); + let bb: (OwnedRoomId, Option) = (room_id.to_owned(), None); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (&RoomId, Option<&UserId>) = + let cc: (OwnedRoomId, Option) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(None, cc.1); @@ -433,17 +391,17 @@ fn serde_tuple_option_value_none() { #[test] fn serde_tuple_option_none_value() { - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let user_id = user_id!("@user:example.com"); let mut aa = Vec::::new(); aa.push(0xFF); aa.extend_from_slice(user_id.as_bytes()); - let bb: (Option<&RoomId>, &UserId) = (None, user_id); + let bb: (Option, OwnedUserId) = (None, user_id.to_owned()); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, &UserId) = + let cc: (Option, OwnedUserId) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(None, cc.0); @@ -452,19 +410,19 @@ fn serde_tuple_option_none_value() { #[test] fn serde_tuple_option_some_value() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); + let user_id = user_id!("@user:example.com"); let mut aa = Vec::::new(); aa.extend_from_slice(room_id.as_bytes()); aa.push(0xFF); aa.extend_from_slice(user_id.as_bytes()); - let bb: (Option<&RoomId>, &UserId) = (Some(room_id), user_id); + let bb: (Option, OwnedUserId) = (Some(room_id.to_owned()), user_id.to_owned()); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, &UserId) = + let cc: (Option, OwnedUserId) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(bb.0, cc.0); @@ -473,19 +431,19 @@ fn serde_tuple_option_some_value() { #[test] fn serde_tuple_option_some_some() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); + let user_id = user_id!("@user:example.com"); let mut aa = Vec::::new(); aa.extend_from_slice(room_id.as_bytes()); aa.push(0xFF); aa.extend_from_slice(user_id.as_bytes()); - let bb: (Option<&RoomId>, Option<&UserId>) = (Some(room_id), Some(user_id)); + let bb: (Option, Option) = (Some(room_id.to_owned()), Some(user_id.to_owned())); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, Option<&UserId>) = + let cc: (Option, Option) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(cc.0, bb.0); @@ -496,11 +454,11 @@ fn serde_tuple_option_some_some() { fn serde_tuple_option_none_none() { let aa = vec![0xFF]; - let bb: (Option<&RoomId>, Option<&UserId>) = (None, None); + let bb: (Option, Option) = (None, None); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, Option<&UserId>) = + let cc: (Option, Option) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(cc.0, bb.0); @@ -509,8 +467,8 @@ fn serde_tuple_option_none_none() { #[test] fn serde_tuple_option_some_none_some() { - let room_id: &RoomId = "!room:example.com".try_into().unwrap(); - let user_id: &UserId = "@user:example.com".try_into().unwrap(); + let room_id = room_id!("!room:example.com"); + let user_id = user_id!("@user:example.com"); let mut aa = Vec::::new(); aa.extend_from_slice(room_id.as_bytes()); @@ -524,24 +482,24 @@ fn serde_tuple_option_some_none_some() { let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) = + let cc: (Option>, Option>, Option>) = de::from_slice(&bbs).expect("failed to deserialize tuple"); - assert_eq!(bb.0, cc.0); - assert_eq!(None, cc.1); - assert_eq!(bb.1, cc.1); - assert_eq!(bb.2, cc.2); + assert_eq!(bb.0, cc.0.as_deref()); + assert_eq!(None, cc.1.as_deref()); + assert_eq!(bb.1, cc.1.as_deref()); + assert_eq!(bb.2, cc.2.as_deref()); } #[test] fn serde_tuple_option_none_none_none() { let aa = vec![0xFF, 0xFF]; - let bb: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) = (None, None, None); + let bb: (Option, Option, Option) = (None, None, None); let bbs = serialize_to_vec(&bb).expect("failed to serialize tuple"); assert_eq!(aa, bbs); - let cc: (Option<&RoomId>, Option<&EventId>, Option<&UserId>) = + let cc: (Option, Option, Option) = de::from_slice(&bbs).expect("failed to deserialize tuple"); assert_eq!(None, cc.0); diff --git a/src/ruminuwuity/Cargo.toml b/src/ruminuwuity/Cargo.toml new file mode 100644 index 000000000..0079678f4 --- /dev/null +++ b/src/ruminuwuity/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "ruminuwuity" +description.workspace = true +edition.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +version.workspace = true + +[lib] +path = "mod.rs" + +[features] +default = ["client", "server"] +client = [] +server = [] + +unstable-exhaustive-types = [] +unstable-msc3202 = [] +unstable-msc4203 = [] + +[dependencies] +ruma.workspace = true +serde = { workspace = true } +serde_json = { workspace = true } +wildmatch = "2.6.1" + +[dev-dependencies] + +[lints] +workspace = true diff --git a/src/ruminuwuity/admin/continuwuity/mod.rs b/src/ruminuwuity/admin/continuwuity/mod.rs new file mode 100644 index 000000000..7de0b163e --- /dev/null +++ b/src/ruminuwuity/admin/continuwuity/mod.rs @@ -0,0 +1 @@ +pub mod rooms; \ No newline at end of file diff --git a/src/ruminuwuity/admin/continuwuity/rooms/ban.rs b/src/ruminuwuity/admin/continuwuity/rooms/ban.rs new file mode 100644 index 000000000..78a4e4769 --- /dev/null +++ b/src/ruminuwuity/admin/continuwuity/rooms/ban.rs @@ -0,0 +1,50 @@ +pub mod v1 { + use ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata}; + + metadata! { + method: PUT, + rate_limited: false, + authentication: AccessToken, + history: { + 1.0 => "/_continuwuity/admin/rooms/{room_id}/ban", + } + } + + #[request] + pub struct Request { + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + /// Whether to ban (true) or unban (false) the room. + /// If true, and the room is not banned, all local users will be evacuated + /// and prevented from re-joining. + /// If false, and the room is unbanned, local users will be allowed to re-join. + /// No-ops are no-ops. + pub banned: bool, + } + + #[response] + pub struct Response { + pub kicked_users: Vec, + pub failed_kicked_users: Vec, + pub local_aliases: Vec + } + + impl Request { + #[must_use] + pub fn new(room_id: OwnedRoomId, banned: bool) -> Self { + Self { room_id, banned } + } + } + + impl Response { + #[must_use] + pub fn new( + kicked_users: Vec, + failed_kicked_users: Vec, + local_aliases: Vec, + ) -> Self { + Self { kicked_users, failed_kicked_users, local_aliases } + } + } +} \ No newline at end of file diff --git a/src/ruminuwuity/admin/continuwuity/rooms/list.rs b/src/ruminuwuity/admin/continuwuity/rooms/list.rs new file mode 100644 index 000000000..9f73a2105 --- /dev/null +++ b/src/ruminuwuity/admin/continuwuity/rooms/list.rs @@ -0,0 +1,37 @@ +pub mod v1 { + use ruma::{ + OwnedRoomId, api::{auth_scheme::AccessToken, request, response}, metadata + }; + + metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + 1.0 => "/_continuwuity/admin/rooms/list", + } + } + + #[request] + #[derive(Default)] + pub struct Request; + + #[response] + pub struct Response { + pub rooms: Vec, + } + + impl Request { + #[must_use] + pub fn new() -> Self { + Self::default() + } + } + + impl Response { + #[must_use] + pub fn new(rooms: Vec) -> Self { + Self { rooms } + } + } +} \ No newline at end of file diff --git a/src/ruminuwuity/admin/continuwuity/rooms/mod.rs b/src/ruminuwuity/admin/continuwuity/rooms/mod.rs new file mode 100644 index 000000000..fde322e30 --- /dev/null +++ b/src/ruminuwuity/admin/continuwuity/rooms/mod.rs @@ -0,0 +1,2 @@ +pub mod list; +pub mod ban; diff --git a/src/ruminuwuity/admin/get_suspended.rs b/src/ruminuwuity/admin/get_suspended.rs new file mode 100644 index 000000000..0fc6056e6 --- /dev/null +++ b/src/ruminuwuity/admin/get_suspended.rs @@ -0,0 +1,53 @@ +//! `GET /_matrix/client/v1/admin/suspend/{userId}` +//! +//! Check the suspension status of a target user + +pub mod v1 { + //! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc]) + //! + //! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323 + + use ruma::{ + OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata + }; + + metadata! { + method: GET, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}", + } + } + + /// Request type for the get & set user suspension status endpoint. + #[request(error = ruma::api::client::Error)] + pub struct Request { + /// The user to look up. + #[ruma_api(path)] + pub user_id: OwnedUserId, + } + + /// Response type for the suspension endpoints + #[response(error = ruma::api::client::Error)] + pub struct Response { + /// Whether the user is currently suspended. + pub suspended: bool, + } + + impl Request { + /// Creates a new `Request` with the given user id. + #[must_use] + pub fn new(user_id: OwnedUserId) -> Self { + Self { user_id } + } + } + + impl Response { + /// Creates a new `Response` with the given suspension status. + #[must_use] + pub fn new(suspended: bool) -> Self { + Self { suspended } + } + } +} \ No newline at end of file diff --git a/src/ruminuwuity/admin/mod.rs b/src/ruminuwuity/admin/mod.rs new file mode 100644 index 000000000..470de8177 --- /dev/null +++ b/src/ruminuwuity/admin/mod.rs @@ -0,0 +1,3 @@ +pub mod continuwuity; +pub mod get_suspended; +pub mod set_suspended; \ No newline at end of file diff --git a/src/ruminuwuity/admin/set_suspended.rs b/src/ruminuwuity/admin/set_suspended.rs new file mode 100644 index 000000000..9d6e9be09 --- /dev/null +++ b/src/ruminuwuity/admin/set_suspended.rs @@ -0,0 +1,55 @@ +//! `PUT /_matrix/client/v1/admin/suspend/{userId}` +//! +//! Set the suspension status of a target user + +pub mod v1 { + //! `/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{userID}` ([msc]) + //! + //! [msc]: https://github.com/matrix-org/matrix-spec-proposals/pull/4323 + + use ruma::{ + OwnedUserId, api::{auth_scheme::AccessToken, request, response}, metadata + }; + + metadata! { + method: PUT, + rate_limited: false, + authentication: AccessToken, + history: { + unstable => "/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/{user_id}", + } + } + + /// Request type for the set user suspension status endpoint. + #[request(error = ruma::api::client::Error)] + pub struct Request { + /// The user to look up. + #[ruma_api(path)] + pub user_id: OwnedUserId, + + pub suspended: bool, + } + + /// Response type for the suspension endpoints + #[response(error = ruma::api::client::Error)] + pub struct Response { + /// Whether the user is currently suspended. + pub suspended: bool, + } + + impl Request { + /// Creates a new `Request` with the given user id. + #[must_use] + pub fn new(user_id: OwnedUserId, suspended: bool) -> Self { + Self { user_id, suspended } + } + } + + impl Response { + /// Creates a new `Response` with the given suspension status. + #[must_use] + pub fn new(suspended: bool) -> Self { + Self { suspended } + } + } +} \ No newline at end of file diff --git a/src/ruminuwuity/draupnir_antispam/mod.rs b/src/ruminuwuity/draupnir_antispam/mod.rs new file mode 100644 index 000000000..e81dc6fd2 --- /dev/null +++ b/src/ruminuwuity/draupnir_antispam/mod.rs @@ -0,0 +1,2 @@ +pub mod user_may_invite; +pub mod user_may_join_room; \ No newline at end of file diff --git a/src/ruminuwuity/draupnir_antispam/user_may_invite.rs b/src/ruminuwuity/draupnir_antispam/user_may_invite.rs new file mode 100644 index 000000000..524313ae8 --- /dev/null +++ b/src/ruminuwuity/draupnir_antispam/user_may_invite.rs @@ -0,0 +1,50 @@ +//! `POST /api/1/spam_check/user_may_invite` +//! +//! Checks that a user may invite the given user to the given room via Draupnir anti-spam + +pub mod v1 { + use ruma::{ + OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: AppserviceToken, + history: { + 1.0 => "/api/1/spam_check/user_may_invite", + } + } + + /// Request type for the `user_may_invite` callback. + #[request] + pub struct Request { + /// The room the invitee is being invited to + pub room_id: OwnedRoomId, + /// The user sending the invite + pub inviter: OwnedUserId, + /// The user being invited + pub invitee: OwnedUserId, + } + + /// Response type for the `user_may_invite` callback. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a new empty `Request`. + #[must_use] + pub fn new(room_id: OwnedRoomId, inviter: OwnedUserId, invitee: OwnedUserId) -> Self { + Self { room_id, inviter, invitee } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/ruminuwuity/draupnir_antispam/user_may_join_room.rs b/src/ruminuwuity/draupnir_antispam/user_may_join_room.rs new file mode 100644 index 000000000..5bb2b0bf0 --- /dev/null +++ b/src/ruminuwuity/draupnir_antispam/user_may_join_room.rs @@ -0,0 +1,50 @@ +//! `POST /api/1/spam_check/user_may_join_room` +//! +//! Endpoint that checks whether a user may join a given room via Draupnir anti-spam + +pub mod v1 { + use ruma::{ + OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: AppserviceToken, + history: { + 1.0 => "/api/1/spam_check/user_may_join_room", + } + } + + /// Request type for the `user_may_join_room` callback. + #[request] + pub struct Request { + /// The user trying to join a room + pub user: OwnedUserId, + /// The room the user is trying to join + pub room: OwnedRoomId, + /// Whether the user was invited to this room + pub is_invited: bool, + } + + /// Response type for the `user_may_join_room` callback. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a new empty `Request`. + #[must_use] + pub fn new(user: OwnedUserId, room: OwnedRoomId, is_invited: bool) -> Self { + Self { user, room, is_invited } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/ruminuwuity/invite_permission_config.rs b/src/ruminuwuity/invite_permission_config.rs new file mode 100644 index 000000000..64bd08c41 --- /dev/null +++ b/src/ruminuwuity/invite_permission_config.rs @@ -0,0 +1,247 @@ +//! Types for invite filtering ([MSC4155]). +//! +//! MSC4155: https://github.com/matrix-org/matrix-spec-proposals/pull/4155 + +use ruma::{ServerName, UserId}; +use ruma::exports::ruma_macros::EventContent; +use serde::{Deserialize, Serialize}; +use wildmatch::WildMatch; + +/// Represents a user's level of filtering on actions from another user or server. +/// "Ignore" and "block" are defined in [MSC4283]. +/// +/// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FilterLevel { + Allow, + Ignore, + Block, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +#[ruma_event(type = "m.invite_permission_config", kind = GlobalAccountData)] +pub struct InvitePermissionConfigEventContent { + /// A global on/off toggle for all rules + #[serde(default = "ruma::serde::default_true")] + pub enabled: bool, + + /// A list of globs matching users which are allowed to send an invite. + /// Entries in this list supersede entries in the ignored and blocked lists. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub allowed_users: Vec, + /// A list of globs matching users whose invites should be ignored (as defined in [MSC4283]). + /// + /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub ignored_users: Vec, + /// A list of globs matching users whose invites should be blocked (as defined in [MSC4283]). + /// Invites from blocked users should be refused with the M_INVITE_BLOCKED status code. + /// + /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blocked_users: Vec, + + + /// A list of globs matching servers which are allowed to send an invite. + /// Entries in this list supersede entries in the ignored and blocked lists. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub allowed_servers: Vec, + /// A list of globs matching servers whose invites should be ignored (as defined in [MSC4283]). + /// + /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub ignored_servers: Vec, + /// A list of globs matching servers whose invites should be blocked (as defined in [MSC4283]). + /// Invites from blocked servers should be refused with the M_INVITE_BLOCKED status code. + /// + /// MSC4283: https://github.com/matrix-org/matrix-spec-proposals/pull/4283 + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blocked_servers: Vec, +} + +impl InvitePermissionConfigEventContent { + /// Creates a new `InvitePermissionConfigEventContent` from six lists of globs. + #[must_use] + pub fn new( + enabled: bool, + allowed_users: Vec, + ignored_users: Vec, + blocked_users: Vec, + allowed_servers: Vec, + ignored_servers: Vec, + blocked_servers: Vec, + ) -> Self { + Self { + enabled, + allowed_users, + ignored_users, + blocked_users, + allowed_servers, + ignored_servers, + blocked_servers, + } + } + + /// Test the filters against a user id. This function will check both the + /// user rules _and_ the server rules. + #[must_use] + #[allow(clippy::if_same_then_else)] + pub fn user_filter_level(&self, user: &UserId) -> FilterLevel { + if !self.enabled { + FilterLevel::Allow + } else if Self::matches(&self.allowed_users, user.as_str()) { + FilterLevel::Allow + } else if Self::matches(&self.ignored_users, user.as_str()) { + FilterLevel::Ignore + } else if Self::matches(&self.blocked_users, user.as_str()) { + FilterLevel::Block + } else { + self.server_filter_level(user.server_name()) + } + } + + /// Test the filters against a server name. Port numbers are ignored. + #[must_use] + pub fn server_filter_level(&self, server: &ServerName) -> FilterLevel { + if !self.enabled { + FilterLevel::Allow + } else { + let server = server.host(); + if Self::matches(&self.allowed_servers, server) { + FilterLevel::Allow + } else if Self::matches(&self.ignored_servers, server) { + FilterLevel::Ignore + } else if Self::matches(&self.blocked_servers, server) { + FilterLevel::Block + } else { + FilterLevel::Allow + } + } + } + + fn matches(a: &[String], s: &str) -> bool { + a.iter().map(String::as_str).any(|a| WildMatch::new(a).matches(s)) + } +} + +#[cfg(test)] +mod tests { + use ruma::{ServerName, UserId, events::GlobalAccountDataEvent}; + use serde_json::{from_value as from_json_value, json}; + + use crate::invite_permission_config::{FilterLevel, InvitePermissionConfigEventContent}; + + fn user_id(id: &str) -> &UserId { + <&UserId>::try_from(id).unwrap() + } + + fn server_name(name: &str) -> &ServerName { + <&ServerName>::try_from(name).unwrap() + } + + #[test] + fn default_values() { + let data = json!({ + "content": {}, + "type": "org.matrix.msc4155.invite_permission_config" + }); + + let event: GlobalAccountDataEvent = from_json_value(data).unwrap(); + assert!(event.content.enabled); + assert!(event.content.allowed_users.is_empty()); + assert!(event.content.ignored_users.is_empty()); + assert!(event.content.blocked_users.is_empty()); + assert!(event.content.allowed_servers.is_empty()); + assert!(event.content.ignored_servers.is_empty()); + assert!(event.content.blocked_servers.is_empty()); + assert_eq!(event.content.user_filter_level(user_id("@alice:example.com")), FilterLevel::Allow); + assert_eq!(event.content.server_filter_level(server_name("example.com")), FilterLevel::Allow); + } + + #[test] + fn block_the_world() { + let event = InvitePermissionConfigEventContent { + enabled: true, + blocked_servers: vec!["*".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:foo.com:8080")), FilterLevel::Block); + assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); + } + + #[test] + fn only_goodguys() { + let event = InvitePermissionConfigEventContent { + enabled: true, + allowed_servers: vec!["goodguys.org".to_owned()], + blocked_servers: vec!["*".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); + } + + #[test] + fn exclude_badguys() { + let event = InvitePermissionConfigEventContent { + enabled: true, + blocked_servers: vec!["badguys.org".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org:8080")), FilterLevel::Block); + assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); + } + + #[test] + fn only_goodguys_except_for_kevin() { + let event = InvitePermissionConfigEventContent { + enabled: true, + blocked_users: vec!["@kevin:goodguys.org".to_owned()], + allowed_servers: vec!["goodguys.org".to_owned()], + blocked_servers: vec!["*".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@kevin:goodguys.org")), FilterLevel::Block); + assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); + } + + #[test] + fn no_badguys_except_for_alice() { + let event = InvitePermissionConfigEventContent { + enabled: true, + allowed_users: vec!["@alice:badguys.org".to_owned()], + blocked_servers: vec!["badguys.org".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@alice:badguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@kevin:badguys.org")), FilterLevel::Block); + } + + #[test] + fn only_goodguys_and_ignore_reallybadguys() { + let event = InvitePermissionConfigEventContent { + enabled: true, + allowed_servers: vec!["goodguys.org".to_owned()], + ignored_servers: vec!["reallybadguys.org".to_owned()], + blocked_servers: vec!["*".to_owned()], + ..Default::default() + }; + + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org:8080")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@alice:goodguys.org")), FilterLevel::Allow); + assert_eq!(event.user_filter_level(user_id("@bob:bar.com")), FilterLevel::Block); + assert_eq!(event.user_filter_level(user_id("@kevin:reallybadguys.org")), FilterLevel::Ignore); + } +} \ No newline at end of file diff --git a/src/ruminuwuity/meowlnir_antispam/accept_make_join.rs b/src/ruminuwuity/meowlnir_antispam/accept_make_join.rs new file mode 100644 index 000000000..d9fa5ef56 --- /dev/null +++ b/src/ruminuwuity/meowlnir_antispam/accept_make_join.rs @@ -0,0 +1,59 @@ +//! `POST /_meowlnir/antispam/*/accept_make_join` +//! +//! Endpoint to accept or decline incoming make_join federation requests. +//! Used by the `fi.mau.spam_check` restricted join rule. +//! +//! References: +//! - https://mau.dev/maunium/synapse/-/blob/52741d3/synapse/handlers/event_auth.py#L280-292 + +pub mod v1 { + use ruma::{ + OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: AppserviceToken, + history: { + 1.0 => "/_meowlnir/antispam/{management_room_id}/accept_make_join", + } + } + + /// Request type for the `accept_make_join` callback. + #[request] + pub struct Request { + /// The relevant management room + #[ruma_api(path)] + pub management_room_id: OwnedRoomId, + /// The user trying to join a room + pub user: OwnedUserId, + /// The room the user is trying to join + pub room: OwnedRoomId, + } + + /// Response type for the `accept_make_join` callback. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a new empty `Request`. + #[must_use] + pub fn new( + management_room_id: OwnedRoomId, + user: OwnedUserId, + room: OwnedRoomId, + ) -> Self { + Self { management_room_id, user, room } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/ruminuwuity/meowlnir_antispam/mod.rs b/src/ruminuwuity/meowlnir_antispam/mod.rs new file mode 100644 index 000000000..e0d02480b --- /dev/null +++ b/src/ruminuwuity/meowlnir_antispam/mod.rs @@ -0,0 +1,4 @@ +pub mod user_may_invite; +pub mod user_may_join_room; + +pub mod accept_make_join; \ No newline at end of file diff --git a/src/ruminuwuity/meowlnir_antispam/user_may_invite.rs b/src/ruminuwuity/meowlnir_antispam/user_may_invite.rs new file mode 100644 index 000000000..91289c753 --- /dev/null +++ b/src/ruminuwuity/meowlnir_antispam/user_may_invite.rs @@ -0,0 +1,58 @@ +//! `POST /_meowlnir/antispam/*/user_may_invite` +//! +//! Checks that a user may invite the given user to the given room via Meowlnir anti-spam + +pub mod v1 { + use ruma::{ + OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: AppserviceToken, + history: { + 1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_invite", + } + } + + /// Request type for the `user_may_invite` callback. + #[request] + pub struct Request { + /// The relevant management room + #[ruma_api(path)] + pub management_room_id: OwnedRoomId, + /// The user sending the invite + pub inviter: OwnedUserId, + /// The user being invited + pub invitee: OwnedUserId, + /// The room the invitee is being invited to + pub room_id: OwnedRoomId, + } + + /// Response type for the `user_may_invite` callback. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a new empty `Request`. + #[must_use] + pub fn new( + management_room_id: OwnedRoomId, + inviter: OwnedUserId, + invitee: OwnedUserId, + room_id: OwnedRoomId, + ) -> Self { + Self { management_room_id, inviter, invitee, room_id } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/ruminuwuity/meowlnir_antispam/user_may_join_room.rs b/src/ruminuwuity/meowlnir_antispam/user_may_join_room.rs new file mode 100644 index 000000000..606a94a42 --- /dev/null +++ b/src/ruminuwuity/meowlnir_antispam/user_may_join_room.rs @@ -0,0 +1,58 @@ +//! `POST /_meowlnir/antispam/*/user_may_join_room` +//! +//! Endpoint to track invite joins via Meowlnir anti-spam + +pub mod v1 { + use ruma::{ + OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: AppserviceToken, + history: { + 1.0 => "/_meowlnir/antispam/{management_room_id}/user_may_join_room", + } + } + + /// Request type for the `user_may_join_room` callback. + #[request] + pub struct Request { + /// The relevant management room + #[ruma_api(path)] + pub management_room_id: OwnedRoomId, + /// The user trying to join a room + pub user: OwnedUserId, + /// The room the user is trying to join + pub room: OwnedRoomId, + /// Whether the user was invited to this room + pub is_invited: bool, + } + + /// Response type for the `user_may_join_room` callback. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a new empty `Request`. + #[must_use] + pub fn new( + management_room_id: OwnedRoomId, + user: OwnedUserId, + room: OwnedRoomId, + is_invited: bool, + ) -> Self { + Self { management_room_id, user, room, is_invited } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/ruminuwuity/mod.rs b/src/ruminuwuity/mod.rs new file mode 100644 index 000000000..7406f840d --- /dev/null +++ b/src/ruminuwuity/mod.rs @@ -0,0 +1,7 @@ +//! Ruminuwuity: Continuwuity-specific APIs and structs that depend only on Ruma + +pub mod admin; +pub mod draupnir_antispam; +pub mod meowlnir_antispam; +pub mod policy; +pub mod invite_permission_config; \ No newline at end of file diff --git a/src/ruminuwuity/policy/event.rs b/src/ruminuwuity/policy/event.rs new file mode 100644 index 000000000..a044a2f87 --- /dev/null +++ b/src/ruminuwuity/policy/event.rs @@ -0,0 +1,100 @@ +//! Types for the [`org.matrix.msc4284.policy`] event. +//! +//! [`org.matrix.msc4284.policy`]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 + +use ruma::exports::ruma_macros::EventContent; +use serde::{Deserialize, Serialize}; + +use ruma::events::EmptyStateKey; + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent, Default)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +#[ruma_event(type = "org.matrix.msc4284.policy", kind = State, state_key_type = EmptyStateKey)] +pub struct RoomPolicyEventContent { + /// The server name of the room's policy server. + /// + /// If the value is empty or unreachable, the policy server should be ignored. + pub via: Option, + /// The public key this policy server will sign with. + pub public_key: Option +} + +impl RoomPolicyEventContent { + /// Create an empty `RoomPolicyEventContent`. + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct PolicyServerResponseContent { + /// The policy server's verdict. Either `ok` or `spam`. + pub recommendation: String, +} + +impl PolicyServerResponseContent { + /// Create a new `PolicyServerResponseContent` with the given recommendation. + #[must_use] + pub fn new(recommendation: String) -> Self { + Self { recommendation } + } +} + +impl From for PolicyServerResponseContent { + fn from(recommendation: String) -> Self { + Self::new(recommendation) + } +} + +#[cfg(test)] +mod tests { + use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + + use super::RoomPolicyEventContent; + use ruma::events::OriginalStateEvent; + + #[test] + fn serialization() { + let content = RoomPolicyEventContent { + via: Some("example.com".to_owned()), + public_key: Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned()) + }; + + let actual = to_json_value(content).unwrap(); + let expected = json!({ + "via": "example.com", + "public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s" + }); + + assert_eq!(actual, expected); + } + + #[test] + fn deserialization() { + let json_data = json!({ + "content": { + "via": "example.com", + "public_key": "6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s" + }, + "event_id": "123:example.com", + "origin_server_ts": 1, + "room_id": "!123456:example.com", + "sender": "@carl:example.com", + "state_key": "", + "type": "org.matrix.msc4284.policy" + }); + + let content = from_json_value::>(json_data) + .unwrap() + .content; + assert_eq!( + content.via, + Some("example.com".to_owned()) + ); + assert_eq!( + content.public_key, + Some("6yhHGKhCiXTSEN2ksjV7kX_N6rBQZ3Xb-M7LlC6NS-s".to_owned()) + ); + } +} diff --git a/src/ruminuwuity/policy/mod.rs b/src/ruminuwuity/policy/mod.rs new file mode 100644 index 000000000..7b60a5d78 --- /dev/null +++ b/src/ruminuwuity/policy/mod.rs @@ -0,0 +1,4 @@ +pub mod policy_check; +pub mod policy_sign; +pub mod report_content; +pub mod event; \ No newline at end of file diff --git a/src/ruminuwuity/policy/policy_check.rs b/src/ruminuwuity/policy/policy_check.rs new file mode 100644 index 000000000..592655d7e --- /dev/null +++ b/src/ruminuwuity/policy/policy_check.rs @@ -0,0 +1,60 @@ +//! `POST /_matrix/policy/unstable/org.matrix.msc4284/event/{eventId}/check` +//! +//! Checks if an event is allowed by the room's policy server. +//! This is now a fallback behaviour that will be removed later. + +pub mod unstable { + //! `/policy/unstable/org.matrix.msc4284` ([spec]) + //! + //! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 + + use ruma::{ + OwnedEventId, api::{federation::authentication::ServerSignatures, request, response}, metadata + }; + use serde_json::value::RawValue as RawJsonValue; + + metadata! { + method: POST, + rate_limited: false, + authentication: ServerSignatures, + history: { + unstable => "/_matrix/policy/unstable/org.matrix.msc4284/event/{event_id}/check", + } + } + + /// Response type for the `check` endpoint. + #[response] + pub struct Response { + /// Either `ok` or `spam`, indicating the policy server's recommendation. + pub recommendation: String, + } + + impl Response { + /// Creates a new `Response` with the given recommendation. + #[must_use] + pub fn new(recommendation: String) -> Self { + Self { recommendation } + } + } + + /// Request type for the `check` endpoint. + #[request] + pub struct Request { + /// The event ID to check. + #[ruma_api(path)] + pub event_id: OwnedEventId, + + /// The PDU body (optional) + #[ruma_api(body)] + #[serde(skip_serializing_if = "Option::is_none")] + pub pdu: Option>, + } + + impl Request { + /// Creates a new `Request` with the given event ID. + #[must_use] + pub fn new(event_id: OwnedEventId) -> Self { + Self { event_id, pdu: None } + } + } +} diff --git a/src/ruminuwuity/policy/policy_sign.rs b/src/ruminuwuity/policy/policy_sign.rs new file mode 100644 index 000000000..8e8c9ae29 --- /dev/null +++ b/src/ruminuwuity/policy/policy_sign.rs @@ -0,0 +1,55 @@ +//! `POST /_matrix/policy/unstable/org.matrix.msc4284/sign` +//! +//! Asks a policy server to sign our event + +pub mod unstable { + //! `/policy/unstable/org.matrix.msc4284` ([spec]) + //! + //! [spec]: https://github.com/matrix-org/matrix-spec-proposals/pull/4284 + use ruma::{ + ServerSignatures, + api::{federation::authentication::ServerSignatures as ServerSignaturesAuth, request, response}, metadata + }; + use serde_json::value::RawValue as RawJsonValue; + + metadata! { + method: POST, + rate_limited: false, + authentication: ServerSignaturesAuth, + history: { + unstable => "/_matrix/policy/unstable/org.matrix.msc4284/sign", + } + } + + /// Response type for the `sign` endpoint. + #[response] + pub struct Response { + /// The signatures returned from the policy server (if provided) + #[ruma_api(body)] + pub signatures: Option + } + + impl Response { + /// Creates a new `Response` with the given recommendation. + #[must_use] + pub fn new(signatures: Option) -> Self { + Self { signatures } + } + } + + /// Request type for the `sign` endpoint. + #[request] + pub struct Request { + /// The PDU body (in canonical JSON) + #[ruma_api(body)] + pub pdu: Box, + } + + impl Request { + /// Creates a new `Request` with the given event JSON + #[must_use] + pub fn new(pdu: Box) -> Self { + Self { pdu } + } + } +} \ No newline at end of file diff --git a/src/ruminuwuity/policy/report_content.rs b/src/ruminuwuity/policy/report_content.rs new file mode 100644 index 000000000..959981920 --- /dev/null +++ b/src/ruminuwuity/policy/report_content.rs @@ -0,0 +1,58 @@ +//! `GET /_matrix/federation/*/rooms/{roomId}/report/{eventId}` +//! +//! Send a request to report an event originating from another server. + +pub mod msc3843 { + //! `MSC3843` ([MSC]) + //! + //! [MSC]: https://github.com/matrix-org/matrix-spec-proposals/pull/3843 + + use ruma::{ + OwnedEventId, OwnedRoomId, api::{federation::authentication::ServerSignatures, request, response}, metadata + }; + + metadata! { + method: POST, + rate_limited: false, + authentication: ServerSignatures, + history: { + unstable => "/_matrix/federation/unstable/org.matrix.msc3843/rooms/{room_id}/report/{event_id}", + } + } + + /// Request type for the `report_content` endpoint. + #[request] + pub struct Request { + /// The room ID that the reported event was sent in. + #[ruma_api(path)] + pub room_id: OwnedRoomId, + + /// The event being reported. + #[ruma_api(path)] + pub event_id: OwnedEventId, + + /// The reason that the event is being reported. + pub reason: String, + } + + /// Response type for the `report_content` endpoint. + #[response] + #[derive(Default)] + pub struct Response; + + impl Request { + /// Creates a `Request` with the given room ID, event ID and reason. + #[must_use] + pub fn new(room_id: OwnedRoomId, event_id: OwnedEventId, reason: String) -> Self { + Self { room_id, event_id, reason } + } + } + + impl Response { + /// Creates a new empty `Response`. + #[must_use] + pub fn new() -> Self { + Self::default() + } + } +} diff --git a/src/service/Cargo.toml b/src/service/Cargo.toml index 7d9d90915..f7c0ae71b 100644 --- a/src/service/Cargo.toml +++ b/src/service/Cargo.toml @@ -111,6 +111,7 @@ rand.workspace = true regex.workspace = true reqwest.workspace = true ruma.workspace = true +ruminuwuity.workspace = true rustyline-async.workspace = true rustyline-async.optional = true serde_json.workspace = true diff --git a/src/service/account_data/mod.rs b/src/service/account_data/mod.rs index 3f8c289a3..12dedbaad 100644 --- a/src/service/account_data/mod.rs +++ b/src/service/account_data/mod.rs @@ -7,17 +7,21 @@ use database::{Deserialized, Handle, Ignore, Json, Map}; use futures::{Stream, StreamExt, TryFutureExt}; use ruma::{ - RoomId, UserId, - events::{ - AnyGlobalAccountDataEvent, AnyRawAccountDataEvent, AnyRoomAccountDataEvent, + OwnedRoomId, OwnedUserId, RoomId, UserId, events::{ + AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, GlobalAccountDataEventType, RoomAccountDataEventType, - }, - serde::Raw, + }, serde::Raw }; use serde::Deserialize; use crate::{Dep, globals}; +#[derive(Debug)] +pub enum AnyRawAccountDataEvent { + Room(Raw), + Global(Raw), +} + pub struct Service { services: Services, db: Data, @@ -132,7 +136,7 @@ pub fn changes_since<'a>( since: Option, to: Option, ) -> impl Stream + Send + 'a { - type Key<'a> = (Option<&'a RoomId>, &'a UserId, u64, Ignore); + type Key = (Option, OwnedUserId, u64, Ignore); // Skip the data that's exactly at since, because we sent that last time // ...unless this is an initial sync, in which case send everything @@ -142,8 +146,8 @@ pub fn changes_since<'a>( .roomuserdataid_accountdata .stream_from(&first_possible) .ignore_err() - .ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key<'_>, _)| { - room_id == *room_id_ && user_id == *user_id_ && to.is_none_or(|to| *count <= to) + .ready_take_while(move |((room_id_, user_id_, count, _), _): &(Key, _)| { + room_id == room_id_.as_deref() && user_id == user_id_ && to.is_none_or(|to| *count <= to) }) .map(move |(_, v)| { match room_id { diff --git a/src/service/admin/create.rs b/src/service/admin/create.rs index 913acc2d6..0069e8752 100644 --- a/src/service/admin/create.rs +++ b/src/service/admin/create.rs @@ -13,7 +13,6 @@ member::{MembershipState, RoomMemberEventContent}, name::RoomNameEventContent, power_levels::RoomPowerLevelsEventContent, - preview_url::RoomPreviewUrlsEventContent, topic::RoomTopicEventContent, }, }; @@ -25,7 +24,7 @@ /// Users in this room are considered admins by conduwuit, and the room can be /// used to issue admin commands by talking to the server user inside it. pub async fn create_admin_room(services: &Services) -> Result { - let room_id = RoomId::new(services.globals.server_name()); + let room_id = RoomId::new_v1(services.globals.server_name()); let room_version = &RoomVersionId::V11; let _short_id = services @@ -34,22 +33,24 @@ pub async fn create_admin_room(services: &Services) -> Result { .get_or_create_shortroomid(&room_id) .await; - let state_lock = services.rooms.state.mutex.lock(&room_id).await; + let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await; // Create a user for the server let server_user = services.globals.server_user.as_ref(); services.users.create(server_user, None, None).await?; - let create_content = { + let mut create_content = { use RoomVersionId::*; match room_version { | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => RoomCreateEventContent::new_v1(server_user.into()), - | V11 => RoomCreateEventContent::new_v11(), - | _ => RoomCreateEventContent::new_v12(), + | _ => RoomCreateEventContent::new_v11(), } }; + create_content.federate = true; + create_content.room_version = room_version.clone(); + info!("Creating admin room {} with version {}", room_id, room_version); // 1. The room create event @@ -57,12 +58,7 @@ pub async fn create_admin_room(services: &Services) -> Result { .rooms .timeline .build_and_append_pdu( - PduBuilder::state(String::new(), &RoomCreateEventContent { - federate: true, - predecessor: None, - room_version: room_version.clone(), - ..create_content - }), + PduBuilder::state(String::new(), &create_content), server_user, Some(&room_id), &state_lock, @@ -89,14 +85,14 @@ pub async fn create_admin_room(services: &Services) -> Result { // 3. Power levels let users = BTreeMap::from_iter([(server_user.into(), 69420.into())]); + let mut power_levels_content = RoomPowerLevelsEventContent::new(&room_version.rules().unwrap().authorization); + power_levels_content.users = users; + services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(String::new(), &RoomPowerLevelsEventContent { - users, - ..Default::default() - }), + PduBuilder::state(String::new(), &power_levels_content), server_user, Some(&room_id), &state_lock, @@ -163,13 +159,12 @@ pub async fn create_admin_room(services: &Services) -> Result { .boxed() .await?; + let room_topic = format!("Manage {} | Run commands prefixed with `!admin` | Run `!admin -h` for help | Documentation: https://continuwuity.org/", services.config.server_name); services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(String::new(), &RoomTopicEventContent { - topic: format!("Manage {} | Run commands prefixed with `!admin` | Run `!admin -h` for help | Documentation: https://continuwuity.org/", services.config.server_name), - }), + PduBuilder::state(String::new(), &RoomTopicEventContent::markdown(room_topic)), server_user, Some(&room_id), &state_lock, @@ -178,16 +173,14 @@ pub async fn create_admin_room(services: &Services) -> Result { .await?; // 6. Room alias - let alias = &services.globals.admin_alias; + let mut alias_content = RoomCanonicalAliasEventContent::new(); + alias_content.alias = Some(services.globals.admin_alias.clone()); services .rooms .timeline .build_and_append_pdu( - PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent { - alias: Some(alias.clone()), - alt_aliases: Vec::new(), - }), + PduBuilder::state(String::new(), &alias_content), server_user, Some(&room_id), &state_lock, @@ -198,20 +191,7 @@ pub async fn create_admin_room(services: &Services) -> Result { services .rooms .alias - .set_alias(alias, &room_id, server_user)?; - - // 7. (ad-hoc) Disable room URL previews for everyone by default - services - .rooms - .timeline - .build_and_append_pdu( - PduBuilder::state(String::new(), &RoomPreviewUrlsEventContent { disabled: true }), - server_user, - Some(&room_id), - &state_lock, - ) - .boxed() - .await?; + .set_alias(&services.globals.admin_alias, &room_id, server_user)?; Ok(()) } diff --git a/src/service/admin/grant.rs b/src/service/admin/grant.rs index 3054627a7..1388ab73c 100644 --- a/src/service/admin/grant.rs +++ b/src/service/admin/grant.rs @@ -27,7 +27,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result { return Ok(()); }; - let state_lock = self.services.state.mutex.lock(&room_id).await; + let state_lock = self.services.state.mutex.lock(room_id.as_str()).await; if self.services.state_cache.is_joined(user_id, &room_id).await { return Err!(debug_warn!("User is already joined in the admin room")); @@ -100,7 +100,7 @@ pub async fn make_user_admin(&self, user_id: &UserId) -> Result { "", ) .await - .unwrap_or_default(); + .expect("admin room should have power levels"); room_power_levels .users @@ -135,9 +135,7 @@ async fn set_room_tag(&self, room_id: &RoomId, user_id: &UserId, tag: &str) -> R .account_data .get_room(room_id, user_id, RoomAccountDataEventType::Tag) .await - .unwrap_or_else(|_| TagEvent { - content: TagEventContent { tags: BTreeMap::new() }, - }); + .unwrap_or_else(|_| TagEvent::new(TagEventContent::new(BTreeMap::new()))); event .content @@ -177,9 +175,9 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result { return Err!(error!("No admin room available or created.")); }; - let state_lock = self.services.state.mutex.lock(&room_id).await; + let state_lock = self.services.state.mutex.lock(room_id.as_str()).await; - let event = match self + let mut member_content = match self .services .state_accessor .get_member(&room_id, user_id) @@ -203,17 +201,13 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result { }, }; + member_content.membership = Leave; + member_content.reason = Some("Admin Revoked".to_owned()); + self.services .timeline .build_and_append_pdu( - PduBuilder::state(user_id.to_string(), &RoomMemberEventContent { - membership: Leave, - reason: Some("Admin Revoked".into()), - is_direct: None, - join_authorized_via_users_server: None, - third_party_invite: None, - ..event - }), + PduBuilder::state(user_id.to_string(), &member_content), self.services.globals.server_user.as_ref(), Some(&room_id), &state_lock, diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index a887dfa79..c44b1b3d2 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -17,7 +17,7 @@ use futures::{Future, FutureExt, StreamExt, TryFutureExt}; use loole::{Receiver, Sender}; use ruma::{ - Mxc, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId, + OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId, UInt, UserId, events::{ Mentions, room::{ @@ -30,7 +30,7 @@ }; use tokio::sync::RwLock; -use crate::{Dep, account_data, globals, media::MXC_LENGTH, rooms, rooms::state::RoomMutexGuard}; +use crate::{Dep, account_data, globals, media::{MXC_LENGTH, mxc::Mxc}, rooms::{self, state::RoomMutexGuard}}; pub struct Service { services: Services, @@ -200,19 +200,15 @@ pub async fn text_or_file( .await .expect("failed to create text file"); let size_u64: u64 = message_content.body().len().try_into().map_or(0, |n| n); - let metadata = FileInfo { - mimetype: Some("text/markdown".to_owned()), - size: Some(UInt::new_saturating(size_u64)), - thumbnail_info: None, - thumbnail_source: None, - }; - let content = FileMessageEventContent { - body: "Output was too large to send as text.".to_owned(), - formatted: None, - filename: Some("output.md".to_owned()), - source: MediaSource::Plain(file), - info: Some(Box::new(metadata)), - }; + + let mut metadata = FileInfo::new(); + metadata.mimetype = Some("text/markdown".to_owned()); + metadata.size = Some(UInt::new_saturating(size_u64)); + + let mut content = FileMessageEventContent::plain("Output was too large to send as text.".to_owned(), file); + content.filename = Some("output.md".to_owned()); + content.info = Some(Box::new(metadata)); + RoomMessageEventContent::new(MessageType::File(content)) } else { message_content diff --git a/src/service/antispam/mod.rs b/src/service/antispam/mod.rs index 5e61588e2..98cb69f0f 100644 --- a/src/service/antispam/mod.rs +++ b/src/service/antispam/mod.rs @@ -2,7 +2,8 @@ use async_trait::async_trait; use conduwuit::{Result, config::Antispam, debug}; -use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam}; +use ruma::{OwnedRoomId, OwnedUserId, api::{auth_scheme::AppserviceToken, path_builder::VersionHistory}}; +use ruminuwuity::{draupnir_antispam, meowlnir_antispam}; use crate::{client, config, sending, service::Dep}; @@ -37,7 +38,7 @@ async fn send_antispam_request( request: T, ) -> Result where - T: ruma::api::OutgoingRequest + Debug + Send, + T: ruma::api::OutgoingRequest + Debug + Send, { sending::antispam::send_antispam_request( &self.services.client.appservice, diff --git a/src/service/emergency/mod.rs b/src/service/emergency/mod.rs index 9dc6fbe49..aaf1b547a 100644 --- a/src/service/emergency/mod.rs +++ b/src/service/emergency/mod.rs @@ -72,9 +72,7 @@ async fn set_emergency_access(&self) -> Result { None, server_user, GlobalAccountDataEventType::PushRules.to_string().into(), - &serde_json::to_value(&GlobalAccountDataEvent { - content: PushRulesEventContent { global: ruleset }, - }) + &serde_json::to_value(&GlobalAccountDataEvent::new(PushRulesEventContent::new(ruleset))) .expect("to json value always works"), ) .await?; diff --git a/src/service/federation/execute.rs b/src/service/federation/execute.rs index 9ea1d2601..aadf204dd 100644 --- a/src/service/federation/execute.rs +++ b/src/service/federation/execute.rs @@ -1,64 +1,85 @@ -use std::{fmt::Debug, mem}; +use std::{any::Any, borrow::Cow, fmt::Debug, mem, sync::LazyLock}; use bytes::Bytes; use conduwuit::{ - Err, Error, Result, debug, debug::INFO_SPAN_LEVEL, debug_error, debug_warn, err, implement, - trace, utils::response::LimitReadExt, -}; -use http::{HeaderValue, header::AUTHORIZATION}; + Err, Error, Result, debug, debug_error, debug_warn, err, implement, trace, utils::response::LimitReadExt, matrix::versions::{unstable_features, versions}, }; use ipaddress::IPAddress; use reqwest::{Client, Method, Request, Response, Url}; use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId, - api::{ - EndpointError, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, - client::error::Error as RumaError, federation::authentication::XMatrix, - }, - serde::Base64, + CanonicalJsonObject, CanonicalJsonValue, ServerName, ServerSigningKeyId, api::{ + EndpointError, IncomingResponse, Metadata, OutgoingRequest, SupportedVersions, auth_scheme::{AuthScheme, NoAuthentication, SendAccessToken}, client::error::Error as RumaError, federation::authentication::{ServerSignatures, ServerSignaturesInput, XMatrix}, path_builder::{PathBuilder, SinglePath, VersionHistory} + }, serde::Base64 }; -use crate::resolver::actual::ActualDest; +use crate::{SUPPORTED_VERSIONS, resolver::actual::ActualDest}; /// Sends a request to a federation server #[implement(super::Service)] #[tracing::instrument(skip_all, name = "request", level = "debug")] -pub async fn execute(&self, dest: &ServerName, request: T) -> Result +pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, { let client = &self.services.client.federation; - self.execute_on(client, dest, request).await + self.execute_signed(client, dest, request).await } /// Like execute() but with a very large timeout #[implement(super::Service)] #[tracing::instrument(skip_all, name = "synapse", level = "debug")] -pub async fn execute_synapse( +pub async fn execute_synapse<'i, T>( &self, dest: &ServerName, request: T, ) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, { let client = &self.services.client.synapse; - self.execute_on(client, dest, request).await + self.execute_signed(client, dest, request).await +} + +#[implement(super::Service)] +pub async fn execute_unauthenticated<'i, T>(&self, dest: &ServerName, request: T) -> Result +where + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, +{ + let client = &self.services.client.federation; + let authentication = SendAccessToken::None; + + self.execute_on(client, dest, request, authentication).await +} + +#[implement(super::Service)] +pub async fn execute_signed<'i, T>(&self, client: &Client, dest: &ServerName, request: T) -> Result +where + T: OutgoingRequest::: FederationPathBuilderInput>> + Send, +{ + let authentication = ServerSignaturesInput::new( + self.services.server.name.clone(), + dest.to_owned(), + self.services.server_keys.keypair(), + ); + + self.execute_on(client, dest, request, authentication).await } #[implement(super::Service)] #[tracing::instrument( name = "fed", - level = INFO_SPAN_LEVEL, - skip(self, client, request), + level = "info", + skip(self, client, request, authentication), )] -pub async fn execute_on( +pub async fn execute_on<'i, T, PathBuilderInput>( &self, client: &Client, dest: &ServerName, request: T, + authentication: ::Input<'_>, ) -> Result where - T: OutgoingRequest + Send, + T: OutgoingRequest:: = PathBuilderInput>> + Send, + PathBuilderInput: FederationPathBuilderInput { if !self.services.server.config.allow_federation { return Err!(Config("allow_federation", "Federation is disabled.")); @@ -69,8 +90,17 @@ pub async fn execute_on( } let actual = self.services.resolver.get_actual_dest(dest).await?; - let request = into_http_request::(&actual, request)?; - let request = self.prepare(dest, request)?; + + let request = Request::try_from( + request.try_into_http_request::>( + actual.string().as_str(), + authentication, + PathBuilderInput::create(), + )? + )?; + self.validate_url(request.url())?; + self.services.server.check_running()?; + self.perform::(dest, &actual, request, client).await } @@ -98,17 +128,6 @@ async fn perform( } } -#[implement(super::Service)] -fn prepare(&self, dest: &ServerName, mut request: http::Request>) -> Result { - self.sign_request(&mut request, dest); - - let request = Request::try_from(request)?; - self.validate_url(request.url())?; - self.services.server.check_running()?; - - Ok(request) -} - #[implement(super::Service)] fn validate_url(&self, url: &Url) -> Result<()> { if let Some(url_host) = url.host_str() { @@ -229,90 +248,27 @@ fn handle_error( Err(e.into()) } -#[implement(super::Service)] -fn sign_request(&self, http_request: &mut http::Request>, dest: &ServerName) { - type Member = (String, Value); - type Value = CanonicalJsonValue; - type Object = CanonicalJsonObject; - - let origin = &self.services.server.name; - let body = http_request.body(); - let uri = http_request - .uri() - .path_and_query() - .expect("http::Request missing path_and_query"); - - let mut req: Object = if !body.is_empty() { - let content: CanonicalJsonValue = - serde_json::from_slice(body).expect("failed to serialize body"); - - let authorization: [Member; 5] = [ - ("content".into(), content), - ("destination".into(), dest.as_str().into()), - ("method".into(), http_request.method().as_str().into()), - ("origin".into(), origin.as_str().into()), - ("uri".into(), uri.to_string().into()), - ]; - - authorization.into() - } else { - let authorization: [Member; 4] = [ - ("destination".into(), dest.as_str().into()), - ("method".into(), http_request.method().as_str().into()), - ("origin".into(), origin.as_str().into()), - ("uri".into(), uri.to_string().into()), - ]; - - authorization.into() - }; - - self.services - .server_keys - .sign_json(&mut req) - .expect("request signing failed"); - - let signatures = req["signatures"] - .as_object() - .and_then(|object| object[origin.as_str()].as_object()) - .expect("origin signatures object"); - - let key: &ServerSigningKeyId = signatures - .keys() - .next() - .map(|k| k.as_str().try_into()) - .expect("at least one signature from this origin") - .expect("keyid is json string"); - - let sig: Base64 = signatures - .values() - .next() - .map(|s| s.as_str().map(Base64::parse)) - .expect("at least one signature from this origin") - .expect("signature is json string") - .expect("signature is valid base64"); - - let x_matrix = XMatrix::new(origin.into(), dest.into(), key.into(), sig); - let authorization = HeaderValue::from(&x_matrix); - let authorization = http_request - .headers_mut() - .insert(AUTHORIZATION, authorization); - - debug_assert!(authorization.is_none(), "Authorization header already present"); +/// A trait for the input types of acceptable path builders for outgoing federation requests. +/// +/// Ruma uses Rust's type system to encode the versioning scheme of endpoints in the Matrix spec. +/// Every endpoint has a `PathBuilder` associated type, which has an `Input` associated type. +/// Endpoints with multiple versions have `VersionHistory` as their `PathBuilder`, which has `SupportedVersions` +/// as its `Input` type. Endpoints with no version have `SinglePath` as their `PathBuilder`, which has `()` as its `Input` type. +/// Both `SupportedVersions` and `()` can be created out of thin air using static data (or no data at all). This property +/// is what the `FederationPathBuilderInput` trait represents. +/// +/// This trait allows the federation sender service's functions to accept requests for either versioned or unversioned endpoints, +/// by requiring that the `Input` of the `PathBuilder` of the endpoint implements `FederationPathBuilderInput`. +pub(crate) trait FederationPathBuilderInput { + fn create() -> Self; } -fn into_http_request(actual: &ActualDest, request: T) -> Result>> -where - T: OutgoingRequest + Send, -{ - const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11]; - - let http_request = request - .try_into_http_request::>( - actual.string().as_str(), - SendAccessToken::None, - &VERSIONS, - ) - .map_err(|e| err!(BadServerResponse("Invalid destination: {e:?}")))?; - - Ok(http_request) +impl FederationPathBuilderInput for () { + fn create() -> Self {} +} + +impl FederationPathBuilderInput for Cow<'_, SupportedVersions> { + fn create() -> Self { + Cow::Borrowed(&SUPPORTED_VERSIONS) + } } diff --git a/src/service/federation/mod.rs b/src/service/federation/mod.rs index 155218754..8dfab52a7 100644 --- a/src/service/federation/mod.rs +++ b/src/service/federation/mod.rs @@ -1,4 +1,5 @@ mod execute; +pub(crate) use execute::FederationPathBuilderInput; use std::sync::Arc; diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 3930042d3..2e82cc513 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -59,7 +59,7 @@ async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result { let (ber_count, ber_bytes) = self.bad_event_ratelimiter.read().iter().fold( (0_usize, 0_usize), |(mut count, mut bytes), (event_id, _)| { - bytes = bytes.saturating_add(event_id.capacity()); + bytes = bytes.saturating_add(event_id.as_bytes().len()); bytes = bytes.saturating_add(size_of::()); count = count.saturating_add(1); (count, bytes) diff --git a/src/service/key_backups/mod.rs b/src/service/key_backups/mod.rs index 1bf048ef3..616a66b58 100644 --- a/src/service/key_backups/mod.rs +++ b/src/service/key_backups/mod.rs @@ -7,9 +7,7 @@ use database::{Deserialized, Ignore, Interfix, Json, Map}; use futures::StreamExt; use ruma::{ - OwnedRoomId, RoomId, UserId, - api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup}, - serde::Raw, + OwnedRoomId, OwnedUserId, RoomId, UserId, api::client::backup::{BackupAlgorithm, KeyBackupData, RoomKeyBackup}, serde::Raw }; use crate::{Dep, globals}; @@ -103,7 +101,7 @@ pub async fn update_backup<'a>( #[implement(Service)] pub async fn get_latest_backup_version(&self, user_id: &UserId) -> Result { - type Key<'a> = (&'a UserId, &'a str); + type Key<'a> = (OwnedUserId, &'a str); let last_possible_key = (user_id, u64::MAX); self.db @@ -122,7 +120,7 @@ pub async fn get_latest_backup( &self, user_id: &UserId, ) -> Result<(String, Raw)> { - type Key<'a> = (&'a UserId, &'a str); + type Key<'a> = (OwnedUserId, &'a str); type KeyVal<'a> = (Key<'a>, Raw); let last_possible_key = (user_id, u64::MAX); @@ -197,11 +195,11 @@ pub async fn get_all( user_id: &UserId, version: &str, ) -> BTreeMap { - type Key<'a> = (Ignore, Ignore, &'a RoomId, &'a str); + type Key<'a> = (Ignore, Ignore, OwnedRoomId, &'a str); type KeyVal<'a> = (Key<'a>, Raw); let mut rooms = BTreeMap::::new(); - let default = || RoomKeyBackup { sessions: BTreeMap::new() }; + let default = || RoomKeyBackup::new(BTreeMap::new()); let prefix = (user_id, version, Interfix); self.db diff --git a/src/service/media/data.rs b/src/service/media/data.rs index 0bc355cd2..fcf75c3a7 100644 --- a/src/service/media/data.rs +++ b/src/service/media/data.rs @@ -6,7 +6,9 @@ }; use database::{Database, Interfix, Map}; use futures::StreamExt; -use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition}; +use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition}; + +use crate::media::mxc::Mxc; use super::{preview::UrlPreviewData, thumbnail::Dim}; @@ -41,7 +43,7 @@ pub(super) fn create_file_metadata( content_type: Option<&str>, ) -> Result> { let dim: &[u32] = &[dim.width, dim.height]; - let key = (mxc, dim, content_disposition, content_type); + let key = (mxc, dim, content_disposition.map(ToString::to_string), content_type); let key = database::serialize_key(key)?; self.mediaid_file.insert(&key, []); if let Some(user) = user { @@ -146,7 +148,7 @@ pub(super) async fn get_all_user_mxcs(&self, user_id: &UserId) -> Vec { + /// ServerName part of the MXC URI + pub server_name: &'a ServerName, + + /// MediaId part of the MXC URI + pub media_id: &'a str, +} + +impl fmt::Display for Mxc<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "mxc://{}/{}", self.server_name, self.media_id) + } +} + +impl<'a> TryFrom<&'a MxcUri> for Mxc<'a> { + type Error = MxcUriError; + + fn try_from(s: &'a MxcUri) -> Result { + let (server_name, media_id) = s.parts()?; + + Ok(Self { server_name, media_id }) + } +} + +impl<'a> TryFrom<&'a str> for Mxc<'a> { + type Error = MxcUriError; + + fn try_from(s: &'a str) -> Result { + let s: &MxcUri = s.into(); + s.try_into() + } +} + +impl<'a> TryFrom<&'a OwnedMxcUri> for Mxc<'a> { + type Error = MxcUriError; + + fn try_from(s: &'a OwnedMxcUri) -> Result { + let s: &MxcUri = s.as_ref(); + s.try_into() + } +} + +impl Serialize for Mxc<'_> { + fn serialize(&self, s: S) -> Result { + s.serialize_str(self.to_string().as_str()) + } +} \ No newline at end of file diff --git a/src/service/media/preview.rs b/src/service/media/preview.rs index 853e6bc9d..06dd06532 100644 --- a/src/service/media/preview.rs +++ b/src/service/media/preview.rs @@ -130,7 +130,8 @@ pub async fn download_image( ) -> Result { use conduwuit::utils::random_string; use image::ImageReader; - use ruma::Mxc; + + use crate::media::mxc::Mxc; let mut preview_data = preview_data.unwrap_or_default(); diff --git a/src/service/media/remote.rs b/src/service/media/remote.rs index 29e354cef..242f85f23 100644 --- a/src/service/media/remote.rs +++ b/src/service/media/remote.rs @@ -6,18 +6,16 @@ }; use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE, HeaderValue}; use ruma::{ - Mxc, ServerName, UserId, - api::{ - OutgoingRequest, - client::{ + ServerName, UserId, api::{ + Metadata, OutgoingRequest, auth_scheme::NoAuthentication, client::{ error::ErrorKind::{NotFound, Unrecognized}, media, - }, - federation, - federation::authenticated_media::{Content, FileOrLocation}, - }, + }, federation::{self, authenticated_media::{Content, FileOrLocation}, authentication::ServerSignatures}, path_builder::PathBuilder + } }; +use crate::{federation::FederationPathBuilderInput, media::mxc::Mxc}; + use super::{Dim, FileMeta}; #[implement(super::Service)] @@ -87,14 +85,10 @@ async fn fetch_thumbnail_authenticated( ) -> Result { use federation::authenticated_media::get_content_thumbnail::v1::{Request, Response}; - let request = Request { - media_id: mxc.media_id.into(), - method: dim.method.clone().into(), - width: dim.width.into(), - height: dim.height.into(), - animated: true.into(), - timeout_ms, - }; + let mut request = Request::new(mxc.media_id.into(), dim.width.into(), dim.height.into()); + request.method = Some(dim.method.clone()); + request.animated = Some(true); + request.timeout_ms = timeout_ms; let Response { content, .. } = self.federation_request(mxc, server, request).await?; @@ -102,6 +96,7 @@ async fn fetch_thumbnail_authenticated( | FileOrLocation::File(content) => self.handle_thumbnail_file(mxc, user, dim, content).await, | FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await, + | _ => Err!("Unknown content in response"), } } @@ -115,16 +110,15 @@ async fn fetch_content_authenticated( ) -> Result { use federation::authenticated_media::get_content::v1::{Request, Response}; - let request = Request { - media_id: mxc.media_id.into(), - timeout_ms, - }; + let mut request = Request::new(mxc.media_id.into()); + request.timeout_ms = timeout_ms; let Response { content, .. } = self.federation_request(mxc, server, request).await?; match content { | FileOrLocation::File(content) => self.handle_content_file(mxc, user, content).await, | FileOrLocation::Location(location) => self.handle_location(mxc, user, &location).await, + | _ => Err!("Unknown content in response"), } } @@ -140,23 +134,18 @@ async fn fetch_thumbnail_unauthenticated( ) -> Result { use media::get_content_thumbnail::v3::{Request, Response}; - let request = Request { - allow_remote: true, - allow_redirect: true, - animated: true.into(), - method: dim.method.clone().into(), - width: dim.width.into(), - height: dim.height.into(), - server_name: mxc.server_name.into(), - media_id: mxc.media_id.into(), - timeout_ms, - }; + let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into(), dim.width.into(), dim.height.into()); + request.allow_redirect = true; + request.allow_remote = true; + request.animated = Some(true); + request.method = Some(dim.method.clone()); + request.timeout_ms = timeout_ms; let Response { file, content_type, content_disposition, .. - } = self.federation_request(mxc, server, request).await?; + } = self.federation_request_unauthenticated(mxc, server, request).await?; - let content = Content { file, content_type, content_disposition }; + let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap()); self.handle_thumbnail_file(mxc, user, dim, content).await } @@ -172,19 +161,16 @@ async fn fetch_content_unauthenticated( ) -> Result { use media::get_content::v3::{Request, Response}; - let request = Request { - allow_remote: true, - allow_redirect: true, - server_name: mxc.server_name.into(), - media_id: mxc.media_id.into(), - timeout_ms, - }; + let mut request = Request::new(mxc.media_id.into(), mxc.server_name.into()); + request.allow_remote = true; + request.allow_redirect = true; + request.timeout_ms = timeout_ms; let Response { file, content_type, content_disposition, .. - } = self.federation_request(mxc, server, request).await?; + } = self.federation_request_unauthenticated(mxc, server, request).await?; - let content = Content { file, content_type, content_disposition }; + let content = Content::new(file, content_type.unwrap(), content_disposition.unwrap()); self.handle_content_file(mxc, user, content).await } @@ -307,14 +293,14 @@ async fn location_request(&self, location: &str) -> Result { } #[implement(super::Service)] -async fn federation_request( +async fn federation_request<'i, Request>( &self, mxc: &Mxc<'_>, server: Option<&ServerName>, request: Request, ) -> Result where - Request: OutgoingRequest + Send + Debug, + Request: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, { self.services .sending @@ -322,6 +308,22 @@ async fn federation_request( .await } +#[implement(super::Service)] +async fn federation_request_unauthenticated<'i, Request>( + &self, + mxc: &Mxc<'_>, + server: Option<&ServerName>, + request: Request, +) -> Result +where + Request: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, +{ + self.services + .sending + .send_unauthenticated_request(server.unwrap_or(mxc.server_name), request) + .await +} + #[implement(super::Service)] #[allow(deprecated)] pub async fn fetch_remote_thumbnail_legacy( @@ -333,22 +335,19 @@ pub async fn fetch_remote_thumbnail_legacy( media_id: &body.media_id, }; + let mut request = media::get_content_thumbnail::v3::Request::new(body.media_id.clone(), body.server_name.clone(), body.width, body.height); + request.method = body.method.clone(); + request.allow_remote = body.allow_remote; + request.allow_redirect = body.allow_redirect; + request.animated = body.animated; + request.timeout_ms = body.timeout_ms; + self.check_legacy_freeze()?; self.check_fetch_authorized(&mxc)?; let response = self .services .sending - .send_federation_request(mxc.server_name, media::get_content_thumbnail::v3::Request { - allow_remote: body.allow_remote, - height: body.height, - width: body.width, - method: body.method.clone(), - server_name: body.server_name.clone(), - media_id: body.media_id.clone(), - timeout_ms: body.timeout_ms, - allow_redirect: body.allow_redirect, - animated: body.animated, - }) + .send_unauthenticated_request(mxc.server_name, request) .await?; let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; @@ -373,18 +372,17 @@ pub async fn fetch_remote_content_legacy( allow_redirect: bool, timeout_ms: Duration, ) -> Result { + let mut request = media::get_content::v3::Request::new(mxc.media_id.into(), mxc.server_name.into()); + request.allow_remote = true; + request.allow_redirect = allow_redirect; + request.timeout_ms = timeout_ms; + self.check_legacy_freeze()?; self.check_fetch_authorized(mxc)?; let response = self .services .sending - .send_federation_request(mxc.server_name, media::get_content::v3::Request { - allow_remote: true, - server_name: mxc.server_name.into(), - media_id: mxc.media_id.into(), - timeout_ms, - allow_redirect, - }) + .send_unauthenticated_request(mxc.server_name, request) .await?; let content_disposition = make_content_disposition( diff --git a/src/service/media/thumbnail.rs b/src/service/media/thumbnail.rs index fa29e4b8d..be6469b93 100644 --- a/src/service/media/thumbnail.rs +++ b/src/service/media/thumbnail.rs @@ -8,12 +8,14 @@ use std::{cmp, num::Saturating as Sat}; use conduwuit::{Result, checked, err, implement}; -use ruma::{Mxc, UInt, UserId, http_headers::ContentDisposition, media::Method}; +use ruma::{UInt, UserId, http_headers::ContentDisposition, media::Method}; use tokio::{ fs, io::{AsyncReadExt, AsyncWriteExt}, }; +use crate::media::mxc::Mxc; + use super::{FileMeta, data::Metadata}; /// Dimension specification for a thumbnail. diff --git a/src/service/migrations.rs b/src/service/migrations.rs index 69b116a25..ac8ef519a 100644 --- a/src/service/migrations.rs +++ b/src/service/migrations.rs @@ -243,7 +243,13 @@ async fn migrate(services: &Services) -> Result<()> { services .users .stream() - .filter(|user_id| services.users.is_active_local(user_id)) + .filter_map(async |user_id| { + if services.users.is_active_local(&user_id).await { + Some(user_id) + } else { + None + } + }) .ready_for_each(|user_id| { let matches = patterns.matches(user_id.localpart()); if matches.matched_any() { @@ -268,7 +274,6 @@ async fn migrate(services: &Services) -> Result<()> { .rooms .metadata .iter_ids() - .map(ToOwned::to_owned) .collect::>() .await { @@ -305,7 +310,6 @@ async fn db_lt_12(services: &Services) -> Result<()> { for username in &services .users .list_local_users() - .map(ToOwned::to_owned) .collect::>() .await { @@ -385,7 +389,6 @@ async fn db_lt_13(services: &Services) -> Result<()> { for username in &services .users .list_local_users() - .map(ToOwned::to_owned) .collect::>() .await { @@ -480,7 +483,6 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services) .rooms .metadata .iter_ids() - .map(ToOwned::to_owned) .collect::>() .await; @@ -491,7 +493,6 @@ async fn retroactively_fix_bad_data_from_roomuserid_joined(services: &Services) .rooms .state_cache .room_members(room_id) - .map(ToOwned::to_owned) .collect() .await; @@ -603,11 +604,8 @@ async fn fix_referencedevents_missing_sep(services: &Services) -> Result { } async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result { - use conduwuit::arrayvec::ArrayString; - use ruma::identifiers_validation::MAX_BYTES; - - type ArrayId = ArrayString; - type Key<'a> = (&'a RoomId, u64, &'a UserId); + type ArrayId = String; + type Key = (OwnedRoomId, u64, OwnedUserId); info!("Fixing undeleted entries in readreceiptid_readreceipt..."); @@ -621,8 +619,8 @@ async fn fix_readreceiptid_readreceipt_duplicates(services: &Services) -> Result readreceiptid_readreceipt .keys() .expect_ok() - .ready_for_each(|key: Key<'_>| { - let (room_id, _, user_id) = key; + .ready_for_each(|key: Key| { + let (ref room_id, _, ref user_id) = key; let last_room = cur_room.replace( room_id .as_str() @@ -715,8 +713,8 @@ async fn fix_corrupt_msc4133_fields(services: &Services) -> Result { const POPULATED_USERROOMID_LEFTSTATE_TABLE_MARKER: &str = "populate_userroomid_leftstate_table"; async fn populate_userroomid_leftstate_table(services: &Services) -> Result { - type KeyVal<'a> = (Key<'a>, Raw>); - type Key<'a> = (&'a UserId, &'a RoomId); + type KeyVal = (Key, Raw>); + type Key = (OwnedUserId, OwnedRoomId); let db = &services.db; let cork = db.cork_and_sync(); @@ -731,16 +729,16 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result { usize, HashMap<_, _>, ), - ((user_id, room_id), state): KeyVal<'_>| + ((user_id, room_id), state): KeyVal| -> Result<(usize, usize, HashMap<_, _>)> { if state.deserialize().is_err() { let latest_shortstatehash = - if let Some(shortstatehash) = shortstatehash_cache.get(room_id) { + if let Some(shortstatehash) = shortstatehash_cache.get(&room_id) { *shortstatehash } else if let Ok(shortstatehash) = - services.rooms.state.get_room_shortstatehash(room_id).await + services.rooms.state.get_room_shortstatehash(&room_id).await { - shortstatehash_cache.insert(room_id.to_owned(), shortstatehash); + shortstatehash_cache.insert(room_id.clone(), shortstatehash); shortstatehash } else { warn!(%room_id, %user_id, "room has no shortstatehash"); @@ -792,8 +790,8 @@ async fn populate_userroomid_leftstate_table(services: &Services) -> Result { async fn fix_local_invite_state(services: &Services) -> Result { // Clean up the effects of !1249 by caching stripped state for invites - type KeyVal<'a> = (Key<'a>, Raw>); - type Key<'a> = (&'a UserId, &'a RoomId); + type KeyVal = (Key, Raw>); + type Key = (OwnedUserId, OwnedRoomId); let db = &services.db; let cork = db.cork_and_sync(); @@ -802,9 +800,9 @@ async fn fix_local_invite_state(services: &Services) -> Result { // for each user invited to a room let fixed = userroomid_invitestate.stream() // if they're a local user on this homeserver - .try_filter(|((user_id, _), _): &KeyVal<'_>| ready(services.globals.user_is_local(user_id))) - .and_then(async |((user_id, room_id), stripped_state): KeyVal<'_>| Ok::<_, - conduwuit::Error>((user_id.to_owned(), room_id.to_owned(), stripped_state.deserialize + .try_filter(|((user_id, _), _): &KeyVal| ready(services.globals.user_is_local(user_id))) + .and_then(async |((user_id, room_id), stripped_state): KeyVal| Ok::<_, + conduwuit::Error>((user_id.clone(), room_id.clone(), stripped_state.deserialize ().unwrap_or_else(|e| { trace!("Failed to deserialize: {:?}", stripped_state.json()); warn!( @@ -812,7 +810,7 @@ async fn fix_local_invite_state(services: &Services) -> Result { %room_id, "Failed to deserialize stripped state for invite, removing from db: {e}" ); - userroomid_invitestate.del((user_id, room_id)); + userroomid_invitestate.del((&user_id, &room_id)); vec![] })))) .try_fold(0_usize, async |mut fixed, (user_id, room_id, stripped_state)| { diff --git a/src/service/mod.rs b/src/service/mod.rs index 922610c53..b8c5b9f59 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -47,3 +47,11 @@ conduwuit::mod_ctor! {} conduwuit::mod_dtor! {} + +use std::sync::LazyLock; +use conduwuit::matrix::versions::{unstable_features, versions}; +use ruma::api::SupportedVersions; + +pub static SUPPORTED_VERSIONS: LazyLock = LazyLock::new(|| { + SupportedVersions::from_parts(&versions(), &unstable_features()) +}); \ No newline at end of file diff --git a/src/service/presence/mod.rs b/src/service/presence/mod.rs index 8eca7ab98..7ae74d6eb 100644 --- a/src/service/presence/mod.rs +++ b/src/service/presence/mod.rs @@ -183,7 +183,6 @@ pub async fn unset_all_presence(&self) { .services .users .list_local_users() - .map(ToOwned::to_owned) .collect::>() .await { @@ -194,9 +193,9 @@ pub async fn unset_all_presence(&self) { | _ => continue, }; - if !matches!( + if matches!( presence.presence, - PresenceState::Unavailable | PresenceState::Online | PresenceState::Busy + PresenceState::Offline ) { trace!(%user_id, ?presence, "Skipping user"); continue; diff --git a/src/service/presence/presence.rs b/src/service/presence/presence.rs index 3357bd616..846fc1459 100644 --- a/src/service/presence/presence.rs +++ b/src/service/presence/presence.rs @@ -47,17 +47,16 @@ pub(super) async fn to_presence_event( ) -> PresenceEvent { let now = utils::millis_since_unix_epoch(); let last_active_ago = Some(UInt::new_saturating(now.saturating_sub(self.last_active_ts))); + let mut content = PresenceEventContent::new(self.state.clone()); + content.status_msg = self.status_msg.clone(); + content.currently_active = Some(self.currently_active); + content.last_active_ago = last_active_ago; + content.displayname = users.displayname(user_id).await.ok(); + content.avatar_url = users.avatar_url(user_id).await.ok(); PresenceEvent { sender: user_id.to_owned(), - content: PresenceEventContent { - presence: self.state.clone(), - status_msg: self.status_msg.clone(), - currently_active: Some(self.currently_active), - last_active_ago, - displayname: users.displayname(user_id).await.ok(), - avatar_url: users.avatar_url(user_id).await.ok(), - }, + content, } } } diff --git a/src/service/pusher/mod.rs b/src/service/pusher/mod.rs index cd9c87993..e300edc2c 100644 --- a/src/service/pusher/mod.rs +++ b/src/service/pusher/mod.rs @@ -11,24 +11,17 @@ use futures::{Stream, StreamExt}; use ipaddress::IPAddress; use ruma::{ - DeviceId, OwnedDeviceId, RoomId, UInt, UserId, - api::{ - IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, - client::push::{Pusher, PusherKind, set_pusher}, - push_gateway::send_event_notification::{ + DeviceId, OwnedDeviceId, RoomId, UInt, UserId, api::{ + IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{NoAuthentication, SendAccessToken}, client::push::{Pusher, PusherKind, set_pusher}, path_builder::SinglePath, push_gateway::send_event_notification::{ self, v1::{Device, Notification, NotificationCounts, NotificationPriority}, - }, - }, - events::{ + } + }, events::{ AnySyncTimelineEvent, StateEventType, TimelineEventType, - room::power_levels::RoomPowerLevelsEventContent, - }, - push::{ + room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}}, + }, push::{ Action, PushConditionPowerLevelsCtx, PushConditionRoomCtx, PushFormat, Ruleset, Tweak, - }, - serde::Raw, - uint, + }, room_version_rules::{AuthorizationRules, RoomPowerLevelsRules, RoomVersionRules}, serde::Raw, uint }; use crate::{Dep, client, config, globals, rooms, sending, users}; @@ -42,6 +35,7 @@ struct Services { globals: Dep, config: Dep, client: Dep, + state: Dep, state_accessor: Dep, state_cache: Dep, users: Dep, @@ -64,6 +58,7 @@ fn build(args: crate::Args<'_>) -> Result> { globals: args.depend::("globals"), client: args.depend::("client"), config: args.depend::("config"), + state: args.depend::("rooms::state"), state_accessor: args .depend::("rooms::state_accessor"), state_cache: args.depend::("rooms::state_cache"), @@ -137,6 +132,7 @@ pub async fn set_pusher( | set_pusher::v3::PusherAction::Delete(ids) => { self.delete_pusher(sender, ids.pushkey.as_str()).await; }, + | _ => return Err!(Request(InvalidParam("Unknown pusher action"))), } Ok(()) @@ -193,7 +189,7 @@ pub fn get_pushkeys<'a>( #[tracing::instrument(skip(self, dest, request))] pub async fn send_request(&self, dest: &str, request: T) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest + Debug + Send, { const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0]; @@ -201,7 +197,7 @@ pub async fn send_request(&self, dest: &str, request: T) -> Result(&dest, SendAccessToken::None, &VERSIONS) + .try_into_http_request::(&dest, SendAccessToken::None, ()) .map_err(|e| { err!(BadServerResponse(warn!( "Failed to find destination {dest} for push gateway: {e}" @@ -298,22 +294,20 @@ pub async fn send_push_notice( { let mut notify = None; let mut tweaks = Vec::new(); - if event.room_id().is_none() { - // This only affects v12+ create events + let Some(room_id) = event.room_id() else { + // Only v12+ create events have no room ID return Ok(()); - } + }; - let power_levels: RoomPowerLevelsEventContent = self + let power_levels = self .services .state_accessor - .room_state_get(event.room_id().unwrap(), &StateEventType::RoomPowerLevels, "") - .await - .and_then(|event| event.get_content()) - .unwrap_or_default(); + .get_room_power_levels(room_id) + .await; let serialized = event.to_format(); for action in self - .get_actions(user, &ruleset, &power_levels, &serialized, event.room_id().unwrap()) + .get_actions(user, &ruleset, power_levels.clone(), &serialized, event.room_id().unwrap()) .await { let n = match action { @@ -347,15 +341,11 @@ pub async fn get_actions<'a>( &self, user: &UserId, ruleset: &'a Ruleset, - power_levels: &RoomPowerLevelsEventContent, + power_levels: RoomPowerLevels, pdu: &Raw, room_id: &RoomId, ) -> &'a [Action] { - let power_levels = PushConditionPowerLevelsCtx { - users: power_levels.users.clone(), - users_default: power_levels.users_default, - notifications: power_levels.notifications.clone(), - }; + let power_levels = PushConditionPowerLevelsCtx::from(power_levels); let room_joined_count = self .services @@ -373,15 +363,9 @@ pub async fn get_actions<'a>( .await .unwrap_or_else(|_| user.localpart().to_owned()); - let ctx = PushConditionRoomCtx { - room_id: room_id.to_owned(), - member_count: room_joined_count, - user_id: user.to_owned(), - user_display_name, - power_levels: Some(power_levels), - }; + let ctx = PushConditionRoomCtx::new(room_id.to_owned(), room_joined_count, user.to_owned(), user_display_name).with_power_levels(power_levels); - ruleset.get_actions(pdu, &ctx) + ruleset.get_actions(pdu, &ctx).await } #[tracing::instrument(skip(self, unread, pusher, tweaks, event))] diff --git a/src/service/resolver/cache.rs b/src/service/resolver/cache.rs index cfea71875..6bad6402d 100644 --- a/src/service/resolver/cache.rs +++ b/src/service/resolver/cache.rs @@ -8,7 +8,7 @@ }; use database::{Cbor, Deserialized, Map}; use futures::{Stream, StreamExt, future::join}; -use ruma::ServerName; +use ruma::{OwnedServerName, ServerName}; use serde::{Deserialize, Serialize}; use super::fed::FedDest; @@ -107,19 +107,19 @@ pub async fn get_override(&self, name: &str) -> Result { } #[implement(Cache)] -pub fn destinations(&self) -> impl Stream + Send + '_ { +pub fn destinations(&self) -> impl Stream + Send + '_ { self.destinations .stream() .ignore_err() - .map(|item: (&ServerName, Cbor<_>)| (item.0, item.1.0)) + .map(|item: (OwnedServerName, Cbor<_>)| (item.0, item.1.0)) } #[implement(Cache)] -pub fn overrides(&self) -> impl Stream + Send + '_ { +pub fn overrides(&self) -> impl Stream + Send + '_ { self.overrides .stream() .ignore_err() - .map(|item: (&ServerName, Cbor<_>)| (item.0, item.1.0)) + .map(|item: (OwnedServerName, Cbor<_>)| (item.0, item.1.0)) } impl CachedDest { diff --git a/src/service/resolver/well_known.rs b/src/service/resolver/well_known.rs index f72e4e8b4..7da52add6 100644 --- a/src/service/resolver/well_known.rs +++ b/src/service/resolver/well_known.rs @@ -1,6 +1,9 @@ +use std::str::FromStr; + use conduwuit::{ Result, debug, debug_error, debug_info, implement, trace, utils::response::LimitReadExt, }; +use ruma::{OwnedServerName, ServerName}; #[implement(super::Service)] #[tracing::instrument(name = "well-known", level = "debug", skip(self, dest))] @@ -40,7 +43,7 @@ pub(super) async fn request_well_known(&self, dest: &str) -> Result Result( &'a self, room_id: &'a RoomId, - ) -> impl Stream + Send + 'a { + ) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .aliasid_alias .stream_prefix(&prefix) .ignore_err() - .map(|(_, alias): (Ignore, &RoomAliasId)| alias) + .map(|(_, alias): (Ignore, OwnedRoomAliasId)| alias) } #[tracing::instrument(skip(self), level = "debug")] - pub fn all_local_aliases(&self) -> impl Stream + Send + '_ { + pub fn all_local_aliases(&self) -> impl Stream + Send + '_ { self.db .alias_roomid .stream() .ignore_err() - .map(|(alias_localpart, room_id): (&str, &RoomId)| (room_id, alias_localpart)) + .map(|(alias_localpart, room_id): (&str, OwnedRoomId)| (room_id, alias_localpart)) } async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result { @@ -236,34 +234,14 @@ async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> } // Checking whether the user is able to change canonical aliases of the room - if let Ok(power_levels) = self + let can_change_canonical_alias = self .services .state_accessor - .room_state_get_content::( - &room_id, - &StateEventType::RoomPowerLevels, - "", - ) - .map_ok(RoomPowerLevels::from) + .get_room_power_levels(&room_id) .await - { - return Ok( - power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias) - ); - } + .user_can_send_state(user_id, StateEventType::RoomCanonicalAlias); - // If there is no power levels event, only the room creator can change - // canonical aliases - if let Ok(event) = self - .services - .state_accessor - .room_state_get(&room_id, &StateEventType::RoomCreate, "") - .await - { - return Ok(event.sender() == user_id); - } - - Err!(Database("Room has no m.room.create event")) + Ok(can_change_canonical_alias) } async fn who_created_alias(&self, alias: &RoomAliasId) -> Result { @@ -299,7 +277,7 @@ async fn resolve_appservice_alias( .sending .send_appservice_request( appservice.registration.clone(), - query_room_alias::v1::Request { room_alias: room_alias.to_owned() }, + query_room_alias::v1::Request::new(room_alias.to_owned()), ) .await, Ok(Some(_opt_result)) diff --git a/src/service/rooms/alias/remote.rs b/src/service/rooms/alias/remote.rs index 3e8480d9b..cc73990af 100644 --- a/src/service/rooms/alias/remote.rs +++ b/src/service/rooms/alias/remote.rs @@ -16,7 +16,7 @@ pub(super) async fn remote_resolve( error!("Unable to resolve remote room alias {}: {e}", room_alias); Err(e) }, - | Ok(Response { room_id, servers }) => { + | Ok(Response { room_id, servers, .. }) => { debug!("Remote resolved {room_alias:?} to {room_id:?} with servers {servers:?}"); Ok((room_id, servers)) }, @@ -31,7 +31,7 @@ async fn remote_request( ) -> Result { use federation::query::get_room_information::v1::Request; - let request = Request { room_alias: room_alias.to_owned() }; + let request = Request::new(room_alias.to_owned()); self.services .sending diff --git a/src/service/rooms/directory/mod.rs b/src/service/rooms/directory/mod.rs index 4ea106418..775764fa1 100644 --- a/src/service/rooms/directory/mod.rs +++ b/src/service/rooms/directory/mod.rs @@ -3,7 +3,7 @@ use conduwuit::{Result, implement, utils::stream::TryIgnore}; use database::Map; use futures::Stream; -use ruma::{RoomId, api::client::room::Visibility}; +use ruma::{OwnedRoomId, RoomId, api::client::room::Visibility}; pub struct Service { db: Data, @@ -32,7 +32,7 @@ fn name(&self) -> &str { crate::service::make_name(std::module_path!()) } pub fn set_not_public(&self, room_id: &RoomId) { self.db.publicroomids.remove(room_id); } #[implement(Service)] -pub fn public_rooms(&self) -> impl Stream + Send { +pub fn public_rooms(&self) -> impl Stream + Send { self.db.publicroomids.keys().ignore_err() } diff --git a/src/service/rooms/event_handler/fetch_and_handle_outliers.rs b/src/service/rooms/event_handler/fetch_and_handle_outliers.rs index fbe732330..39468e8df 100644 --- a/src/service/rooms/event_handler/fetch_and_handle_outliers.rs +++ b/src/service/rooms/event_handler/fetch_and_handle_outliers.rs @@ -12,7 +12,7 @@ api::federation::event::get_event, }; -use super::get_room_version_id; +use super::get_room_version; /// Find the event and auth it. Once the event is validated (steps 1 - 8) /// it is appended to the outliers Tree. @@ -109,15 +109,12 @@ pub(super) async fn fetch_and_handle_outliers<'a, Pdu, Events>( match self .services .sending - .send_federation_request(origin, get_event::v1::Request { - event_id: (*next_id).to_owned(), - include_unredacted_content: None, - }) + .send_federation_request(origin, get_event::v1::Request::new((*next_id).to_owned())) .await { | Ok(res) => { debug!("Got {next_id} over federation from {origin}"); - let Ok(room_version_id) = get_room_version_id(create_event) else { + let Ok(room_version_id) = get_room_version(create_event) else { back_off((*next_id).to_owned()); continue; }; diff --git a/src/service/rooms/event_handler/fetch_state.rs b/src/service/rooms/event_handler/fetch_state.rs index 511806bbf..1f48908c3 100644 --- a/src/service/rooms/event_handler/fetch_state.rs +++ b/src/service/rooms/event_handler/fetch_state.rs @@ -31,10 +31,7 @@ pub(super) async fn fetch_state( let res = self .services .sending - .send_federation_request(origin, get_room_state_ids::v1::Request { - room_id: room_id.to_owned(), - event_id: event_id.to_owned(), - }) + .send_federation_request(origin, get_room_state_ids::v1::Request::new(event_id.to_owned(), room_id.to_owned())) .await .inspect_err(|e| debug_warn!("Fetching state for event failed: {e}"))?; diff --git a/src/service/rooms/event_handler/handle_incoming_pdu.rs b/src/service/rooms/event_handler/handle_incoming_pdu.rs index da142f356..fcbcec46d 100644 --- a/src/service/rooms/event_handler/handle_incoming_pdu.rs +++ b/src/service/rooms/event_handler/handle_incoming_pdu.rs @@ -55,7 +55,7 @@ async fn should_rescind_invite( // Does the target user have a pending invite? let Ok(pending_invite_state) = services .state_cache - .invite_state(target_user_id, room_id) + .invite_state(&target_user_id, room_id) .await else { return Ok(None); // No pending invite, so nothing to rescind @@ -146,9 +146,11 @@ pub async fn handle_incoming_pdu<'a>( let origin_acl_check = self.acl_check(origin, room_id); // 1.3.2 Check room ACL on sender's server name - let sender: &UserId = value + let sender: OwnedUserId = value .get("sender") - .try_into() + .and_then(|v| v.as_str()) + .ok_or_else(|| err!("No sender in object")) + .and_then(|v| Ok(UserId::parse(v)?)) .map_err(|e| err!(Request(InvalidParam("PDU does not have a valid sender key: {e}"))))?; let sender_acl_check: OptionFuture<_> = sender @@ -180,7 +182,7 @@ pub async fn handle_incoming_pdu<'a>( // copied from https://github.com/element-hq/synapse/blob/7e4588a/synapse/handlers/federation_event.py#L255-L300 if value.get("type").and_then(|t| t.as_str()) == Some("m.room.member") { if let Some(pdu) = - should_rescind_invite(&self.services, &mut value.clone(), sender, room_id).await? + should_rescind_invite(&self.services, &mut value.clone(), &sender, room_id).await? { debug_info!( "Invite to {room_id} appears to have been rescinded by {sender}, marking as \ @@ -188,7 +190,7 @@ pub async fn handle_incoming_pdu<'a>( ); self.services .state_cache - .mark_as_left(sender, room_id, Some(pdu)) + .mark_as_left(&sender, room_id, Some(pdu)) .await; return Ok(None); } diff --git a/src/service/rooms/event_handler/handle_outlier_pdu.rs b/src/service/rooms/event_handler/handle_outlier_pdu.rs index b39946eca..9a0f8c138 100644 --- a/src/service/rooms/event_handler/handle_outlier_pdu.rs +++ b/src/service/rooms/event_handler/handle_outlier_pdu.rs @@ -10,7 +10,7 @@ events::StateEventType, }; -use super::{check_room_id, get_room_version_id, to_room_version}; +use super::{check_room_id, get_room_version}; use crate::rooms::timeline::pdu_fits; #[implement(super::Service)] @@ -41,18 +41,19 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>( // 2. Check signatures, otherwise drop // 3. check content hash, redact if doesn't match - let room_version_id = get_room_version_id(create_event)?; + let room_version = get_room_version(create_event)?; + let room_rules = room_version.rules().expect("room version should have defined rules"); let mut incoming_pdu = match self .services .server_keys - .verify_event(&value, Some(&room_version_id)) + .verify_event(&value, Some(&room_version)) .await { | Ok(ruma::signatures::Verified::All) => value, | Ok(ruma::signatures::Verified::Signatures) => { // Redact debug_info!("Calculated hash does not match (redaction): {event_id}"); - let Ok(obj) = ruma::canonical_json::redact(value, &room_version_id, None) else { + let Ok(obj) = ruma::canonical_json::redact(value, &room_rules.redaction, None) else { return Err!(Request(InvalidParam("Redaction failed"))); }; @@ -184,7 +185,7 @@ pub(super) async fn handle_outlier_pdu<'a, Pdu>( }; let auth_check = state_res::event_auth::auth_check( - &to_room_version(&room_version_id), + &room_rules, &pdu_event, None, // TODO: third party invite state_fetch, diff --git a/src/service/rooms/event_handler/mod.rs b/src/service/rooms/event_handler/mod.rs index 4cf39527d..ed4c288f1 100644 --- a/src/service/rooms/event_handler/mod.rs +++ b/src/service/rooms/event_handler/mod.rs @@ -14,7 +14,7 @@ use std::{collections::HashMap, fmt::Write, sync::Arc, time::Instant}; use async_trait::async_trait; -use conduwuit::{Err, Event, PduEvent, Result, RoomVersion, Server, SyncRwLock, utils::MutexMap}; +use conduwuit::{Err, Event, PduEvent, Result, Server, SyncRwLock, utils::MutexMap}; use ruma::{ OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, events::room::create::RoomCreateEventContent, @@ -114,14 +114,9 @@ fn check_room_id(room_id: &RoomId, pdu: &Pdu) -> Result { Ok(()) } -fn get_room_version_id(create_event: &Pdu) -> Result { +fn get_room_version(create_event: &Pdu) -> Result { let content: RoomCreateEventContent = create_event.get_content()?; let room_version = content.room_version; Ok(room_version) -} - -#[inline] -fn to_room_version(room_version_id: &RoomVersionId) -> RoomVersion { - RoomVersion::new(room_version_id).expect("room version is supported") -} +} \ No newline at end of file diff --git a/src/service/rooms/event_handler/parse_incoming_pdu.rs b/src/service/rooms/event_handler/parse_incoming_pdu.rs index 23a667c2b..b604e9865 100644 --- a/src/service/rooms/event_handler/parse_incoming_pdu.rs +++ b/src/service/rooms/event_handler/parse_incoming_pdu.rs @@ -1,8 +1,8 @@ use conduwuit::{ - Result, RoomVersion, err, implement, matrix::event::gen_event_id_canonical_json, + Result, err, implement, matrix::event::gen_event_id_canonical_json, result::FlatOk, }; -use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomVersionId}; +use ruma::{CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, room_version_rules::RoomIdFormatVersion}; use serde_json::value::RawValue as RawJsonValue; type Parsed = (OwnedRoomId, OwnedEventId, CanonicalJsonObject); @@ -21,7 +21,7 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result { value .get("room_id") .and_then(CanonicalJsonValue::as_str) - .map(OwnedRoomId::parse) + .map(RoomId::parse) .flat_ok_or(err!(Request(InvalidParam("Invalid room_id in pdu"))))? } else { // v12 rooms might have no room_id in the create event. We'll need to check the @@ -35,18 +35,17 @@ pub async fn parse_incoming_pdu(&self, pdu: &RawJsonValue) -> Result { .and_then(CanonicalJsonValue::as_str) .unwrap_or("1"); let vi = RoomVersionId::try_from(room_version).unwrap_or(RoomVersionId::V1); - let vf = RoomVersion::new(&vi).expect("supported room version"); - if vf.room_ids_as_hashes { + if vi.rules().expect("room version should have defined rules").room_id_format == RoomIdFormatVersion::V2 { let (event_id, _) = gen_event_id_canonical_json(pdu, &vi).map_err(|e| { err!(Request(InvalidParam("Could not convert event to canonical json: {e}"))) })?; - OwnedRoomId::parse(event_id.as_str().replace('$', "!")).expect("valid room ID") + RoomId::parse(event_id.as_str().replace('$', "!")).expect("valid room ID") } else { // V11 or below room, room_id must be present value .get("room_id") .and_then(CanonicalJsonValue::as_str) - .map(OwnedRoomId::parse) + .map(RoomId::parse) .flat_ok_or(err!(Request(InvalidParam("Invalid or missing room_id in pdu"))))? } }; diff --git a/src/service/rooms/event_handler/policy_server.rs b/src/service/rooms/event_handler/policy_server.rs index ca5dadeaf..7fe8eedb0 100644 --- a/src/service/rooms/event_handler/policy_server.rs +++ b/src/service/rooms/event_handler/policy_server.rs @@ -3,22 +3,24 @@ //! This module implements a check against a room-specific policy server, as //! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284). -use std::{collections::BTreeMap, time::Duration}; +use std::{collections::BTreeMap, sync::LazyLock, time::Duration}; use conduwuit::{ Err, Event, PduEvent, Result, debug, debug_error, debug_info, debug_warn, implement, trace, warn, }; use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, KeyId, RoomId, ServerName, SigningKeyId, - api::federation::room::{ - policy_check::unstable::Request as PolicyCheckRequest, - policy_sign::unstable::Request as PolicySignRequest, - }, - events::{StateEventType, room::policy::RoomPolicyEventContent}, + CanonicalJsonObject, CanonicalJsonValue, KeyId, OwnedKeyId, RoomId, ServerName, SigningKeyId, events::StateEventType +}; +use ruminuwuity::policy::{ + policy_check::unstable::Request as PolicyCheckRequest, + policy_sign::unstable::Request as PolicySignRequest, + event::RoomPolicyEventContent }; use serde_json::value::RawValue; +static POLICY_EVENT_TYPE_UNSTABLE: LazyLock = LazyLock::new(|| StateEventType::from("org.matrix.msc4284.policy")); + /// Asks a remote policy server if the event is allowed. /// /// If the event is the `org.matrix.msc4284.policy` configuration state event, @@ -44,7 +46,7 @@ pub async fn ask_policy_server( return Ok(true); // don't ever contact policy servers } - if *pdu.event_type() == StateEventType::RoomPolicy.into() { + if *pdu.event_type() == POLICY_EVENT_TYPE_UNSTABLE.clone().into() { debug!( room_id = %room_id, event_type = ?pdu.event_type(), @@ -56,7 +58,7 @@ pub async fn ask_policy_server( let Ok(policyserver) = self .services .state_accessor - .room_state_get_content(room_id, &StateEventType::RoomPolicy, "") + .room_state_get_content(room_id, &POLICY_EVENT_TYPE_UNSTABLE, "") .await .inspect_err(|e| { if !e.is_not_found() { @@ -86,11 +88,7 @@ pub async fn ask_policy_server( return Ok(true); }, }; - if via.is_empty() { - trace!("Policy server is empty for room {room_id}, skipping spam check"); - return Ok(true); - } - if !self.services.state_cache.server_in_room(via, room_id).await { + if !self.services.state_cache.server_in_room(&via, room_id).await { debug!( via = %via, "Policy server is not in the room, skipping spam check" @@ -110,14 +108,14 @@ pub async fn ask_policy_server( "Getting policy server signature on event" ); return self - .fetch_policy_server_signature(pdu, pdu_json, via, outgoing, room_id) + .fetch_policy_server_signature(pdu, pdu_json, &via, outgoing, room_id) .await; } // for incoming events, is it signed by with the key // "ed25519:policy_server"? if let Some(CanonicalJsonValue::Object(sigs)) = pdu_json.get("signatures") { if let Some(CanonicalJsonValue::Object(server_sigs)) = sigs.get(via.as_str()) { - let wanted_key_id: &KeyId = + let wanted_key_id: OwnedKeyId = SigningKeyId::parse("ed25519:policy_server")?; if let Some(CanonicalJsonValue::String(_sig_value)) = server_sigs.get(wanted_key_id.as_str()) @@ -134,14 +132,15 @@ pub async fn ask_policy_server( via = %via, "Checking event for spam with policy server via legacy check" ); + + let mut request = PolicyCheckRequest::new(pdu.event_id().to_owned()); + request.pdu = Some(outgoing); + let response = tokio::time::timeout( Duration::from_secs(self.services.server.config.policy_server_request_timeout), self.services .sending - .send_federation_request(via, PolicyCheckRequest { - event_id: pdu.event_id().to_owned(), - pdu: Some(outgoing), - }), + .send_federation_request(&via, request), ) .await; let response = match response { @@ -202,7 +201,7 @@ pub async fn fetch_policy_server_signature( Duration::from_secs(self.services.server.config.policy_server_request_timeout), self.services .sending - .send_federation_request(via, PolicySignRequest { pdu: outgoing }), + .send_federation_request(via, PolicySignRequest::new(outgoing)), ) .await; @@ -250,7 +249,7 @@ pub async fn fetch_policy_server_signature( } let keypairs = sigs.get(via).unwrap(); let wanted_key_id = KeyId::parse("ed25519:policy_server")?; - if !keypairs.contains_key(wanted_key_id) { + if !keypairs.contains_key(&wanted_key_id) { debug_warn!( "Policy server returned signature, but did not use the key ID \ 'ed25519:policy_server'." @@ -262,7 +261,7 @@ pub async fn fetch_policy_server_signature( .or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default())); if let CanonicalJsonValue::Object(signatures_map) = signatures_entry { - let sig_value = keypairs.get(wanted_key_id).unwrap().to_owned(); + let sig_value = keypairs.get(&wanted_key_id).unwrap().to_owned(); match signatures_map.get_mut(via.as_str()) { | Some(CanonicalJsonValue::Object(inner_map)) => { diff --git a/src/service/rooms/event_handler/resolve_state.rs b/src/service/rooms/event_handler/resolve_state.rs index cd747e044..ea97eb44e 100644 --- a/src/service/rooms/event_handler/resolve_state.rs +++ b/src/service/rooms/event_handler/resolve_state.rs @@ -11,7 +11,7 @@ utils::stream::{IterStream, ReadyExt, TryWidebandExt, WidebandExt}, }; use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join}; -use ruma::{OwnedEventId, RoomId, RoomVersionId}; +use ruma::{OwnedEventId, RoomId, RoomVersionId, room_version_rules::RoomVersionRules}; use crate::rooms::state_compressor::CompressedState; @@ -20,7 +20,7 @@ pub async fn resolve_state( &self, room_id: &RoomId, - room_version_id: &RoomVersionId, + room_version_rules: &RoomVersionRules, incoming_state: HashMap, ) -> Result> { trace!("Loading current room state ids"); @@ -71,7 +71,7 @@ pub async fn resolve_state( trace!("Resolving state"); let state = self - .state_resolution(room_version_id, fork_states.iter(), &auth_chain_sets) + .state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets) .boxed() .await?; @@ -103,7 +103,7 @@ pub async fn resolve_state( #[tracing::instrument(name = "ruma", level = "debug", skip_all)] pub async fn state_resolution<'a, StateSets>( &'a self, - room_version: &'a RoomVersionId, + room_version_rules: &'a RoomVersionRules, state_sets: StateSets, auth_chain_sets: &'a [HashSet], ) -> Result> @@ -112,7 +112,7 @@ pub async fn state_resolution<'a, StateSets>( { let event_fetch = |event_id| self.event_fetch(event_id); let event_exists = |event_id| self.event_exists(event_id); - state_res::resolve(room_version, state_sets, auth_chain_sets, &event_fetch, &event_exists) + state_res::resolve(room_version_rules, state_sets, auth_chain_sets, &event_fetch, &event_exists) .map_err(|e| err!(error!("State resolution failed: {e:?}"))) .await } diff --git a/src/service/rooms/event_handler/state_at_incoming.rs b/src/service/rooms/event_handler/state_at_incoming.rs index d3bb8f796..ef3b9fe97 100644 --- a/src/service/rooms/event_handler/state_at_incoming.rs +++ b/src/service/rooms/event_handler/state_at_incoming.rs @@ -11,7 +11,7 @@ utils::stream::{BroadbandExt, IterStream, ReadyExt, TryBroadbandExt, TryWidebandExt}, }; use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt, future::try_join}; -use ruma::{OwnedEventId, RoomId, RoomVersionId}; +use ruma::{OwnedEventId, RoomId, RoomVersionId, room_version_rules::RoomVersionRules}; use crate::rooms::short::ShortStateHash; @@ -77,7 +77,7 @@ pub(super) async fn state_at_incoming_resolved( &self, incoming_pdu: &Pdu, room_id: &RoomId, - room_version_id: &RoomVersionId, + room_version_rules: &RoomVersionRules, ) -> Result>> where Pdu: Event + Send + Sync, @@ -118,7 +118,7 @@ pub(super) async fn state_at_incoming_resolved( .await?; let Ok(new_state) = self - .state_resolution(room_version_id, fork_states.iter(), &auth_chain_sets) + .state_resolution(room_version_rules, fork_states.iter(), &auth_chain_sets) .boxed() .await else { diff --git a/src/service/rooms/event_handler/upgrade_outlier_pdu.rs b/src/service/rooms/event_handler/upgrade_outlier_pdu.rs index a60c2b668..78f877865 100644 --- a/src/service/rooms/event_handler/upgrade_outlier_pdu.rs +++ b/src/service/rooms/event_handler/upgrade_outlier_pdu.rs @@ -10,7 +10,7 @@ use futures::{FutureExt, StreamExt, future::ready}; use ruma::{CanonicalJsonValue, RoomId, ServerName, events::StateEventType}; -use super::{get_room_version_id, to_room_version}; +use super::get_room_version; use crate::rooms::{ state_compressor::{CompressedState, HashSetCompressStateEvent}, timeline::RawPduId, @@ -52,7 +52,8 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( "Upgrading PDU from outlier to timeline" ); let timer = Instant::now(); - let room_version_id = get_room_version_id(create_event)?; + let room_version_id = get_room_version(create_event)?; + let room_version_rules = room_version_id.rules().expect("room version should have defined rules"); // 10. Fetch missing state and auth chain events by calling /state_ids at // backwards extremities doing all the checks in this list starting at 1. @@ -65,7 +66,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( let mut state_at_incoming_event = if incoming_pdu.prev_events().count() == 1 { self.state_at_incoming_degree_one(&incoming_pdu).await? } else { - self.state_at_incoming_resolved(&incoming_pdu, room_id, &room_version_id) + self.state_at_incoming_resolved(&incoming_pdu, room_id, &room_version_rules) .await? }; @@ -78,8 +79,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( let state_at_incoming_event = state_at_incoming_event.expect("we always set this to some above"); - let room_version = to_room_version(&room_version_id); - debug!( event_id = %incoming_pdu.event_id, "Performing auth check to upgrade" @@ -98,7 +97,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( "Running initial auth check" ); let auth_check = state_res::event_auth::auth_check( - &room_version, + &room_version_rules, &incoming_pdu, None, // TODO: third party invite |ty, sk| state_fetch(ty.clone(), sk.into()), @@ -124,7 +123,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( incoming_pdu.sender(), incoming_pdu.state_key(), incoming_pdu.content(), - &room_version, + &room_version_rules, ) .await?; @@ -138,7 +137,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( "Running auth check with claimed state auth" ); let auth_check = state_res::event_auth::auth_check( - &room_version, + &room_version_rules, &incoming_pdu, None, // third-party invite state_fetch, @@ -179,7 +178,6 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( .services .state .get_forward_extremities(room_id) - .map(ToOwned::to_owned) .ready_filter(|event_id| { // Remove any that are referenced by this incoming event's prev_events !incoming_pdu.prev_events().any(is_equal_to!(event_id)) @@ -232,7 +230,7 @@ pub(super) async fn upgrade_outlier_to_timeline_pdu( } let new_room_state = self - .resolve_state(room_id, &room_version_id, state_after) + .resolve_state(room_id, &room_version_rules, state_after) .await?; // Set the new room state to the resolved state diff --git a/src/service/rooms/lazy_loading/mod.rs b/src/service/rooms/lazy_loading/mod.rs index c17d5c4f5..71fe82960 100644 --- a/src/service/rooms/lazy_loading/mod.rs +++ b/src/service/rooms/lazy_loading/mod.rs @@ -89,13 +89,13 @@ pub async fn retain_lazy_members(&self, senders: MemberSet, ctx: &Context<'_>) - let mut senders = MemberSet::with_capacity(senders.len()); while let Some((status, sender)) = witness.next().await { if include_redundant || status == Status::Unseen { - senders.insert(sender.into()); + senders.insert(sender.clone()); continue; } if let Status::Seen(seen) = status { if seen == 0 || ctx.token == Some(seen) { - senders.insert(sender.into()); + senders.insert(sender.clone()); continue; } } diff --git a/src/service/rooms/metadata/mod.rs b/src/service/rooms/metadata/mod.rs index 54eef47d1..310ab2c76 100644 --- a/src/service/rooms/metadata/mod.rs +++ b/src/service/rooms/metadata/mod.rs @@ -3,7 +3,7 @@ use conduwuit::{Result, implement, utils::stream::TryIgnore}; use database::Map; use futures::{Stream, StreamExt}; -use ruma::RoomId; +use ruma::{OwnedRoomId, RoomId}; use crate::{Dep, rooms}; @@ -58,7 +58,7 @@ pub async fn exists(&self, room_id: &RoomId) -> bool { } #[implement(Service)] -pub fn iter_ids(&self) -> impl Stream + Send + '_ { +pub fn iter_ids(&self) -> impl Stream + Send + '_ { self.db.roomid_shortroomid.keys().ignore_err() } @@ -83,7 +83,7 @@ pub fn ban_room(&self, room_id: &RoomId, banned: bool) { } #[implement(Service)] -pub fn list_banned_rooms(&self) -> impl Stream + Send + '_ { +pub fn list_banned_rooms(&self) -> impl Stream + Send + '_ { self.db.bannedroomids.keys().ignore_err() } diff --git a/src/service/rooms/read_receipt/data.rs b/src/service/rooms/read_receipt/data.rs index c8df393d6..5058af4dd 100644 --- a/src/service/rooms/read_receipt/data.rs +++ b/src/service/rooms/read_receipt/data.rs @@ -7,9 +7,7 @@ use database::{Deserialized, Json, Map}; use futures::{Stream, StreamExt}; use ruma::{ - CanonicalJsonObject, OwnedUserId, RoomId, UserId, - events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent}, - serde::Raw, + CanonicalJsonObject, OwnedRoomId, OwnedUserId, RoomId, UserId, events::{AnySyncEphemeralRoomEvent, receipt::ReceiptEvent}, serde::Raw }; use crate::{Dep, globals}; @@ -66,8 +64,8 @@ pub(super) fn readreceipts_since<'a>( room_id: &'a RoomId, since: u64, ) -> impl Stream + Send + 'a { - type Key<'a> = (&'a RoomId, u64, &'a UserId); - type KeyVal<'a> = (Key<'a>, CanonicalJsonObject); + type Key = (OwnedRoomId, u64, OwnedUserId); + type KeyVal = (Key, CanonicalJsonObject); let after_since = since.saturating_add(1); // +1 so we don't send the event at since let first_possible_edu = (room_id, after_since); @@ -75,13 +73,13 @@ pub(super) fn readreceipts_since<'a>( self.readreceiptid_readreceipt .stream_from(&first_possible_edu) .ignore_err() - .ready_take_while(move |((r, ..), _): &KeyVal<'_>| *r == room_id) - .map(move |((_, count, user_id), mut json): KeyVal<'_>| { + .ready_take_while(move |((r, ..), _): &KeyVal| *r == room_id) + .map(move |((_, count, user_id), mut json): KeyVal| { json.remove("room_id"); let event = serde_json::value::to_raw_value(&json)?; - Ok((user_id.to_owned(), count, Raw::from_json(event))) + Ok((user_id, count, Raw::from_json(event))) }) .ignore_err() } diff --git a/src/service/rooms/read_receipt/mod.rs b/src/service/rooms/read_receipt/mod.rs index 09381e158..89280a34e 100644 --- a/src/service/rooms/read_receipt/mod.rs +++ b/src/service/rooms/read_receipt/mod.rs @@ -85,18 +85,21 @@ pub async fn private_read_get( let event_id: OwnedEventId = pdu.event_id().to_owned(); let user_id: OwnedUserId = user_id.to_owned(); + + let mut receipt = ruma::events::receipt::Receipt::default(); + // TODO: start storing the timestamp so we can return one + receipt.ts = None; + receipt.thread = ruma::events::receipt::ReceiptThread::Unthreaded; + let content: BTreeMap = BTreeMap::from_iter([( event_id, BTreeMap::from_iter([( ruma::events::receipt::ReceiptType::ReadPrivate, - BTreeMap::from_iter([(user_id, ruma::events::receipt::Receipt { - ts: None, // TODO: start storing the timestamp so we can return one - thread: ruma::events::receipt::ReceiptThread::Unthreaded, - })]), + BTreeMap::from_iter([(user_id, receipt)]), )]), )]); let receipt_event_content = ReceiptEventContent(content); - let receipt_sync_event = SyncEphemeralRoomEvent { content: receipt_event_content }; + let receipt_sync_event = SyncEphemeralRoomEvent::new(receipt_event_content); let event = serde_json::value::to_raw_value(&receipt_sync_event) .expect("receipt created manually"); @@ -165,7 +168,7 @@ pub fn pack_receipts(receipts: I) -> Raw (), // cache miss | Some(None) => return Ok(None), | Some(Some(cached)) => { - let allowed_rooms = cached.summary.allowed_room_ids.iter().map(AsRef::as_ref); - let is_accessible_child = self.is_accessible_child( current_room, - &cached.summary.join_rule, + &cached.summary.summary.join_rule, identifier, - allowed_rooms, ); let accessibility = if is_accessible_child.await { @@ -172,10 +165,8 @@ async fn get_summary_and_children_federation( user_id: &UserId, via: &[OwnedServerName], ) -> Result> { - let request = federation::space::get_hierarchy::v1::Request { - room_id: current_room.to_owned(), - suggested_only, - }; + let mut request = federation::space::get_hierarchy::v1::Request::new(current_room.to_owned()); + request.suggested_only = suggested_only; let mut requests: FuturesUnordered<_> = via .iter() @@ -213,14 +204,13 @@ async fn get_summary_and_children_federation( .ready_filter_map(|(child, mut cache)| { (!cache.contains_key(current_room)).then_some((child, cache)) }) - .for_each(|(child, cache)| self.cache_insert(cache, current_room, child)) + .for_each(|(summary, cache)| self.cache_insert(cache, current_room, summary)) .await; let identifier = Identifier::UserId(user_id); - let allowed_room_ids = summary.allowed_room_ids.iter().map(AsRef::as_ref); let is_accessible_child = self - .is_accessible_child(current_room, &summary.join_rule, &identifier, allowed_room_ids) + .is_accessible_child(current_room, &summary.summary.join_rule, &identifier) .await; let accessibility = if is_accessible_child { @@ -313,7 +303,6 @@ async fn get_room_summary( room_id, &join_rule.clone().into(), identifier, - join_rule.allowed_rooms(), ) .await; @@ -381,38 +370,34 @@ async fn get_room_summary( encryption, ); - let summary = SpaceHierarchyParentSummary { - canonical_alias, - name, - topic, - world_readable, + let mut summary = RoomSummary::new( + room_id.to_owned(), + join_rule.clone().into(), guest_can_join, - avatar_url, - room_type, - children_state, - encryption, - room_version, - room_id: room_id.to_owned(), - num_joined_members: num_joined_members.try_into().unwrap_or_default(), - allowed_room_ids: join_rule.allowed_rooms().map(Into::into).collect(), - join_rule: join_rule.clone().into(), - }; + num_joined_members.try_into().unwrap_or_default(), + world_readable + ); + summary.canonical_alias = canonical_alias; + summary.name = name; + summary.topic = topic; + summary.avatar_url = avatar_url; + summary.encryption = encryption; + summary.room_type = room_type; + summary.room_version = room_version; + + let summary = SpaceHierarchyParentSummary::new(summary, children_state); Ok(summary) } /// With the given identifier, checks if a room is accessible #[implement(Service)] -async fn is_accessible_child<'a, I>( +async fn is_accessible_child( &self, current_room: &RoomId, - join_rule: &SpaceRoomJoinRule, + join_rule: &JoinRuleSummary, identifier: &Identifier<'_>, - allowed_rooms: I, -) -> bool -where - I: Iterator + Send, -{ +) -> bool { if let Identifier::ServerName(server_name) = identifier { // Checks if ACLs allow for the server to participate if self @@ -437,23 +422,17 @@ async fn is_accessible_child<'a, I>( } } - match *join_rule { - | SpaceRoomJoinRule::Public - | SpaceRoomJoinRule::Knock - | SpaceRoomJoinRule::KnockRestricted => true, - | SpaceRoomJoinRule::Restricted => - allowed_rooms - .stream() - .any(async |room| match identifier { - | Identifier::UserId(user) => - self.services.state_cache.is_joined(user, room).await, - | Identifier::ServerName(server) => - self.services.state_cache.server_in_room(server, room).await, - }) - .await, - - // Invite only, Private, or Custom join rule - | _ => false, + match join_rule { + | JoinRuleSummary::Public + | JoinRuleSummary::Knock + | JoinRuleSummary::KnockRestricted(_) => true, + | JoinRuleSummary::Restricted(restricted_summary) => { + (&restricted_summary.allowed_room_ids).stream().any(async |room| match identifier { + | Identifier::UserId(user) => self.services.state_cache.is_joined(user, room).await, + | Identifier::ServerName(server) => self.services.state_cache.server_in_room(server, room).await, + }).await + }, + _ => false } } @@ -481,44 +460,14 @@ async fn cache_insert( &self, mut cache: MutexGuard<'_, Cache>, current_room: &RoomId, - child: SpaceHierarchyChildSummary, + summary: RoomSummary, ) { - let SpaceHierarchyChildSummary { - canonical_alias, - name, - num_joined_members, - room_id, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - allowed_room_ids, - encryption, - room_version, - } = child; - - let summary = SpaceHierarchyParentSummary { - canonical_alias, - name, - num_joined_members, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - allowed_room_ids, - room_id: room_id.clone(), - children_state: self - .get_space_child_events(&room_id) + let children_state = self + .get_space_child_events(&summary.room_id) .map(Event::into_format) .collect() - .await, - encryption, - room_version, - }; + .await; + let summary = SpaceHierarchyParentSummary::new(summary, children_state); cache.insert(current_room.to_owned(), Some(CachedSpaceHierarchySummary { summary })); } @@ -527,39 +476,7 @@ async fn cache_insert( // ruma-client-api types impl From for SpaceHierarchyRoomsChunk { fn from(value: CachedSpaceHierarchySummary) -> Self { - let SpaceHierarchyParentSummary { - canonical_alias, - name, - num_joined_members, - room_id, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - children_state, - allowed_room_ids, - encryption, - room_version, - } = value.summary; - - Self { - canonical_alias, - name, - num_joined_members, - room_id, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - children_state, - encryption, - room_version, - allowed_room_ids, - } + Self::new(value.summary.summary, value.summary.children_state) } } @@ -567,37 +484,5 @@ fn from(value: CachedSpaceHierarchySummary) -> Self { /// ruma-client-api types #[must_use] pub fn summary_to_chunk(summary: SpaceHierarchyParentSummary) -> SpaceHierarchyRoomsChunk { - let SpaceHierarchyParentSummary { - canonical_alias, - name, - num_joined_members, - room_id, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - children_state, - allowed_room_ids, - encryption, - room_version, - } = summary; - - SpaceHierarchyRoomsChunk { - canonical_alias, - name, - num_joined_members, - room_id, - topic, - world_readable, - guest_can_join, - avatar_url, - join_rule, - room_type, - children_state, - encryption, - room_version, - allowed_room_ids, - } + SpaceHierarchyRoomsChunk::new(summary.summary, summary.children_state) } diff --git a/src/service/rooms/spaces/tests.rs b/src/service/rooms/spaces/tests.rs index d0395fdd0..4f14627f7 100644 --- a/src/service/rooms/spaces/tests.rs +++ b/src/service/rooms/spaces/tests.rs @@ -1,23 +1,16 @@ use std::str::FromStr; use ruma::{ - UInt, - api::federation::space::{SpaceHierarchyParentSummary, SpaceHierarchyParentSummaryInit}, - owned_room_id, owned_server_name, - space::SpaceRoomJoinRule, + UInt, api::federation::space::SpaceHierarchyParentSummary, owned_room_id, owned_server_name, room::{JoinRuleSummary, RoomSummary}, }; use crate::rooms::spaces::{PaginationToken, get_parent_children_via}; #[test] fn get_summary_children() { - let summary: SpaceHierarchyParentSummary = SpaceHierarchyParentSummaryInit { - num_joined_members: UInt::from(1_u32), - room_id: owned_room_id!("!root:example.org"), - world_readable: true, - guest_can_join: true, - join_rule: SpaceRoomJoinRule::Public, - children_state: vec![ + let summary = SpaceHierarchyParentSummary::new( + RoomSummary::new(owned_room_id!("!root:example.org"), JoinRuleSummary::Public, true, UInt::from(1_u32), true), + vec![ serde_json::from_str( r#"{ "content": { @@ -62,10 +55,8 @@ fn get_summary_children() { }"#, ) .unwrap(), - ], - allowed_room_ids: vec![], - } - .into(); + ] + ); assert_eq!( get_parent_children_via(&summary, false) diff --git a/src/service/rooms/state/mod.rs b/src/service/rooms/state/mod.rs index 554dae84a..8731b86f8 100644 --- a/src/service/rooms/state/mod.rs +++ b/src/service/rooms/state/mod.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Write, iter::once, sync::Arc}; use async_trait::async_trait; -use conduwuit::{RoomVersion, debug}; +use conduwuit::debug; use conduwuit_core::{ Event, PduEvent, Result, err, result::FlatOk, @@ -17,12 +17,10 @@ FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, future::join_all, pin_mut, }; use ruma::{ - EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId, - events::{ + EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId, events::{ AnyStrippedStateEvent, StateEventType, TimelineEventType, room::create::RoomCreateEventContent, - }, - serde::Raw, + }, room_version_rules::RoomVersionRules, serde::Raw }; use crate::{ @@ -128,7 +126,7 @@ pub async fn force_state( self.services .state_cache - .update_membership(room_id, user_id, &pdu, false) + .update_membership(room_id, &user_id, &pdu, false) .await?; }, | TimelineEventType::SpaceChild => { @@ -381,13 +379,13 @@ pub async fn get_room_shortstatehash(&self, room_id: &RoomId) -> Result( &'a self, room_id: &'a RoomId, - ) -> impl Stream + Send + 'a { + ) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomid_pduleaves .keys_prefix(&prefix) - .map_ok(|(_, event_id): (Ignore, &EventId)| event_id) + .map_ok(|(_, event_id): (Ignore, OwnedEventId)| event_id) .ignore_err() } @@ -414,7 +412,7 @@ pub async fn set_forward_extremities<'a, I>( } /// This fetches auth events from the current state. - #[tracing::instrument(skip(self, content, room_version), level = "trace")] + #[tracing::instrument(skip(self, content, room_version_rules), level = "trace")] pub async fn get_auth_events( &self, room_id: &RoomId, @@ -422,14 +420,14 @@ pub async fn get_auth_events( sender: &UserId, state_key: Option<&str>, content: &serde_json::value::RawValue, - room_version: &RoomVersion, + room_version_rules: &RoomVersionRules, ) -> Result> { let Ok(shortstatehash) = self.get_room_shortstatehash(room_id).await else { return Ok(HashMap::new()); }; let auth_types = - state_res::auth_types_for_event(kind, sender, state_key, content, room_version)?; + state_res::auth_types_for_event(kind, sender, state_key, content, room_version_rules)?; debug!(?auth_types, "Auth types for event"); let sauthevents: HashMap<_, _> = auth_types .iter() diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs index f719fc7ba..3dacb082e 100644 --- a/src/service/rooms/state_accessor/mod.rs +++ b/src/service/rooms/state_accessor/mod.rs @@ -3,29 +3,18 @@ mod state; mod user_can; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; use async_trait::async_trait; -use conduwuit::{Result, err}; +use conduwuit::{Event, Result, err}; use database::Map; use ruma::{ - EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, RoomId, UserId, - events::{ + EventEncryptionAlgorithm, JsOption, OwnedRoomAliasId, OwnedUserId, RoomId, UserId, events::{ StateEventType, room::{ - avatar::RoomAvatarEventContent, - canonical_alias::RoomCanonicalAliasEventContent, - create::RoomCreateEventContent, - encryption::RoomEncryptionEventContent, - guest_access::{GuestAccess, RoomGuestAccessEventContent}, - history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, - join_rules::{JoinRule, RoomJoinRulesEventContent}, - member::RoomMemberEventContent, - name::RoomNameEventContent, - topic::RoomTopicEventContent, + avatar::RoomAvatarEventContent, canonical_alias::RoomCanonicalAliasEventContent, create::{RoomCreateEvent, RoomCreateEventContent}, encryption::RoomEncryptionEventContent, guest_access::{GuestAccess, RoomGuestAccessEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, join_rules::{JoinRule, RoomJoinRulesEventContent}, member::RoomMemberEventContent, name::RoomNameEventContent, pinned_events::RoomPinnedEventsEventContent, power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, topic::RoomTopicEventContent }, - }, - room::RoomType, + }, room::RoomType }; use crate::{Dep, rooms}; @@ -162,4 +151,38 @@ pub async fn is_encrypted_room(&self, room_id: &RoomId) -> bool { .await .is_ok() } + + /// Get a set of the room's creators. This will always contain a single user for room versions 11 and earlier. + pub async fn get_room_creators(&self, room_id: &RoomId) -> HashSet { + let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known"); + + let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await.expect("room should have a create event"); + let create_content: RoomCreateEventContent = create_event.get_content().expect("create event content should be valid"); + + let mut creators = HashSet::new(); + if room_version_rules.authorization.use_room_create_sender { + creators.insert(create_event.sender); + } else { + #[allow(deprecated)] + creators.insert(create_content.creator.unwrap()); + } + + if room_version_rules.authorization.additional_room_creators { + creators.extend(create_content.additional_creators); + } + + creators + } + + /// Get the room's power levels. This will never fail -- if the room has no power level state event, + /// the default power levels for the room's version will be returned. + pub async fn get_room_power_levels(&self, room_id: &RoomId) -> RoomPowerLevels { + let room_version_rules = self.services.state.get_room_version(room_id).await.expect("room should have a version").rules().expect("room version should be known"); + let creators = self.get_room_creators(room_id).await; + let power_levels_event: RoomPowerLevelsEventContent = self.room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "") + .await + .unwrap_or_else(|_| RoomPowerLevelsEventContent::new(&room_version_rules.authorization)); + + RoomPowerLevels::new(power_levels_event.into(), &room_version_rules.authorization, creators) + } } diff --git a/src/service/rooms/state_accessor/server_can.rs b/src/service/rooms/state_accessor/server_can.rs index 5613d369e..f4886b6ef 100644 --- a/src/service/rooms/state_accessor/server_can.rs +++ b/src/service/rooms/state_accessor/server_can.rs @@ -44,13 +44,17 @@ pub async fn server_can_see_event( | HistoryVisibility::Invited => { // Allow if any member on requesting server was AT LEAST invited, else deny current_server_members - .any(|member| self.user_was_invited(shortstatehash, member)) + .any(async |member| { + self.user_was_invited(shortstatehash, &member).await + }) .await }, | HistoryVisibility::Joined => { // Allow if any member on requested server was joined, else deny current_server_members - .any(|member| self.user_was_joined(shortstatehash, member)) + .any(async |member| { + self.user_was_joined(shortstatehash, &member).await + }) .await }, | HistoryVisibility::WorldReadable | HistoryVisibility::Shared | _ => true, diff --git a/src/service/rooms/state_accessor/state.rs b/src/service/rooms/state_accessor/state.rs index 136a570d0..e96fb2e56 100644 --- a/src/service/rooms/state_accessor/state.rs +++ b/src/service/rooms/state_accessor/state.rs @@ -316,7 +316,7 @@ pub fn state_full( shortstatehash: ShortStateHash, ) -> impl Stream + Send + '_ { self.state_full_pdus(shortstatehash) - .ready_filter_map(|pdu| Some(((pdu.kind().clone().into(), pdu.state_key()?.into()), pdu))) + .ready_filter_map(|pdu| Some(((pdu.kind().to_string().into(), pdu.state_key()?.into()), pdu))) } #[implement(super::Service)] diff --git a/src/service/rooms/state_accessor/user_can.rs b/src/service/rooms/state_accessor/user_can.rs index 36bafa284..699dec685 100644 --- a/src/service/rooms/state_accessor/user_can.rs +++ b/src/service/rooms/state_accessor/user_can.rs @@ -1,4 +1,4 @@ -use conduwuit::{Err, Result, RoomVersion, implement, matrix::Event, pdu::PduBuilder}; +use conduwuit::{Err, Result, implement, matrix::Event, pdu::PduBuilder}; use ruma::{ EventId, RoomId, UserId, events::{ @@ -7,7 +7,6 @@ create::RoomCreateEventContent, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, member::{MembershipState, RoomMemberEventContent}, - power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, }, }, }; @@ -45,53 +44,45 @@ pub async fn user_can_redact( ))); } - let room_create = self - .room_state_get(room_id, &StateEventType::RoomCreate, "") - .await?; - let create_content: RoomCreateEventContent = - serde_json::from_str(room_create.content().get())?; - let room_features = RoomVersion::new(&create_content.room_version)?; - if room_features.explicitly_privilege_room_creators { + let create_event = self.room_state_get(room_id, &StateEventType::RoomCreate, "").await?; + let create_event_content: RoomCreateEventContent = create_event.get_content().unwrap(); + let room_version_rules = create_event_content.room_version.rules().expect("room version should have defined rules"); + if room_version_rules.authorization.explicitly_privilege_room_creators { let sender_owned = sender.to_owned(); - if sender == room_create.sender() - || create_content + // NOTE: we don't check the pre-v11 `creator` field because no room version has + // `explicitly_privilege_room_creators` and `use_room_create_sender` set at the same time + if sender == create_event.sender() + || create_event_content .additional_creators - .is_some_and(|cs| cs.contains(&sender_owned)) + .contains(&sender_owned) { return Ok(true); } } - match self - .room_state_get_content::( - room_id, - &StateEventType::RoomPowerLevels, - "", - ) - .await - { - | Ok(pl_event_content) => { - let pl_event: RoomPowerLevels = pl_event_content.into(); - Ok(pl_event.user_can_redact_event_of_other(sender) - || pl_event.user_can_redact_own_event(sender) - && match redacting_event { - | Ok(redacting_event) => - if federation { - redacting_event.sender().server_name() == sender.server_name() - } else { - redacting_event.sender() == sender - }, - | _ => false, - }) - }, - | _ => { - // Falling back on m.room.create to judge power level - Ok(room_create.sender() == sender - || redacting_event - .as_ref() - .is_ok_and(|redacting_event| redacting_event.sender() == sender)) - }, + + let power_levels = self.get_room_power_levels(room_id).await; + + if power_levels.user_can_redact_event_of_other(sender) { + return Ok(true); } + + if power_levels.user_can_redact_own_event(sender) { + let is_own_event = match redacting_event { + Ok(redacting_event) => { + if federation { + redacting_event.sender().server_name() == sender.server_name() + } else { + redacting_event.sender() == sender + } + }, + _ => false + }; + + return Ok(is_own_event); + } + + return Ok(false); } /// Whether a user is allowed to see an event, based on diff --git a/src/service/rooms/state_cache/mod.rs b/src/service/rooms/state_cache/mod.rs index 1a7a8a47b..6a15dfb80 100644 --- a/src/service/rooms/state_cache/mod.rs +++ b/src/service/rooms/state_cache/mod.rs @@ -12,9 +12,7 @@ use database::{Deserialized, Ignore, Interfix, Map}; use futures::{Stream, StreamExt, future::join5, pin_mut}; use ruma::{ - OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId, - events::{AnyStrippedStateEvent, room::member::MembershipState}, - serde::Raw, + OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, events::{AnyStrippedStateEvent, room::member::MembershipState}, serde::Raw }; use crate::{Dep, account_data, appservice::RegistrationInfo, config, globals, rooms, users}; @@ -147,13 +145,13 @@ pub fn get_appservice_in_room_cache_usage(&self) -> (usize, usize) { pub fn room_servers<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomserverids .keys_prefix(&prefix) .ignore_err() - .map(|(_, server): (Ignore, &ServerName)| server) + .map(|(_, server): (Ignore, OwnedServerName)| server) } #[implement(Service)] @@ -170,13 +168,13 @@ pub async fn server_in_room<'a>(&'a self, server: &'a ServerName, room_id: &'a R pub fn server_rooms<'a>( &'a self, server: &'a ServerName, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (server, Interfix); self.db .serverroomids .keys_prefix(&prefix) .ignore_err() - .map(|(_, room_id): (Ignore, &RoomId)| room_id) + .map(|(_, room_id): (Ignore, OwnedRoomId)| room_id) } /// Returns true if server can see user by sharing at least one room. @@ -184,7 +182,7 @@ pub fn server_rooms<'a>( #[tracing::instrument(skip(self), level = "trace")] pub async fn server_sees_user(&self, server: &ServerName, user_id: &UserId) -> bool { self.server_rooms(server) - .any(|room_id| self.is_joined(user_id, room_id)) + .any(async |room_id| self.is_joined(user_id, &room_id).await) .await } @@ -205,7 +203,7 @@ pub fn get_shared_rooms<'a>( &'a self, user_a: &'a UserId, user_b: &'a UserId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { use conduwuit::utils::set; let a = self.rooms_joined(user_a); @@ -219,13 +217,13 @@ pub fn get_shared_rooms<'a>( pub fn room_members<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomuserid_joined .keys_prefix(&prefix) .ignore_err() - .map(|(_, user_id): (Ignore, &UserId)| user_id) + .map(|(_, user_id): (Ignore, OwnedUserId)| user_id) } /// Returns the number of users which are currently in a room @@ -242,7 +240,7 @@ pub async fn room_joined_count(&self, room_id: &RoomId) -> Result { pub fn local_users_in_room<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { self.room_members(room_id) .ready_filter(|user| self.services.globals.user_is_local(user)) } @@ -254,9 +252,15 @@ pub fn local_users_in_room<'a>( pub fn active_local_users_in_room<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { self.local_users_in_room(room_id) - .filter(|user| self.services.users.is_active(user)) + .filter_map(async |user_id| { + if self.services.users.is_active(&user_id).await { + Some(user_id) + } else { + None + } + }) } /// Returns the number of users which are currently invited to a room @@ -276,13 +280,13 @@ pub async fn room_invited_count(&self, room_id: &RoomId) -> Result { pub fn room_useroncejoined<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomuseroncejoinedids .keys_prefix(&prefix) .ignore_err() - .map(|(_, user_id): (Ignore, &UserId)| user_id) + .map(|(_, user_id): (Ignore, OwnedUserId)| user_id) } /// Returns an iterator over all invited members of a room. @@ -291,13 +295,13 @@ pub fn room_useroncejoined<'a>( pub fn room_members_invited<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomuserid_invitecount .keys_prefix(&prefix) .ignore_err() - .map(|(_, user_id): (Ignore, &UserId)| user_id) + .map(|(_, user_id): (Ignore, OwnedUserId)| user_id) } /// Returns an iterator over all knocked members of a room. @@ -306,13 +310,13 @@ pub fn room_members_invited<'a>( pub fn room_members_knocked<'a>( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { let prefix = (room_id, Interfix); self.db .roomuserid_knockedcount .keys_prefix(&prefix) .ignore_err() - .map(|(_, user_id): (Ignore, &UserId)| user_id) + .map(|(_, user_id): (Ignore, OwnedUserId)| user_id) } #[implement(Service)] @@ -350,12 +354,12 @@ pub async fn get_left_count(&self, room_id: &RoomId, user_id: &UserId) -> Result pub fn rooms_joined<'a>( &'a self, user_id: &'a UserId, -) -> impl Stream + Send + 'a { +) -> impl Stream + Send + 'a { self.db .userroomid_joined .keys_raw_prefix(user_id) .ignore_err() - .map(|(_, room_id): (Ignore, &RoomId)| room_id) + .map(|(_, room_id): (Ignore, OwnedRoomId)| room_id) } /// Returns an iterator over all rooms a user was invited to. @@ -365,16 +369,16 @@ pub fn rooms_invited<'a>( &'a self, user_id: &'a UserId, ) -> impl Stream + Send + 'a { - type KeyVal<'a> = (Key<'a>, Raw>); - type Key<'a> = (&'a UserId, &'a RoomId); + type KeyVal = (Key, Raw>); + type Key = (OwnedUserId, OwnedRoomId); let prefix = (user_id, Interfix); self.db .userroomid_invitestate .stream_prefix(&prefix) .ignore_err() - .map(|((_, room_id), state): KeyVal<'_>| (room_id.to_owned(), state)) - .map(|(room_id, state)| Ok((room_id, state.deserialize_as()?))) + .map(|((_, room_id), state): KeyVal| (room_id, state)) + .map(|(room_id, state)| Ok((room_id, state.deserialize_as_unchecked()?))) .ignore_err() } @@ -385,16 +389,16 @@ pub fn rooms_knocked<'a>( &'a self, user_id: &'a UserId, ) -> impl Stream + Send + 'a { - type KeyVal<'a> = (Key<'a>, Raw>); - type Key<'a> = (&'a UserId, &'a RoomId); + type KeyVal = (Key, Raw>); + type Key = (OwnedUserId, OwnedRoomId); let prefix = (user_id, Interfix); self.db .userroomid_knockedstate .stream_prefix(&prefix) .ignore_err() - .map(|((_, room_id), state): KeyVal<'_>| (room_id.to_owned(), state)) - .map(|(room_id, state)| Ok((room_id, state.deserialize_as()?))) + .map(|((_, room_id), state): KeyVal| (room_id, state)) + .map(|(room_id, state)| Ok((room_id, state.deserialize_as_unchecked()?))) .ignore_err() } @@ -411,7 +415,7 @@ pub async fn invite_state( .qry(&key) .await .deserialized() - .and_then(|val: Raw>| val.deserialize_as().map_err(Into::into)) + .and_then(|val: Raw>| val.deserialize_as_unchecked().map_err(Into::into)) } #[implement(Service)] @@ -427,7 +431,7 @@ pub async fn knock_state( .qry(&key) .await .deserialized() - .and_then(|val: Raw>| val.deserialize_as().map_err(Into::into)) + .and_then(|val: Raw>| val.deserialize_as_unchecked().map_err(Into::into)) } #[implement(Service)] @@ -444,15 +448,15 @@ pub fn rooms_left<'a>( &'a self, user_id: &'a UserId, ) -> impl Stream)> + Send + 'a { - type KeyVal<'a> = (Key<'a>, Raw>); - type Key<'a> = (&'a UserId, &'a RoomId); + type KeyVal = (Key, Raw>); + type Key = (OwnedUserId, OwnedRoomId); let prefix = (user_id, Interfix); self.db .userroomid_leftstate .stream_prefix(&prefix) .ignore_err() - .map(|((_, room_id), state): KeyVal<'_>| (room_id.to_owned(), state)) + .map(|((_, room_id), state): KeyVal| (room_id, state)) .map(|(room_id, state)| Ok((room_id, state.deserialize()?))) .ignore_err() } diff --git a/src/service/rooms/state_cache/update.rs b/src/service/rooms/state_cache/update.rs index bf305dbee..042cd43f1 100644 --- a/src/service/rooms/state_cache/update.rs +++ b/src/service/rooms/state_cache/update.rs @@ -9,7 +9,6 @@ AnyStrippedStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType, StateEventType, direct::DirectEvent, - invite_permission_config::FilterLevel, room::{ create::RoomCreateEventContent, member::{MembershipState, RoomMemberEventContent}, @@ -17,6 +16,7 @@ }, serde::Raw, }; +use ruminuwuity::invite_permission_config::FilterLevel; /// Update current membership data. #[implement(super::Service)] @@ -174,12 +174,12 @@ pub async fn update_joined_count(&self, room_id: &RoomId) { self.room_servers(room_id) .ready_for_each(|old_joined_server| { - if joined_servers.remove(old_joined_server) { + if joined_servers.remove(&old_joined_server) { return; } // Server not in room anymore - let roomserver_id = (room_id, old_joined_server); + let roomserver_id = (room_id, old_joined_server.clone()); let serverroom_id = (old_joined_server, room_id); self.db.roomserverids.del(roomserver_id); diff --git a/src/service/rooms/state_cache/via.rs b/src/service/rooms/state_cache/via.rs index 24d92a219..5f88779af 100644 --- a/src/service/rooms/state_cache/via.rs +++ b/src/service/rooms/state_cache/via.rs @@ -17,7 +17,6 @@ pub async fn add_servers_invite_via(&self, room_id: &RoomId, servers: Vec) { let mut servers: Vec<_> = self .servers_invite_via(room_id) - .map(ToOwned::to_owned) .chain(iter(servers.into_iter())) .collect() .await; @@ -81,12 +80,12 @@ pub async fn servers_route_via(&self, room_id: &RoomId) -> Result( &'a self, room_id: &'a RoomId, -) -> impl Stream + Send + 'a { - type KeyVal<'a> = (Ignore, Vec<&'a ServerName>); +) -> impl Stream + Send + 'a { + type KeyVal = (Ignore, Vec); self.db .roomid_inviteviaservers .stream_raw_prefix(room_id) .ignore_err() - .map(|(_, servers): KeyVal<'_>| *servers.last().expect("at least one server")) + .map(|(_, mut servers): KeyVal| servers.pop().expect("at least one server")) } diff --git a/src/service/rooms/threads/mod.rs b/src/service/rooms/threads/mod.rs index 58d11ccee..19ff1d720 100644 --- a/src/service/rooms/threads/mod.rs +++ b/src/service/rooms/threads/mod.rs @@ -11,8 +11,7 @@ use conduwuit_database::{Deserialized, Map}; use futures::{Stream, StreamExt}; use ruma::{ - CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId, - api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread, uint, + CanonicalJsonValue, EventId, OwnedUserId, RoomId, UserId, api::client::threads::get_threads::v1::IncludeThreads, events::relation::BundledThread, serde::Raw, uint }; use serde_json::json; @@ -89,7 +88,7 @@ pub async fn add_to_thread(&self, root_event_id: &EventId, event: &E) -> Resu }) { // Thread already existed relations.count = relations.count.saturating_add(uint!(1)); - relations.latest_event = event.to_format(); + relations.latest_event = Raw::from_json(event.content().to_owned()); let content = serde_json::to_value(relations).expect("to_value always works"); @@ -101,11 +100,7 @@ pub async fn add_to_thread(&self, root_event_id: &EventId, event: &E) -> Resu ); } else { // New thread - let relations = BundledThread { - latest_event: event.to_format(), - count: uint!(1), - current_user_participated: true, - }; + let relations = BundledThread::new(Raw::from_json(event.content().to_owned()), uint!(1), true); let content = serde_json::to_value(relations).expect("to_value always works"); diff --git a/src/service/rooms/timeline/append.rs b/src/service/rooms/timeline/append.rs index 40139a983..08139511f 100644 --- a/src/service/rooms/timeline/append.rs +++ b/src/service/rooms/timeline/append.rs @@ -16,10 +16,10 @@ use ruma::{ CanonicalJsonObject, CanonicalJsonValue, EventId, RoomVersionId, UserId, events::{ - GlobalAccountDataEventType, StateEventType, TimelineEventType, + GlobalAccountDataEventType, TimelineEventType, push_rules::PushRulesEvent, room::{ - encrypted::Relation, power_levels::RoomPowerLevelsEventContent, + encrypted::Relation, redaction::RoomRedactionEventContent, }, }, @@ -204,18 +204,11 @@ pub async fn append_pdu<'a, Leaves>( drop(insert_lock); // See if the event matches any known pushers via power level - let power_levels: RoomPowerLevelsEventContent = self - .services - .state_accessor - .room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "") - .await - .unwrap_or_default(); - + let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await; let mut push_target: HashSet<_> = self .services .state_cache .active_local_users_in_room(room_id) - .map(ToOwned::to_owned) // Don't notify the sender of their own events, and dont send from ignored users .ready_filter(|user| *user != pdu.sender()) .filter_map(|recipient_user| async move { (!self.services.users.user_is_ignored(pdu.sender(), &recipient_user).await).then_some(recipient_user) }) @@ -229,7 +222,7 @@ pub async fn append_pdu<'a, Leaves>( if let Some(state_key) = pdu.state_key() { let target_user_id = UserId::parse(state_key)?; - if self.services.users.is_active_local(target_user_id).await { + if self.services.users.is_active_local(&target_user_id).await { push_target.insert(target_user_id.to_owned()); } } @@ -253,7 +246,7 @@ pub async fn append_pdu<'a, Leaves>( for action in self .services .pusher - .get_actions(user, &rules_for_user, &power_levels, &serialized, room_id) + .get_actions(user, &rules_for_user, power_levels.clone(), &serialized, room_id) .await { match action { @@ -346,7 +339,7 @@ pub async fn append_pdu<'a, Leaves>( // knock event for auth self.services .state_cache - .update_membership(room_id, target_user_id, pdu, true) + .update_membership(room_id, &target_user_id, pdu, true) .await?; } }, diff --git a/src/service/rooms/timeline/backfill.rs b/src/service/rooms/timeline/backfill.rs index 9e3cd273c..53b69617b 100644 --- a/src/service/rooms/timeline/backfill.rs +++ b/src/service/rooms/timeline/backfill.rs @@ -1,6 +1,6 @@ -use std::iter::once; +use std::{collections::HashSet, iter::once}; -use conduwuit::{Err, PduEvent, RoomVersion}; +use conduwuit::{Err, PduEvent}; use conduwuit_core::{ Result, debug, debug_warn, err, implement, info, matrix::{ @@ -10,15 +10,12 @@ utils::{IterStream, ReadyExt}, validated, warn, }; -use futures::{FutureExt, StreamExt}; +use futures::{FutureExt, Stream, StreamExt}; use ruma::{ - CanonicalJsonObject, EventId, Int, RoomId, ServerName, - api::federation, - events::{ + CanonicalJsonObject, EventId, Int, OwnedServerName, RoomId, ServerName, api::federation, events::{ StateEventType, TimelineEventType, - room::{create::RoomCreateEventContent, power_levels::RoomPowerLevelsEventContent}, - }, - uint, + room::{create::RoomCreateEventContent, power_levels::{RoomPowerLevelsEventContent, UserPowerLevel}}, + }, uint }; use serde_json::value::RawValue as RawJsonValue; @@ -55,94 +52,12 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re return Ok(()); } - let power_levels: RoomPowerLevelsEventContent = self - .services - .state_accessor - .room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "") - .await - .unwrap_or_default(); - let create_event_content: RoomCreateEventContent = self - .services - .state_accessor - .room_state_get_content(room_id, &StateEventType::RoomCreate, "") - .await?; - let create_event = self - .services - .state_accessor - .room_state_get(room_id, &StateEventType::RoomCreate, "") - .await?; - - let room_version = - RoomVersion::new(&create_event_content.room_version).expect("supported room version"); - let mut users = power_levels.users.clone(); - if room_version.explicitly_privilege_room_creators { - users.insert(create_event.sender().to_owned(), Int::MAX); - if let Some(additional_creators) = &create_event_content.additional_creators { - for user_id in additional_creators { - users.insert(user_id.to_owned(), Int::MAX); - } - } - } - - let room_mods = users.iter().filter_map(|(user_id, level)| { - let remote_powered = - level > &power_levels.users_default && !self.services.globals.user_is_local(user_id); - let creator = if room_version.explicitly_privilege_room_creators { - create_event.sender() == user_id - || create_event_content - .additional_creators - .as_ref() - .is_some_and(|c| c.contains(user_id)) - } else { - false - }; - - if remote_powered || creator { - debug!(%remote_powered, %creator, "User {user_id} can backfill in room {room_id}"); - Some(user_id.server_name()) - } else { - debug!(%remote_powered, %creator, "User {user_id} cannot backfill in room {room_id}"); - None - } - }); - - let canonical_room_alias_server = once( - self.services - .state_accessor - .get_canonical_alias(room_id) - .await, - ) - .filter_map(Result::ok) - .map(|alias| alias.server_name().to_owned()) - .stream(); - - let mut servers = room_mods - .stream() - .map(ToOwned::to_owned) - .chain(canonical_room_alias_server) - .chain( - self.services - .server - .config - .trusted_servers - .iter() - .map(ToOwned::to_owned) - .stream(), - ) - .ready_filter(|server_name| !self.services.globals.server_is_ours(server_name)) - .filter_map(|server_name| async move { - self.services - .state_cache - .server_in_room(&server_name, room_id) - .await - .then_some(server_name) - }) - .boxed(); + let mut servers = self.candidate_backfill_servers(room_id).await; let mut federated_room = false; - while let Some(ref backfill_server) = servers.next().await { - if !self.services.globals.server_is_ours(backfill_server) { + for backfill_server in servers { + if !self.services.globals.server_is_ours(&backfill_server) { federated_room = true; } info!("Asking {backfill_server} for backfill in {room_id}"); @@ -150,18 +65,14 @@ pub async fn backfill_if_required(&self, room_id: &RoomId, from: PduCount) -> Re .services .sending .send_federation_request( - backfill_server, - federation::backfill::get_backfill::v1::Request { - room_id: room_id.to_owned(), - v: vec![first_pdu.1.event_id().to_owned()], - limit: uint!(100), - }, + &backfill_server, + federation::backfill::get_backfill::v1::Request::new(room_id.to_owned(), vec![first_pdu.1.event_id().to_owned()], uint!(100)) ) .await; match response { | Ok(response) => { for pdu in response.pdus { - if let Err(e) = self.backfill_pdu(backfill_server, pdu).boxed().await { + if let Err(e) = self.backfill_pdu(&backfill_server, pdu).boxed().await { debug_warn!("Failed to add backfilled pdu in room {room_id}: {e}"); } } @@ -207,62 +118,14 @@ pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Resu return Err!(Request(NotFound("No one can backfill this PDU, room is empty."))); } - let power_levels: RoomPowerLevelsEventContent = self - .services - .state_accessor - .room_state_get_content(room_id, &StateEventType::RoomPowerLevels, "") - .await - .unwrap_or_default(); + let mut servers = self.candidate_backfill_servers(room_id).await; - let room_mods = power_levels.users.iter().filter_map(|(user_id, level)| { - if level > &power_levels.users_default && !self.services.globals.user_is_local(user_id) { - Some(user_id.server_name()) - } else { - None - } - }); - - let canonical_room_alias_server = once( - self.services - .state_accessor - .get_canonical_alias(room_id) - .await, - ) - .filter_map(Result::ok) - .map(|alias| alias.server_name().to_owned()) - .stream(); - let mut servers = room_mods - .stream() - .map(ToOwned::to_owned) - .chain(canonical_room_alias_server) - .chain( - self.services - .server - .config - .trusted_servers - .iter() - .map(ToOwned::to_owned) - .stream(), - ) - .ready_filter(|server_name| !self.services.globals.server_is_ours(server_name)) - .filter_map(|server_name| async move { - self.services - .state_cache - .server_in_room(&server_name, room_id) - .await - .then_some(server_name) - }) - .boxed(); - - while let Some(ref backfill_server) = servers.next().await { + for backfill_server in servers { info!("Asking {backfill_server} for event {}", event_id); let value = self .services .sending - .send_federation_request(backfill_server, federation::event::get_event::v1::Request { - event_id: event_id.to_owned(), - include_unredacted_content: Some(false), - }) + .send_federation_request(&backfill_server, federation::event::get_event::v1::Request::new(event_id.to_owned())) .await .and_then(|response| { serde_json::from_str::(response.pdu.get()).map_err(|e| { @@ -275,7 +138,7 @@ pub async fn get_remote_pdu(&self, room_id: &RoomId, event_id: &EventId) -> Resu | Ok(value) => { self.services .event_handler - .handle_incoming_pdu(backfill_server, room_id, event_id, value, false) + .handle_incoming_pdu(&backfill_server, room_id, event_id, value, false) .boxed() .await?; debug!("Successfully backfilled {event_id} from {backfill_server}"); @@ -305,7 +168,7 @@ pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box) -> .services .event_handler .mutex_federation - .lock(&room_id) + .lock(room_id.as_str()) .await; // Skip the PDU if we already have it as a timeline event @@ -326,7 +189,7 @@ pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box) -> let shortroomid = self.services.short.get_shortroomid(&room_id).await?; - let insert_lock = self.mutex_insert.lock(&room_id).await; + let insert_lock = self.mutex_insert.lock(room_id.as_str()).await; let count: i64 = self.services.globals.next_count().unwrap().try_into()?; @@ -352,3 +215,45 @@ pub async fn backfill_pdu(&self, origin: &ServerName, pdu: Box) -> debug!("Prepended backfill pdu"); Ok(()) } + +#[implement(super::Service)] +async fn candidate_backfill_servers(&self, room_id: &RoomId) -> HashSet { + let mut candidate_backfill_servers = HashSet::new(); + + let power_levels = self.services.state_accessor.get_room_power_levels(room_id).await; + + // Insert servers of room creators + if let Some(creators) = &power_levels.rules.privileged_creators { + for creator in creators { + candidate_backfill_servers.insert(creator.server_name().to_owned()); + } + } + + // Insert servers of remote users with higher-than-default PL + for (user_id, level) in &power_levels.users { + if !self.services.globals.user_is_local(user_id) && *level > power_levels.users_default { + candidate_backfill_servers.insert(user_id.server_name().to_owned()); + } + } + + // Insert the canonical room alias server + if let Ok(canonical_alias) = self.services.state_accessor.get_canonical_alias(room_id).await { + candidate_backfill_servers.insert(canonical_alias.server_name().to_owned()); + } + + // Insert all trusted servers in the config + candidate_backfill_servers.extend(self.services.server.config.trusted_servers.iter().cloned()); + + // Remove our own name, we can't request backfill from ourselves + candidate_backfill_servers.remove(self.services.globals.server_name()); + + // Remove all servers that aren't in the room + for server in candidate_backfill_servers.clone() { + if !self.services.state_cache.server_in_room(&server, room_id).await { + candidate_backfill_servers.remove(&server); + } + } + + debug!(?candidate_backfill_servers, "Found candidate servers for backfill"); + candidate_backfill_servers +} diff --git a/src/service/rooms/timeline/build.rs b/src/service/rooms/timeline/build.rs index 51162de96..2838dafa6 100644 --- a/src/service/rooms/timeline/build.rs +++ b/src/service/rooms/timeline/build.rs @@ -157,7 +157,6 @@ pub async fn build_and_append_pdu( .services .state_cache .room_servers(&room_id) - .map(ToOwned::to_owned) .collect() .await; @@ -180,7 +179,7 @@ pub async fn build_and_append_pdu( trace!("Sending PDU {} to {} servers", pdu.event_id(), servers.len()); self.services .sending - .send_pdu_servers(servers.iter().map(AsRef::as_ref).stream(), &pdu_id) + .send_pdu_servers(servers.stream(), &pdu_id) .await?; trace!("Event {} in room {:?} has been appended", pdu.event_id(), room_id); diff --git a/src/service/rooms/timeline/create.rs b/src/service/rooms/timeline/create.rs index 363711c10..eaa8bfd92 100644 --- a/src/service/rooms/timeline/create.rs +++ b/src/service/rooms/timeline/create.rs @@ -6,7 +6,7 @@ matrix::{ event::{Event, gen_event_id}, pdu::{EventHash, PduBuilder, PduEvent}, - state_res::{self, RoomVersion}, + state_res, }, utils::{self, IterStream, ReadyExt, stream::TryIgnore}, warn, @@ -90,7 +90,7 @@ pub async fn create_event( redacts, timestamp, } = pdu_builder; - // If there was no create event yet, assume we are creating a room + trace!( "Creating event of type {} in room {}", event_type, @@ -121,7 +121,9 @@ pub async fn create_event( }, }; - let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); + let Some(room_version_rules) = room_version.rules() else { + return Err!(Request(UnsupportedRoomVersion("Unsupported room version"))); + }; let prev_events: Vec = match room_id { | Some(room_id) => @@ -145,7 +147,7 @@ pub async fn create_event( sender, state_key.as_deref(), &content, - &room_version, + &room_version_rules, ) .await?, | None => HashMap::new(), @@ -242,7 +244,7 @@ pub async fn create_event( }; let auth_check = state_res::auth_check( - &room_version, + &room_version_rules, &pdu, None, // TODO: third_party_invite auth_fetch, @@ -287,7 +289,7 @@ pub async fn create_hash_and_sign_event( if let Err(e) = self .services .server_keys - .hash_and_sign_event(&mut pdu_json, &room_version_id) + .hash_and_sign_event(&mut pdu_json, &room_version) { return match e { | Error::Signatures(ruma::signatures::Error::PduSize) => { @@ -297,7 +299,7 @@ pub async fn create_hash_and_sign_event( }; } // Generate event id - pdu.event_id = gen_event_id(&pdu_json, &room_version_id)?; + pdu.event_id = gen_event_id(&pdu_json, &room_version)?; pdu_json.insert("event_id".into(), CanonicalJsonValue::String(pdu.event_id.clone().into())); // Verify that the *full* PDU isn't over 64KiB. // Ruma only validates that it's under 64KiB before signing and hashing. diff --git a/src/service/rooms/typing/mod.rs b/src/service/rooms/typing/mod.rs index 28b9dfa7a..a71a4c58f 100644 --- a/src/service/rooms/typing/mod.rs +++ b/src/service/rooms/typing/mod.rs @@ -8,7 +8,7 @@ use ruma::{ OwnedRoomId, OwnedUserId, RoomId, UserId, api::federation::transactions::edu::{Edu, TypingContent}, - events::SyncEphemeralRoomEvent, + events::{SyncEphemeralRoomEvent, typing::TypingEventContent}, }; use tokio::sync::{RwLock, broadcast}; @@ -212,12 +212,9 @@ pub async fn typings_event_for_user( &self, room_id: &RoomId, sender_user: &UserId, - ) -> Result> { - Ok(SyncEphemeralRoomEvent { - content: ruma::events::typing::TypingEventContent { - user_ids: self.typing_users_for_user(room_id, sender_user).await?, - }, - }) + ) -> Result> { + let user_ids = self.typing_users_for_user(room_id, sender_user).await?; + Ok(SyncEphemeralRoomEvent::new(TypingEventContent::new(user_ids))) } async fn federation_send( diff --git a/src/service/sending/antispam.rs b/src/service/sending/antispam.rs index c43cad4b2..47c7eaef9 100644 --- a/src/service/sending/antispam.rs +++ b/src/service/sending/antispam.rs @@ -1,9 +1,11 @@ -use std::{fmt::Debug, mem}; +use std::{borrow::Cow, fmt::Debug, mem}; use bytes::BytesMut; use conduwuit::{Err, Result, debug_error, err, utils, utils::response::LimitReadExt, warn}; use reqwest::Client; -use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken}; +use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, auth_scheme::{AppserviceToken, SendAccessToken}, path_builder::VersionHistory}; + +use crate::SUPPORTED_VERSIONS; /// Sends a request to an antispam service pub(crate) async fn send_antispam_request( @@ -13,11 +15,15 @@ pub(crate) async fn send_antispam_request( request: T, ) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest:: + Debug + Send, { const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15]; let http_request = request - .try_into_http_request::(base_url, SendAccessToken::Always(secret), &VERSIONS)? + .try_into_http_request::( + base_url, + SendAccessToken::Always(secret), + Cow::Borrowed(&SUPPORTED_VERSIONS), + )? .map(BytesMut::freeze); let reqwest_request = reqwest::Request::try_from(http_request)?; diff --git a/src/service/sending/appservice.rs b/src/service/sending/appservice.rs index dc4f2dd1d..37aef8c96 100644 --- a/src/service/sending/appservice.rs +++ b/src/service/sending/appservice.rs @@ -5,7 +5,7 @@ Err, Result, debug_error, err, implement, trace, utils, utils::response::LimitReadExt, warn, }; use ruma::api::{ - IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken, appservice::Registration, + IncomingResponse, MatrixVersion, OutgoingRequest, appservice::Registration, auth_scheme::{AccessToken, SendAccessToken}, path_builder::SinglePath, }; /// Sends a request to an appservice @@ -19,10 +19,8 @@ pub async fn send_appservice_request( request: T, ) -> Result> where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest + Debug + Send, { - const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_7]; - let Some(dest) = registration.url else { return Ok(None); }; @@ -38,7 +36,7 @@ pub async fn send_appservice_request( .try_into_http_request::( &dest, SendAccessToken::Appservice(hs_token), - &VERSIONS, + (), ) .map_err(|e| { err!(BadServerResponse( diff --git a/src/service/sending/data.rs b/src/service/sending/data.rs index a6bcc2b2d..0d0827d15 100644 --- a/src/service/sending/data.rs +++ b/src/service/sending/data.rs @@ -245,7 +245,7 @@ fn parse_servercurrentevent(key: &[u8], value: &[u8]) -> Result<(Destination, Se })?; ( - Destination::Federation(OwnedServerName::parse(&server).map_err(|_| { + Destination::Federation(ServerName::parse(&server).map_err(|_| { Error::bad_database("Invalid server string in server_currenttransaction") })?), if value.is_empty() { diff --git a/src/service/sending/mod.rs b/src/service/sending/mod.rs index 7997d5327..e2d427db4 100644 --- a/src/service/sending/mod.rs +++ b/src/service/sending/mod.rs @@ -19,7 +19,7 @@ warn, }; use futures::{FutureExt, Stream, StreamExt}; -use ruma::{RoomId, ServerName, UserId, api::OutgoingRequest}; +use ruma::{OwnedServerName, RoomId, ServerName, UserId, api::{OutgoingRequest, auth_scheme::NoAuthentication, federation::authentication::ServerSignatures, path_builder::PathBuilder}}; use tokio::{task, task::JoinSet}; use self::data::Data; @@ -28,8 +28,8 @@ sender::{EDU_LIMIT, PDU_LIMIT}, }; use crate::{ - Dep, account_data, client, federation, globals, presence, pusher, rooms, - rooms::timeline::RawPduId, users, + Dep, account_data, client, federation::{self, FederationPathBuilderInput}, globals, presence, pusher, rooms::{self, timeline::RawPduId}, + users, }; pub struct Service { @@ -187,9 +187,9 @@ pub async fn send_pdu_room(&self, room_id: &RoomId, pdu_id: &RawPduId) -> Result } #[tracing::instrument(skip(self, servers, pdu_id), level = "debug")] - pub async fn send_pdu_servers<'a, S>(&self, servers: S, pdu_id: &RawPduId) -> Result + pub async fn send_pdu_servers(&self, servers: S, pdu_id: &RawPduId) -> Result where - S: Stream + Send + 'a, + S: Stream + Send, { let requests = servers .map(|server| { @@ -233,14 +233,14 @@ pub async fn send_edu_room(&self, room_id: &RoomId, serialized: EduBuf) -> Resul } #[tracing::instrument(skip(self, servers, serialized), level = "debug")] - pub async fn send_edu_servers<'a, S>(&self, servers: S, serialized: EduBuf) -> Result + pub async fn send_edu_servers(&self, servers: S, serialized: EduBuf) -> Result where - S: Stream + Send + 'a, + S: Stream + Send, { let requests = servers .map(|server| { ( - Destination::Federation(server.to_owned()), + Destination::Federation(server), SendingEvent::Edu(serialized.clone()), ) }) @@ -269,12 +269,11 @@ pub async fn flush_room(&self, room_id: &RoomId) -> Result<()> { } #[tracing::instrument(skip(self, servers), level = "debug")] - pub async fn flush_servers<'a, S>(&self, servers: S) -> Result<()> + pub async fn flush_servers(&self, servers: S) -> Result<()> where - S: Stream + Send + 'a, + S: Stream + Send, { servers - .map(ToOwned::to_owned) .map(Destination::Federation) .map(Ok) .ready_try_for_each(|dest| { @@ -289,26 +288,26 @@ pub async fn flush_servers<'a, S>(&self, servers: S) -> Result<()> /// Sends a request to a federation server #[inline] - pub async fn send_federation_request( + pub async fn send_federation_request<'i, T>( &self, dest: &ServerName, request: T, ) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, { self.services.federation.execute(dest, request).await } /// Like send_federation_request() but with a very large timeout #[inline] - pub async fn send_synapse_request( + pub async fn send_synapse_request<'i, T>( &self, dest: &ServerName, request: T, ) -> Result where - T: OutgoingRequest + Debug + Send, + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, { self.services .federation @@ -316,6 +315,22 @@ pub async fn send_synapse_request( .await } + /// Send an unauthenticated federation request with no X-Matrix header. + #[inline] + pub async fn send_unauthenticated_request<'i, T>( + &self, + dest: &ServerName, + request: T, + ) -> Result + where + T: OutgoingRequest::: FederationPathBuilderInput>> + Debug + Send, + { + self.services + .federation + .execute_unauthenticated(dest, request) + .await + } + /// Clean up queued sending event data /// /// Used after we remove an appservice registration or a user deletes a push diff --git a/src/service/sending/sender.rs b/src/service/sending/sender.rs index e7f9544c5..375f49a92 100644 --- a/src/service/sending/sender.rs +++ b/src/service/sending/sender.rs @@ -421,7 +421,7 @@ async fn select_edus_device_changes( let keys_changed = self .services .users - .room_keys_changed(room_id, Some(since.0), None) + .room_keys_changed(&room_id, Some(since.0), None) .ready_filter(|(user_id, _)| self.services.globals.user_is_local(user_id)); pin_mut!(keys_changed); @@ -431,21 +431,13 @@ async fn select_edus_device_changes( } max_edu_count.fetch_max(count, Ordering::Relaxed); - if !device_list_changes.insert(user_id.into()) { + if !device_list_changes.insert(user_id.clone()) { continue; } // Empty prev id forces synapse to resync; because synapse resyncs, // we can just insert placeholder data - let edu = Edu::DeviceListUpdate(DeviceListUpdateContent { - user_id: user_id.into(), - device_id: device_id!("placeholder").to_owned(), - device_display_name: Some("Placeholder".to_owned()), - stream_id: uint!(1), - prev_id: Vec::new(), - deleted: None, - keys: None, - }); + let edu = Edu::DeviceListUpdate(DeviceListUpdateContent::new(user_id, device_id!("placeholder").to_owned(), uint!(1))); let mut buf = EduBuf::new(); serde_json::to_writer(&mut buf, &edu) @@ -478,7 +470,6 @@ async fn select_edus_receipts( .services .state_cache .server_rooms(server_name) - .map(ToOwned::to_owned) .broad_filter_map(|room_id| async move { let receipt_map = self .select_edus_receipts_room(&room_id, since, max_edu_count, &mut num) @@ -497,7 +488,7 @@ async fn select_edus_receipts( return None; } - let receipt_content = Edu::Receipt(ReceiptContent { receipts }); + let receipt_content = Edu::Receipt(ReceiptContent::new(receipts)); let mut buf = EduBuf::new(); serde_json::to_writer(&mut buf, &receipt_content) @@ -555,10 +546,7 @@ async fn select_edus_receipts_room( .remove(&user_id) .expect("our read receipts always have the user here"); - let receipt_data = ReceiptData { - data: receipt, - event_ids: vec![event_id.clone()], - }; + let receipt_data = ReceiptData::new(receipt, vec![event_id.clone()]); if read.insert(user_id, receipt_data).is_none() { *num = num.saturating_add(1); @@ -568,7 +556,7 @@ async fn select_edus_receipts_room( } } - ReceiptMap { read } + ReceiptMap::new(read) } /// Look for presence @@ -616,16 +604,9 @@ async fn select_edus_presence( continue; }; - let update = PresenceUpdate { - user_id: user_id.into(), - presence: presence_event.content.presence, - currently_active: presence_event.content.currently_active.unwrap_or(false), - status_msg: presence_event.content.status_msg, - last_active_ago: presence_event - .content - .last_active_ago - .unwrap_or_else(|| uint!(0)), - }; + let mut update = PresenceUpdate::new(user_id.to_owned(), presence_event.content.presence, presence_event.content.last_active_ago.unwrap_or_else(|| uint!(0))); + update.currently_active = presence_event.content.currently_active.unwrap_or_default(); + update.status_msg = presence_event.content.status_msg; presence_updates.insert(user_id.into(), update); if presence_updates.len() >= SELECT_PRESENCE_LIMIT { @@ -637,9 +618,7 @@ async fn select_edus_presence( return None; } - let presence_content = Edu::Presence(PresenceContent { - push: presence_updates.into_values().collect(), - }); + let presence_content = Edu::Presence(PresenceContent::new(presence_updates.into_values().collect())); let mut buf = EduBuf::new(); serde_json::to_writer(&mut buf, &presence_content) @@ -685,7 +664,7 @@ async fn send_events_dest_appservice( .filter(|event| matches!(event, SendingEvent::Pdu(_))) .count(), ); - let mut edu_jsons: Vec = Vec::with_capacity( + let mut edu_jsons: Vec> = Vec::with_capacity( events .iter() .filter(|event| matches!(event, SendingEvent::Edu(_))) @@ -701,7 +680,7 @@ async fn send_events_dest_appservice( | SendingEvent::Edu(edu) => if appservice.receive_ephemeral { if let Ok(edu) = serde_json::from_slice(edu) { - edu_jsons.push(edu); + edu_jsons.push(Raw::from_json(edu)); } }, | SendingEvent::Flush => {}, // flush only; no new content @@ -719,15 +698,14 @@ async fn send_events_dest_appservice( //debug_assert!(pdu_jsons.len() + edu_jsons.len() > 0, "sending empty // transaction"); + let mut request = ruma::api::appservice::event::push_events::v1::Request::new(txn_id.into(), pdu_jsons); + request.ephemeral = edu_jsons; + request.to_device = Vec::new(); // TODO + match self .send_appservice_request( appservice, - ruma::api::appservice::event::push_events::v1::Request { - events: pdu_jsons, - txn_id: txn_id.into(), - ephemeral: edu_jsons, - to_device: Vec::new(), // TODO - }, + request, ) .await { @@ -850,18 +828,14 @@ async fn send_events_dest_federation( let txn_hash = calculate_hash(preimage); let txn_id = &*URL_SAFE_NO_PAD.encode(txn_hash); - let request = send_transaction_message::v1::Request { - transaction_id: txn_id.into(), - origin: self.server.name.clone(), - origin_server_ts: MilliSecondsSinceUnixEpoch::now(), - pdus, - edus, - }; + let mut request = send_transaction_message::v1::Request::new(txn_id.into(), self.server.name.clone(), MilliSecondsSinceUnixEpoch::now()); + request.pdus = pdus; + request.edus = edus; let result = self .services .federation - .execute_on(&self.services.client.sender, &server, request) + .execute_signed(&self.services.client.sender, &server, request) .await; for (event_id, result) in result.iter().flat_map(|resp| resp.pdus.iter()) { @@ -896,7 +870,7 @@ pub async fn convert_to_outgoing_federation_event( .get("room_id") .and_then(|val| RoomId::parse(val.as_str()?).ok()) { - match self.services.state.get_room_version(room_id).await { + match self.services.state.get_room_version(&room_id).await { | Ok(room_version_id) => match room_version_id { | RoomVersionId::V1 | RoomVersionId::V2 => {}, | _ => _ = pdu_json.remove("event_id"), diff --git a/src/service/server_keys/get.rs b/src/service/server_keys/get.rs index 1c750a435..ac8aca0e4 100644 --- a/src/service/server_keys/get.rs +++ b/src/service/server_keys/get.rs @@ -6,6 +6,8 @@ api::federation::discovery::VerifyKey, }; +use crate::server_keys::util::required_keys; + use super::{PubKeyMap, PubKeys, extract_key}; #[implement(super::Service)] @@ -14,8 +16,6 @@ pub async fn get_event_keys( object: &CanonicalJsonObject, version: &RoomVersionId, ) -> Result { - use ruma::signatures::required_keys; - let required = match required_keys(object, version) { | Ok(required) => required, | Err(e) => { diff --git a/src/service/server_keys/keypair.rs b/src/service/server_keys/keypair.rs index 259c37fb3..12eb2511e 100644 --- a/src/service/server_keys/keypair.rs +++ b/src/service/server_keys/keypair.rs @@ -12,9 +12,7 @@ pub(super) fn init(db: &Arc) -> Result<(Box, VerifyKey remove(db); })?; - let verify_key = VerifyKey { - key: Base64::new(keypair.public_key().to_vec()), - }; + let verify_key = VerifyKey::new(Base64::new(keypair.public_key().to_vec())); let id = format!("ed25519:{}", keypair.version()); let verify_keys: VerifyKeys = [(id.try_into()?, verify_key)].into(); diff --git a/src/service/server_keys/mod.rs b/src/service/server_keys/mod.rs index 07cb0b0d6..eb398ecbb 100644 --- a/src/service/server_keys/mod.rs +++ b/src/service/server_keys/mod.rs @@ -4,6 +4,7 @@ mod request; mod sign; mod verify; +mod util; use std::{collections::BTreeMap, sync::Arc, time::Duration}; @@ -22,7 +23,7 @@ }; use serde_json::value::RawValue as RawJsonValue; -use crate::{Dep, globals, sending}; +use crate::{Dep, globals, sending, server_keys::util::required_keys}; pub struct Service { keypair: Box, @@ -118,8 +119,6 @@ pub async fn required_keys_exist( object: &CanonicalJsonObject, version: &RoomVersionId, ) -> bool { - use ruma::signatures::required_keys; - trace!(?object, "Checking required keys exist"); let Ok(required_keys) = required_keys(object, version) else { debug_error!("Failed to determine required keys"); @@ -137,7 +136,7 @@ pub async fn required_keys_exist( #[implement(Service)] #[tracing::instrument(skip(self), level = "debug")] pub async fn verify_key_exists(&self, origin: &ServerName, key_id: &ServerSigningKeyId) -> bool { - type KeysMap<'a> = BTreeMap<&'a ServerSigningKeyId, &'a RawJsonValue>; + type KeysMap = BTreeMap>; let Ok(keys) = self .db @@ -150,13 +149,13 @@ pub async fn verify_key_exists(&self, origin: &ServerName, key_id: &ServerSignin return false; }; - if let Ok(Some(verify_keys)) = keys.get_field::>("verify_keys") { + if let Ok(Some(verify_keys)) = keys.get_field::("verify_keys") { if verify_keys.contains_key(key_id) { return true; } } - if let Ok(Some(old_verify_keys)) = keys.get_field::>("old_verify_keys") { + if let Ok(Some(old_verify_keys)) = keys.get_field::("old_verify_keys") { if old_verify_keys.contains_key(key_id) { return true; } diff --git a/src/service/server_keys/request.rs b/src/service/server_keys/request.rs index d9907616e..c8823fdc7 100644 --- a/src/service/server_keys/request.rs +++ b/src/service/server_keys/request.rs @@ -23,9 +23,8 @@ pub(super) async fn batch_notary_request<'a, S, K>( use get_remote_server_keys_batch::v2::Request; type RumaBatch = BTreeMap>; - let criteria = QueryCriteria { - minimum_valid_until_ts: Some(self.minimum_valid_ts()), - }; + let mut criteria = QueryCriteria::new(); + criteria.minimum_valid_until_ts = Some(self.minimum_valid_ts()); let mut server_keys = batch.fold(RumaBatch::new(), |mut batch, (server, key_ids)| { batch @@ -46,9 +45,7 @@ pub(super) async fn batch_notary_request<'a, S, K>( .next_back() .cloned() { - let request = Request { - server_keys: server_keys.split_off(&batch), - }; + let request = Request::new(server_keys.split_off(&batch)); debug!( ?notary, @@ -61,7 +58,7 @@ pub(super) async fn batch_notary_request<'a, S, K>( let response = self .services .sending - .send_synapse_request(notary, request) + .send_unauthenticated_request(notary, request) .await? .server_keys .into_iter() @@ -82,15 +79,12 @@ pub async fn notary_request( ) -> Result + Clone + Debug + Send + use<>> { use get_remote_server_keys::v2::Request; - let request = Request { - server_name: target.into(), - minimum_valid_until_ts: self.minimum_valid_ts(), - }; + let request = Request::new(target.into(), self.minimum_valid_ts()); let response = self .services .sending - .send_federation_request(notary, request) + .send_unauthenticated_request(notary, request) .await? .server_keys .into_iter() @@ -107,7 +101,7 @@ pub async fn server_request(&self, target: &ServerName) -> Result Result { + let Some(CanonicalJsonValue::String(raw_type)) = object.get("type") else { + return Err(JsonError::NotOfType { target: "type".to_owned(), of_type: JsonType::String }.into()); + }; + + if raw_type != "m.room.member" { + return Ok(false); + } + + let Some(CanonicalJsonValue::Object(content)) = object.get("content") else { + return Err(JsonError::NotOfType { target: "content".to_owned(), of_type: JsonType::Object }.into()); + }; + + let Some(CanonicalJsonValue::String(membership)) = content.get("membership") else { + return Err(JsonError::NotOfType { target: "membership".to_owned(), of_type: JsonType::String }.into()); + }; + + if membership != "invite" { + return Ok(false); + } + + match content.get("third_party_invite") { + Some(CanonicalJsonValue::Object(_)) => Ok(true), + None => Ok(false), + _ => Err(JsonError::NotOfType { target: "third_party_invite".to_owned(), of_type: JsonType::Object }.into()), + } +} + +/// Extracts the server names to check signatures for given event. +/// +/// Respects the rules for [validating signatures on received events] for populating the result: +/// +/// - Add the server of the sender, except if it's an invite event that results from a third-party +/// invite. +/// - For room versions 1 and 2, add the server of the `event_id`. +/// - For room versions that support restricted join rules, if it's a join event with a +/// `join_authorised_via_users_server`, add the server of that user. +/// +/// [validating signatures on received events]: https://spec.matrix.org/latest/server-server-api/#validating-hashes-and-signatures-on-received-events +pub fn servers_to_check_signatures( + object: &CanonicalJsonObject, + version: &RoomVersionId, +) -> Result, Error> { + let mut servers_to_check = BTreeSet::new(); + + if !is_invite_via_third_party_id(object)? { + match object.get("sender") { + Some(CanonicalJsonValue::String(raw_sender)) => { + let user_id = <&UserId>::try_from(raw_sender.as_str()) + .map_err(|e| Error::from(ParseError::UserId(e)))?; + + servers_to_check.insert(user_id.server_name().to_owned()); + } + _ => return Err(JsonError::NotOfType { target: "sender".to_owned(), of_type: JsonType::String }.into()), + }; + } + + match version { + RoomVersionId::V1 | RoomVersionId::V2 => match object.get("event_id") { + Some(CanonicalJsonValue::String(raw_event_id)) => { + let event_id: OwnedEventId = + raw_event_id.parse().map_err(|e| Error::from(ParseError::EventId(e)))?; + + let server_name = event_id + .server_name() + .ok_or_else(|| ParseError::ServerNameFromEventId(event_id.to_owned()))? + .to_owned(); + + servers_to_check.insert(server_name); + } + _ => { + return Err(JsonError::JsonFieldMissingFromObject("event_id".to_owned()).into()); + } + }, + RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 => {} + // TODO: And for all future versions that have join_authorised_via_users_server + RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 | RoomVersionId::V12 => { + if let Some(authorized_user) = object + .get("content") + .and_then(|c| c.as_object()) + .and_then(|c| c.get("join_authorised_via_users_server")) + { + let authorized_user = authorized_user.as_str().ok_or_else(|| -> Error { + JsonError::NotOfType { target: "join_authorised_via_users_server".to_owned(), of_type: JsonType::String }.into() + })?; + let authorized_user = <&UserId>::try_from(authorized_user) + .map_err(|e| Error::from(ParseError::UserId(e)))?; + + servers_to_check.insert(authorized_user.server_name().to_owned()); + } + } + _ => unimplemented!(), + } + + Ok(servers_to_check) +} + +/// Extracts the server names and key ids to check signatures for given event. +pub fn required_keys( + object: &CanonicalJsonObject, + version: &RoomVersionId, +) -> Result>, Error> { + use CanonicalJsonValue::Object; + let mut map = BTreeMap::>::new(); + let Some(Object(signatures)) = object.get("signatures") else { + return Ok(map); + }; + + for server in servers_to_check_signatures(object, version)? { + let Some(Object(set)) = signatures.get(server.as_str()) else { + continue; + }; + + let entry = map.entry(server.clone()).or_default(); + set.iter() + .map(|(k, _)| k.clone()) + .map(TryInto::try_into) + .filter_map(Result::ok) + .for_each(|key_id| entry.push(key_id)); + } + + Ok(map) +} \ No newline at end of file diff --git a/src/service/server_keys/verify.rs b/src/service/server_keys/verify.rs index 1be384055..f7e35e267 100644 --- a/src/service/server_keys/verify.rs +++ b/src/service/server_keys/verify.rs @@ -63,7 +63,7 @@ pub async fn verify_event( ) -> Result { let room_version = room_version.unwrap_or(&RoomVersionId::V12); let keys = self.get_event_keys(event, room_version).await?; - ruma::signatures::verify_event(&keys, event, room_version).map_err(Into::into) + ruma::signatures::verify_event(&keys, event, &room_version.rules().unwrap()).map_err(Into::into) } #[implement(super::Service)] @@ -74,5 +74,5 @@ pub async fn verify_json( ) -> Result { let room_version = room_version.unwrap_or(&RoomVersionId::V12); let keys = self.get_event_keys(event, room_version).await?; - ruma::signatures::verify_json(&keys, event.clone()).map_err(Into::into) + ruma::signatures::verify_json(&keys, event).map_err(Into::into) } diff --git a/src/service/sync/mod.rs b/src/service/sync/mod.rs index 6ac579f47..d96242d2f 100644 --- a/src/service/sync/mod.rs +++ b/src/service/sync/mod.rs @@ -10,8 +10,6 @@ use ruma::{ OwnedDeviceId, OwnedRoomId, OwnedUserId, api::client::sync::sync_events::{ - self, - v4::{ExtensionsConfig, SyncRequestList}, v5, }, }; @@ -47,11 +45,11 @@ struct Services { } struct SlidingSyncCache { - lists: BTreeMap, - subscriptions: BTreeMap, + lists: BTreeMap, + subscriptions: BTreeMap, // For every room, the roomsince number known_rooms: BTreeMap>, - extensions: ExtensionsConfig, + extensions: v5::request::Extensions, } #[derive(Default)] @@ -136,7 +134,6 @@ pub fn update_snake_sync_request_with_cache( &mut list.room_details.required_state, &cached_list.room_details.required_state, ); - some_or_sticky(&mut list.include_heroes, cached_list.include_heroes); match (&mut list.filters, cached_list.filters.clone()) { | (Some(filters), Some(cached_filters)) => { @@ -219,162 +216,6 @@ pub fn update_snake_sync_request_with_cache( cached.known_rooms.clone() } - pub fn update_sync_request_with_cache( - &self, - key: &SnakeConnectionsKey, - request: &mut sync_events::v4::Request, - ) -> BTreeMap> { - let Some(conn_id) = request.conn_id.clone() else { - return BTreeMap::new(); - }; - - let key = into_db_key(key.0.clone(), key.1.clone(), conn_id); - let mut cache = self.connections.lock(); - let cached = Arc::clone(cache.entry(key).or_insert_with(|| { - Arc::new(SyncMutex::new(SlidingSyncCache { - lists: BTreeMap::new(), - subscriptions: BTreeMap::new(), - known_rooms: BTreeMap::new(), - extensions: ExtensionsConfig::default(), - })) - })); - let cached = &mut cached.lock(); - drop(cache); - - for (list_id, list) in &mut request.lists { - if let Some(cached_list) = cached.lists.get(list_id) { - list_or_sticky(&mut list.sort, &cached_list.sort); - list_or_sticky( - &mut list.room_details.required_state, - &cached_list.room_details.required_state, - ); - some_or_sticky( - &mut list.room_details.timeline_limit, - cached_list.room_details.timeline_limit, - ); - some_or_sticky( - &mut list.include_old_rooms, - cached_list.include_old_rooms.clone(), - ); - match (&mut list.filters, cached_list.filters.clone()) { - | (Some(filter), Some(cached_filter)) => { - some_or_sticky(&mut filter.is_dm, cached_filter.is_dm); - list_or_sticky(&mut filter.spaces, &cached_filter.spaces); - some_or_sticky(&mut filter.is_encrypted, cached_filter.is_encrypted); - some_or_sticky(&mut filter.is_invite, cached_filter.is_invite); - list_or_sticky(&mut filter.room_types, &cached_filter.room_types); - // Should be made possible to change - list_or_sticky(&mut filter.not_room_types, &cached_filter.not_room_types); - some_or_sticky(&mut filter.room_name_like, cached_filter.room_name_like); - list_or_sticky(&mut filter.tags, &cached_filter.tags); - list_or_sticky(&mut filter.not_tags, &cached_filter.not_tags); - }, - | (_, Some(cached_filters)) => list.filters = Some(cached_filters), - | (Some(list_filters), _) => list.filters = Some(list_filters.clone()), - | (..) => {}, - } - list_or_sticky(&mut list.bump_event_types, &cached_list.bump_event_types); - } - cached.lists.insert(list_id.clone(), list.clone()); - } - - cached - .subscriptions - .extend(request.room_subscriptions.clone()); - request - .room_subscriptions - .extend(cached.subscriptions.clone()); - - request.extensions.e2ee.enabled = request - .extensions - .e2ee - .enabled - .or(cached.extensions.e2ee.enabled); - - request.extensions.to_device.enabled = request - .extensions - .to_device - .enabled - .or(cached.extensions.to_device.enabled); - - request.extensions.account_data.enabled = request - .extensions - .account_data - .enabled - .or(cached.extensions.account_data.enabled); - request.extensions.account_data.lists = request - .extensions - .account_data - .lists - .clone() - .or_else(|| cached.extensions.account_data.lists.clone()); - request.extensions.account_data.rooms = request - .extensions - .account_data - .rooms - .clone() - .or_else(|| cached.extensions.account_data.rooms.clone()); - - cached.extensions = request.extensions.clone(); - - cached.known_rooms.clone() - } - - pub fn update_sync_subscriptions( - &self, - key: &DbConnectionsKey, - subscriptions: BTreeMap, - ) { - let mut cache = self.connections.lock(); - let cached = Arc::clone(cache.entry(key.clone()).or_insert_with(|| { - Arc::new(SyncMutex::new(SlidingSyncCache { - lists: BTreeMap::new(), - subscriptions: BTreeMap::new(), - known_rooms: BTreeMap::new(), - extensions: ExtensionsConfig::default(), - })) - })); - let cached = &mut cached.lock(); - drop(cache); - - cached.subscriptions = subscriptions; - } - - pub fn update_sync_known_rooms( - &self, - key: &DbConnectionsKey, - list_id: String, - new_cached_rooms: BTreeSet, - globalsince: u64, - ) { - let mut cache = self.connections.lock(); - let cached = Arc::clone(cache.entry(key.clone()).or_insert_with(|| { - Arc::new(SyncMutex::new(SlidingSyncCache { - lists: BTreeMap::new(), - subscriptions: BTreeMap::new(), - known_rooms: BTreeMap::new(), - extensions: ExtensionsConfig::default(), - })) - })); - let cached = &mut cached.lock(); - drop(cache); - - for (room_id, lastsince) in cached - .known_rooms - .entry(list_id.clone()) - .or_default() - .iter_mut() - { - if !new_cached_rooms.contains(room_id) { - *lastsince = 0; - } - } - let list = cached.known_rooms.entry(list_id).or_default(); - for room_id in new_cached_rooms { - list.insert(room_id, globalsince); - } - } - pub fn update_snake_sync_known_rooms( &self, key: &SnakeConnectionsKey, diff --git a/src/service/sync/watch.rs b/src/service/sync/watch.rs index 969814723..f6dd77271 100644 --- a/src/service/sync/watch.rs +++ b/src/service/sync/watch.rs @@ -38,7 +38,7 @@ pub async fn watch(&self, user_id: &UserId, device_id: &DeviceId) -> Result { pin_mut!(rooms_joined); while let Some(room_id) = rooms_joined.next().await { - let Ok(short_roomid) = self.services.short.get_shortroomid(room_id).await else { + let Ok(short_roomid) = self.services.short.get_shortroomid(&room_id).await else { continue; }; diff --git a/src/service/users/mod.rs b/src/service/users/mod.rs index 5f2787850..771def1da 100644 --- a/src/service/users/mod.rs +++ b/src/service/users/mod.rs @@ -24,11 +24,11 @@ events::{ AnyToDeviceEvent, GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent, - invite_permission_config::{FilterLevel, InvitePermissionConfigEvent}, }, serde::Raw, uint, }; +use ruminuwuity::invite_permission_config::{FilterLevel, InvitePermissionConfigEvent}; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -206,7 +206,7 @@ pub async fn create( pub async fn deactivate_account(&self, user_id: &UserId) -> Result<()> { // Remove all associated devices self.all_device_ids(user_id) - .for_each(|device_id| self.remove_device(user_id, device_id)) + .for_each(async |device_id| self.remove_device(user_id, &device_id).await) .await; // Set the password to "" to indicate a deactivated account. Hashes will never @@ -342,15 +342,8 @@ pub async fn find_from_token(&self, token: &str) -> Result<(OwnedUserId, OwnedDe self.db.token_userdeviceid.get(token).await.deserialized() } - /// Returns an iterator over all users on this homeserver (offered for - /// compatibility) - #[allow(clippy::iter_without_into_iter, clippy::iter_not_returning_iterator)] - pub fn iter(&self) -> impl Stream + Send + '_ { - self.stream().map(ToOwned::to_owned) - } - /// Returns an iterator over all users on this homeserver. - pub fn stream(&self) -> impl Stream + Send { + pub fn stream(&self) -> impl Stream + Send { self.db.userid_password.keys().ignore_err() } @@ -358,12 +351,12 @@ pub fn stream(&self) -> impl Stream + Send { /// /// A user account is considered `local` if the length of it's password is /// greater then zero. - pub fn list_local_users(&self) -> impl Stream + Send + '_ { + pub fn list_local_users(&self) -> impl Stream + Send + '_ { self.db .userid_password .stream() .ignore_err() - .ready_filter_map(|(u, p): (&UserId, &[u8])| (!p.is_empty()).then_some(u)) + .ready_filter_map(|(u, p): (OwnedUserId, &[u8])| (!p.is_empty()).then_some(u)) } /// Returns the origin of the user (password/LDAP/...). @@ -470,15 +463,13 @@ pub async fn create_device( } let key = (user_id, device_id); - let val = Device { - device_id: device_id.into(), - display_name: initial_device_display_name, - last_seen_ip: client_ip, - last_seen_ts: Some(MilliSecondsSinceUnixEpoch::now()), - }; + let mut device = Device::new(device_id.into()); + device.display_name = initial_device_display_name; + device.last_seen_ip = client_ip; + device.last_seen_ts = Some(MilliSecondsSinceUnixEpoch::now()); increment(&self.db.userid_devicelistversion, user_id.as_bytes()); - self.db.userdeviceid_metadata.put(key, Json(val)); + self.db.userdeviceid_metadata.put(key, Json(device)); self.set_token(user_id, device_id, token).await } @@ -518,13 +509,13 @@ pub async fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) { pub fn all_device_ids<'a>( &'a self, user_id: &'a UserId, - ) -> impl Stream + Send + 'a { + ) -> impl Stream + Send + 'a { let prefix = (user_id, Interfix); self.db .userdeviceid_metadata .keys_prefix(&prefix) .ignore_err() - .map(|(_, device_id): (Ignore, &DeviceId)| device_id) + .map(|(_, device_id): (Ignore, OwnedDeviceId)| device_id) } pub async fn get_token(&self, user_id: &UserId, device_id: &DeviceId) -> Result { @@ -866,7 +857,7 @@ pub fn keys_changed<'a>( user_id: &'a UserId, from: Option, to: Option, - ) -> impl Stream + Send + 'a { + ) -> impl Stream + Send + 'a { self.keys_changed_user_or_room(user_id.as_str(), from, to) .map(|(user_id, ..)| user_id) } @@ -877,7 +868,7 @@ pub fn room_keys_changed<'a>( room_id: &'a RoomId, from: Option, to: Option, - ) -> impl Stream + Send + 'a { + ) -> impl Stream + Send + 'a { self.keys_changed_user_or_room(room_id.as_str(), from, to) } @@ -886,8 +877,8 @@ fn keys_changed_user_or_room<'a>( user_or_room_id: &'a str, from: Option, to: Option, - ) -> impl Stream + Send + 'a { - type KeyVal<'a> = ((&'a str, u64), &'a UserId); + ) -> impl Stream + Send + 'a { + type KeyVal<'a> = ((&'a str, u64), OwnedUserId); let from = from.unwrap_or(0); let to = to.unwrap_or(u64::MAX); @@ -909,7 +900,13 @@ pub async fn mark_device_key_update(&self, user_id: &UserId) { .state_cache .rooms_joined(user_id) // Don't send key updates to unencrypted rooms - .filter(|room_id| self.services.state_accessor.is_encrypted_room(room_id)) + .filter_map(async |room_id| { + if self.services.state_accessor.is_encrypted_room(&room_id).await { + Some(room_id) + } else { + None + } + }) .ready_for_each(|room_id| { let key = (room_id, count); self.db.keychangeid_userid.put_raw(key, user_id); @@ -1013,7 +1010,7 @@ pub fn get_to_device_events<'a>( since: Option, to: Option, ) -> impl Stream)> + Send + 'a { - type Key<'a> = (&'a UserId, &'a DeviceId, u64); + type Key = (OwnedUserId, OwnedDeviceId, u64); let from = (user_id, device_id, since.map_or(0, |since| since.saturating_add(1))); @@ -1021,7 +1018,7 @@ pub fn get_to_device_events<'a>( .todeviceid_events .stream_from(&from) .ignore_err() - .ready_take_while(move |((user_id_, device_id_, count), _): &(Key<'_>, _)| { + .ready_take_while(move |((user_id_, device_id_, count), _): &(Key, _)| { user_id == *user_id_ && device_id == *device_id_ && to.is_none_or(|to| *count <= to) @@ -1037,7 +1034,7 @@ pub async fn remove_to_device_events( ) where Until: Into> + Send, { - type Key<'a> = (&'a UserId, &'a DeviceId, u64); + type Key = (OwnedUserId, OwnedDeviceId, u64); let until = until.into().unwrap_or(u64::MAX); let from = (user_id, device_id, until); @@ -1045,10 +1042,10 @@ pub async fn remove_to_device_events( .todeviceid_events .rev_keys_from(&from) .ignore_err() - .ready_take_while(move |(user_id_, device_id_, _): &Key<'_>| { + .ready_take_while(move |(user_id_, device_id_, _): &Key| { user_id == *user_id_ && device_id == *device_id_ }) - .ready_for_each(|key: Key<'_>| { + .ready_for_each(|key: Key| { self.db.todeviceid_events.del(key); }) .await;