refactor: Ruma upstreaming, half-baked edition

Co-authored-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
Ginger
2026-03-29 12:25:42 -04:00
parent b80b9a7950
commit 268ed38b43
142 changed files with 2737 additions and 2289 deletions
Generated
+91 -114
View File
@@ -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"
+15 -20
View File
@@ -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
+1
View File
@@ -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
+2 -2
View File
@@ -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();
+3 -2
View File
@@ -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
+2 -2
View File
@@ -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`
+3 -9
View File
@@ -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
+3 -3
View File
@@ -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)"
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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()))
}
+9 -9
View File
@@ -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(
+1 -1
View File
@@ -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))
}
+3 -6
View File
@@ -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`
+8 -13
View File
@@ -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
+4 -8
View File
@@ -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(),
+1 -1
View File
@@ -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,
+12 -13
View File
@@ -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,
+43 -38
View File
@@ -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
+27 -36
View File
@@ -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::<Vec<_>>()
.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;
+9 -10
View File
@@ -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,
+51 -52
View File
@@ -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::<Vec<_>>()
.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::<CanonicalJsonObject>(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::<CanonicalJsonObject>(raw_value.get())
})
.filter_map(Result::ok);
let mut state_map: HashMap<u64, OwnedEventId> = 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;
+12 -22
View File
@@ -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<S: ::std::hash::BuildHasher>(
.rooms
.state_cache
.servers_invite_via(room_id)
.map(ToOwned::to_owned)
.collect::<HashSet<OwnedServerName>>()
.await,
);
@@ -260,7 +257,7 @@ pub async fn remote_leave_room<S: ::std::hash::BuildHasher>(
.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<S: ::std::hash::BuildHasher>(
.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<S: ::std::hash::BuildHasher>(
.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?;
+25 -51
View File
@@ -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: Event>(
pdu: Pdu,
for_membership: Option<&MembershipEventFilter>,
not_membership: Option<&MembershipEventFilter>,
membership_state_filter: Option<&MembershipState>,
not_membership_state_filter: Option<&MembershipState>,
) -> Option<impl Event> {
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::<RoomMemberEventContent>().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);
}
+4 -5
View File
@@ -47,15 +47,14 @@ pub(crate) async fn joined_rooms_route(
State(services): State<crate::State>,
body: Ruma<joined_rooms::v3::Request>,
) -> Result<joined_rooms::v3::Response> {
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
+9 -10
View File
@@ -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(), &current_member_content),
sender_user,
Some(&body.room_id),
&state_lock,
+1 -1
View File
@@ -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},
+4 -5
View File
@@ -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))
}
+20 -93
View File
@@ -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<OwnedRoomId> = 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<OwnedRoomId> = 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::<CanonicalJsonObject>()
.deserialize_as_unchecked::<CanonicalJsonObject>()
.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::<CanonicalJsonObject>(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<OwnedUserId> = 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<OwnedRoomId> {
// 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",))
}
+4 -3
View File
@@ -44,15 +44,16 @@ impl<Err, Req, Fut, Fun, $($tx,)*> RumaHandler<($($tx,)* Ruma<Req>,)> for Fun
$( $tx: FromRequestParts<State> + Send + Sync + 'static, )*
{
fn add_routes(&'static self, router: Router<State>) -> Router<State> {
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<State>, path: &str) -> Router<State> {
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))
}
}
+4 -8
View File
@@ -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,
}
}
+5 -12
View File
@@ -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();
}
+1 -1
View File
@@ -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};
+1 -1
View File
@@ -67,7 +67,7 @@ fn matches_sender<E: Event>(event: &E, filter: &RoomEventFilter) -> bool {
}
fn matches_type<E: Event>(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;
+5 -2
View File
@@ -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<OwnedEventId> {
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)
+2 -2
View File
@@ -21,12 +21,12 @@ fn with_state_key(self, state_key: impl Into<StateKey>) -> (StateEventType, Stat
impl TypeExt for TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (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<StateKey>) -> (StateEventType, StateKey) {
(self.clone().into(), state_key.into())
(self.clone().to_string().into(), state_key.into())
}
}
+2 -1
View File
@@ -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};
+3 -3
View File
@@ -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<S, T>(state_key: S, content: &T) -> Self
where
T: EventContent<EventType = StateEventType>,
T: StateEventContent,
S: Into<StateKey>,
{
Self {
@@ -47,7 +47,7 @@ pub fn state<S, T>(state_key: S, content: &T) -> Self
pub fn timeline<T>(content: &T) -> Self
where
T: EventContent<EventType = MessageLikeEventType>,
T: MessageLikeEventContent,
{
Self {
event_type: content.event_type().into(),
+6 -2
View File
@@ -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");
+3 -3
View File
@@ -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(),
)
})
+56 -107
View File
@@ -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<Vec<(StateEventType, StateKey)>> {
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<E, F, Fut>(
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<E, F, Fut>(
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<E, F, Fut>(
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<E, F, Fut>(
// 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<E, F, Fut>(
}
// 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<E, F, Fut>(
},
| _ => {
// 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<E, F, Fut>(
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<E, F, Fut>(
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<E, F, Fut>(
// 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<E, F, Fut>(
}
fn is_creator<EV>(
v: &RoomVersion,
v: &RoomVersionRules,
c: &BTreeSet<OwnedUserId>,
ce: &EV,
user_id: &UserId,
@@ -627,9 +623,9 @@ fn is_creator<EV>(
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<EV>(
#[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)]
fn valid_membership_change<E>(
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::<RoomThirdPartyInviteEventContent>(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,
+15 -28
View File
@@ -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<OwnedEventId, Hasher>],
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<E, F, Fut>(
/// `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<OwnedEventId>,
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<StateKey>) -> (StateEventType, Stat
impl EventTypeExt for TimelineEventType {
fn with_state_key(self, state_key: impl Into<StateKey>) -> (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,
+18 -23
View File
@@ -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<IntRoomPowerLevelsEventContent> 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<RoomPowerLevelsEventContent> {
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<RoomPowerLevelsEventContent> {
fn deserialize_integer_power_levels(content: &str, auth_rules: &AuthorizationRules) -> Option<RoomPowerLevelsEventContent> {
match from_json_str::<IntRoomPowerLevelsEventContent>(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<PowerLevelsContentFields, Error> {
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<PowerLevelsContentInvite, Error> {
if room_version.integer_power_levels {
if room_version.authorization.integer_power_levels {
from_json_str::<IntPowerLevelsContentInvite>(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<PowerLevelsContentRedact, Error> {
if room_version.integer_power_levels {
if room_version.authorization.integer_power_levels {
from_json_str::<IntPowerLevelsContentRedact>(content).map(Into::into)
} else {
from_json_str(content)
-169
View File
@@ -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<Self> {
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}`"))),
})
}
}
@@ -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<Vec<(T, Int)>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Ord,
{
#[repr(transparent)]
struct IntWrap(Int);
impl<'de> Deserialize<'de> for IntWrap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_v1_powerlevel(deserializer).map(IntWrap)
}
}
struct IntMapVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> IntMapVisitor<T> {
fn new() -> Self {
Self { _phantom: PhantomData }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
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<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
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<Vec<(T, Int)>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Ord,
{
struct IntMapVisitor<T> {
_phantom: PhantomData<T>,
}
impl<T> IntMapVisitor<T> {
fn new() -> Self {
Self { _phantom: PhantomData }
}
}
impl<'de, T> Visitor<'de> for IntMapVisitor<T>
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<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
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())
}
+5 -8
View File
@@ -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();
+44
View File
@@ -0,0 +1,44 @@
use std::collections::BTreeMap;
pub fn versions() -> Vec<String> {
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<String, bool> {
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) */
])
}
+13 -13
View File
@@ -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 {
() => {};
}
}
+69 -111
View File
@@ -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::<u8>::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::<u8, BUFSIZE>::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<u64, 2>, &'a RoomId);
type Key = (OwnedUserId, ArrayVec<u64, 2>, 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::<u8>::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<OwnedUserId>) = (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<OwnedUserId>) =
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::<u8>::new();
aa.extend_from_slice(room_id.as_bytes());
aa.push(0xFF);
let bb: (&RoomId, Option<&UserId>) = (room_id, None);
let bb: (OwnedRoomId, Option<OwnedUserId>) = (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<OwnedUserId>) =
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::<u8>::new();
aa.push(0xFF);
aa.extend_from_slice(user_id.as_bytes());
let bb: (Option<&RoomId>, &UserId) = (None, user_id);
let bb: (Option<OwnedRoomId>, 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<OwnedRoomId>, 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::<u8>::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<OwnedRoomId>, 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<OwnedRoomId>, 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::<u8>::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<OwnedRoomId>, Option<OwnedUserId>) = (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<OwnedRoomId>, Option<OwnedUserId>) =
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<OwnedRoomId>, Option<OwnedUserId>) = (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<OwnedRoomId>, Option<OwnedUserId>) =
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::<u8>::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<Cow<'_, RoomId>>, Option<Cow<'_, EventId>>, Option<Cow<'_, UserId>>) =
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<OwnedRoomId>, Option<OwnedEventId>, Option<OwnedUserId>) = (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<OwnedRoomId>, Option<OwnedEventId>, Option<OwnedUserId>) =
de::from_slice(&bbs).expect("failed to deserialize tuple");
assert_eq!(None, cc.0);
+31
View File
@@ -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
@@ -0,0 +1 @@
pub mod rooms;
@@ -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<OwnedUserId>,
pub failed_kicked_users: Vec<OwnedUserId>,
pub local_aliases: Vec<OwnedRoomAliasId>
}
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<OwnedUserId>,
failed_kicked_users: Vec<OwnedUserId>,
local_aliases: Vec<OwnedRoomAliasId>,
) -> Self {
Self { kicked_users, failed_kicked_users, local_aliases }
}
}
}
@@ -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<OwnedRoomId>,
}
impl Request {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl Response {
#[must_use]
pub fn new(rooms: Vec<OwnedRoomId>) -> Self {
Self { rooms }
}
}
}
@@ -0,0 +1,2 @@
pub mod list;
pub mod ban;
+53
View File
@@ -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 }
}
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod continuwuity;
pub mod get_suspended;
pub mod set_suspended;
+55
View File
@@ -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 }
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod user_may_invite;
pub mod user_may_join_room;
@@ -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()
}
}
}
@@ -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()
}
}
}
+247
View File
@@ -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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
/// 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<String>,
}
impl InvitePermissionConfigEventContent {
/// Creates a new `InvitePermissionConfigEventContent` from six lists of globs.
#[must_use]
pub fn new(
enabled: bool,
allowed_users: Vec<String>,
ignored_users: Vec<String>,
blocked_users: Vec<String>,
allowed_servers: Vec<String>,
ignored_servers: Vec<String>,
blocked_servers: Vec<String>,
) -> 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<InvitePermissionConfigEventContent> = 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);
}
}
@@ -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()
}
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod user_may_invite;
pub mod user_may_join_room;
pub mod accept_make_join;
@@ -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()
}
}
}
@@ -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()
}
}
}
+7
View File
@@ -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;
+100
View File
@@ -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<String>,
/// The public key this policy server will sign with.
pub public_key: Option<String>
}
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<String> 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::<OriginalStateEvent<RoomPolicyEventContent>>(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())
);
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod policy_check;
pub mod policy_sign;
pub mod report_content;
pub mod event;
+60
View File
@@ -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<Box<RawJsonValue>>,
}
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 }
}
}
}
+55
View File
@@ -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<ServerSignatures>
}
impl Response {
/// Creates a new `Response` with the given recommendation.
#[must_use]
pub fn new(signatures: Option<ServerSignatures>) -> 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<RawJsonValue>,
}
impl Request {
/// Creates a new `Request` with the given event JSON
#[must_use]
pub fn new(pdu: Box<RawJsonValue>) -> Self {
Self { pdu }
}
}
}
+58
View File
@@ -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()
}
}
}
+1
View File
@@ -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
+12 -8
View File
@@ -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<AnyRoomAccountDataEvent>),
Global(Raw<AnyGlobalAccountDataEvent>),
}
pub struct Service {
services: Services,
db: Data,
@@ -132,7 +136,7 @@ pub fn changes_since<'a>(
since: Option<u64>,
to: Option<u64>,
) -> impl Stream<Item = AnyRawAccountDataEvent> + Send + 'a {
type Key<'a> = (Option<&'a RoomId>, &'a UserId, u64, Ignore);
type Key = (Option<OwnedRoomId>, 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 {
+18 -38
View File
@@ -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(())
}
+9 -15
View File
@@ -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,
+11 -15
View File
@@ -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
+3 -2
View File
@@ -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<T>(
request: T,
) -> Result<T::IncomingResponse>
where
T: ruma::api::OutgoingRequest + Debug + Send,
T: ruma::api::OutgoingRequest<Authentication = AppserviceToken, PathBuilder = VersionHistory> + Debug + Send,
{
sending::antispam::send_antispam_request(
&self.services.client.appservice,
+1 -3
View File
@@ -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?;
+75 -119
View File
@@ -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<T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
pub async fn execute<'i, T>(&self, dest: &ServerName, request: T) -> Result<T::IncomingResponse>
where
T: OutgoingRequest + Debug + Send,
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: 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<T>(
pub async fn execute_synapse<'i, T>(
&self,
dest: &ServerName,
request: T,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest + Debug + Send,
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: 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<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: 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<T::IncomingResponse>
where
T: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: 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<T>(
pub async fn execute_on<'i, T, PathBuilderInput>(
&self,
client: &Client,
dest: &ServerName,
request: T,
authentication: <T::Authentication as AuthScheme>::Input<'_>,
) -> Result<T::IncomingResponse>
where
T: OutgoingRequest + Send,
T: OutgoingRequest::<PathBuilder: PathBuilder<Input<'i> = 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<T>(
}
let actual = self.services.resolver.get_actual_dest(dest).await?;
let request = into_http_request::<T>(&actual, request)?;
let request = self.prepare(dest, request)?;
let request = Request::try_from(
request.try_into_http_request::<Vec<u8>>(
actual.string().as_str(),
authentication,
PathBuilderInput::create(),
)?
)?;
self.validate_url(request.url())?;
self.services.server.check_running()?;
self.perform::<T>(dest, &actual, request, client).await
}
@@ -98,17 +128,6 @@ async fn perform<T>(
}
}
#[implement(super::Service)]
fn prepare(&self, dest: &ServerName, mut request: http::Request<Vec<u8>>) -> Result<Request> {
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<Vec<u8>>, 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<T>(actual: &ActualDest, request: T) -> Result<http::Request<Vec<u8>>>
where
T: OutgoingRequest + Send,
{
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_11];
let http_request = request
.try_into_http_request::<Vec<u8>>(
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)
}
}
+1
View File
@@ -1,4 +1,5 @@
mod execute;
pub(crate) use execute::FederationPathBuilderInput;
use std::sync::Arc;
+1 -1
View File
@@ -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::<RateLimitState>());
count = count.saturating_add(1);
(count, bytes)
+5 -7
View File
@@ -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<String> {
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<BackupAlgorithm>)> {
type Key<'a> = (&'a UserId, &'a str);
type Key<'a> = (OwnedUserId, &'a str);
type KeyVal<'a> = (Key<'a>, Raw<BackupAlgorithm>);
let last_possible_key = (user_id, u64::MAX);
@@ -197,11 +195,11 @@ pub async fn get_all(
user_id: &UserId,
version: &str,
) -> BTreeMap<OwnedRoomId, RoomKeyBackup> {
type Key<'a> = (Ignore, Ignore, &'a RoomId, &'a str);
type Key<'a> = (Ignore, Ignore, OwnedRoomId, &'a str);
type KeyVal<'a> = (Key<'a>, Raw<KeyBackupData>);
let mut rooms = BTreeMap::<OwnedRoomId, RoomKeyBackup>::new();
let default = || RoomKeyBackup { sessions: BTreeMap::new() };
let default = || RoomKeyBackup::new(BTreeMap::new());
let prefix = (user_id, version, Interfix);
self.db
+5 -3
View File
@@ -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<Vec<u8>> {
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<OwnedMxcUr
self.mediaid_user
.stream()
.ignore_err()
.ready_filter_map(|(key, user): (&str, &UserId)| {
.ready_filter_map(|(key, user): (&str, OwnedUserId)| {
(user == user_id).then(|| key.into())
})
.collect()
+3 -2
View File
@@ -1,4 +1,5 @@
pub mod blurhash;
pub mod mxc;
mod data;
pub(super) mod migrations;
mod preview;
@@ -17,7 +18,7 @@
},
warn,
};
use ruma::{Mxc, OwnedMxcUri, UserId, http_headers::ContentDisposition};
use ruma::{OwnedMxcUri, UserId, http_headers::ContentDisposition};
use tokio::{
fs,
io::{AsyncReadExt, AsyncWriteExt, BufReader},
@@ -25,7 +26,7 @@
use self::data::{Data, Metadata};
pub use self::thumbnail::Dim;
use crate::{Dep, client, globals, moderation, sending};
use crate::{Dep, client, globals, media::mxc::Mxc, moderation, sending};
#[derive(Debug)]
pub struct FileMeta {
+54
View File
@@ -0,0 +1,54 @@
use std::fmt;
use ruma::{MxcUri, MxcUriError, OwnedMxcUri, ServerName};
use serde::{Serialize, Serializer};
/// A structured, valid MXC URI
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Mxc<'a> {
/// 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<Self, Self::Error> {
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<Self, Self::Error> {
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<Self, Self::Error> {
let s: &MxcUri = s.as_ref();
s.try_into()
}
}
impl Serialize for Mxc<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.to_string().as_str())
}
}
+2 -1
View File
@@ -130,7 +130,8 @@ pub async fn download_image(
) -> Result<UrlPreviewData> {
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();
+60 -62
View File
@@ -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<FileMeta> {
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<FileMeta> {
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<FileMeta> {
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<FileMeta> {
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<FileMeta> {
}
#[implement(super::Service)]
async fn federation_request<Request>(
async fn federation_request<'i, Request>(
&self,
mxc: &Mxc<'_>,
server: Option<&ServerName>,
request: Request,
) -> Result<Request::IncomingResponse>
where
Request: OutgoingRequest + Send + Debug,
Request: OutgoingRequest::<Authentication = ServerSignatures, PathBuilder: PathBuilder<Input<'i>: FederationPathBuilderInput>> + Debug + Send,
{
self.services
.sending
@@ -322,6 +308,22 @@ async fn federation_request<Request>(
.await
}
#[implement(super::Service)]
async fn federation_request_unauthenticated<'i, Request>(
&self,
mxc: &Mxc<'_>,
server: Option<&ServerName>,
request: Request,
) -> Result<Request::IncomingResponse>
where
Request: OutgoingRequest::<Authentication = NoAuthentication, PathBuilder: PathBuilder<Input<'i>: 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<media::get_content::v3::Response, Error> {
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(
+3 -1
View File
@@ -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.
+23 -25
View File
@@ -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::<Vec<_>>()
.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::<Vec<OwnedUserId>>()
.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::<Vec<OwnedUserId>>()
.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::<Vec<_>>()
.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<MAX_BYTES>;
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<Option<Pdu>>);
type Key<'a> = (&'a UserId, &'a RoomId);
type KeyVal = (Key, Raw<Option<Pdu>>);
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<Vec<AnyStrippedStateEvent>>);
type Key<'a> = (&'a UserId, &'a RoomId);
type KeyVal = (Key, Raw<Vec<AnyStrippedStateEvent>>);
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)| {
+8
View File
@@ -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<SupportedVersions> = LazyLock::new(|| {
SupportedVersions::from_parts(&versions(), &unstable_features())
});
+2 -3
View File
@@ -183,7 +183,6 @@ pub async fn unset_all_presence(&self) {
.services
.users
.list_local_users()
.map(ToOwned::to_owned)
.collect::<Vec<OwnedUserId>>()
.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;
+7 -8
View File
@@ -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,
}
}
}
+23 -39
View File
@@ -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<globals::Service>,
config: Dep<config::Service>,
client: Dep<client::Service>,
state: Dep<rooms::state::Service>,
state_accessor: Dep<rooms::state_accessor::Service>,
state_cache: Dep<rooms::state_cache::Service>,
users: Dep<users::Service>,
@@ -64,6 +58,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
globals: args.depend::<globals::Service>("globals"),
client: args.depend::<client::Service>("client"),
config: args.depend::<config::Service>("config"),
state: args.depend::<rooms::state::Service>("rooms::state"),
state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
state_cache: args.depend::<rooms::state_cache::Service>("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<T>(&self, dest: &str, request: T) -> Result<T::IncomingResponse>
where
T: OutgoingRequest + Debug + Send,
T: OutgoingRequest<Authentication = NoAuthentication, PathBuilder = SinglePath> + Debug + Send,
{
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_0];
@@ -201,7 +197,7 @@ pub async fn send_request<T>(&self, dest: &str, request: T) -> Result<T::Incomin
trace!("Push gateway destination: {dest}");
let http_request = request
.try_into_http_request::<BytesMut>(&dest, SendAccessToken::None, &VERSIONS)
.try_into_http_request::<BytesMut>(&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<E>(
{
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<AnySyncTimelineEvent>,
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))]
+5 -5
View File
@@ -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<CachedOverride> {
}
#[implement(Cache)]
pub fn destinations(&self) -> impl Stream<Item = (&ServerName, CachedDest)> + Send + '_ {
pub fn destinations(&self) -> impl Stream<Item = (OwnedServerName, CachedDest)> + 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<Item = (&ServerName, CachedOverride)> + Send + '_ {
pub fn overrides(&self) -> impl Stream<Item = (OwnedServerName, CachedOverride)> + 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 {
+4 -1
View File
@@ -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<Option<Strin
.as_str()
.unwrap_or_default();
if ruma::identifiers_validation::server_name::validate(m_server).is_err() {
if ServerName::parse(m_server).is_err() {
debug_error!("response content missing or invalid");
return Ok(None);
}
+11 -33
View File
@@ -9,11 +9,10 @@
use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, TryFutureExt};
use ruma::{
OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId,
events::{
OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId, events::{
StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
},
}
};
use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending};
@@ -179,7 +178,6 @@ pub async fn resolve_alias(
.services
.state_cache
.room_servers(&room_id)
.map(ToOwned::to_owned)
.collect()
.await;
return Ok((room_id, servers));
@@ -197,22 +195,22 @@ pub async fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<OwnedRoom
pub fn local_aliases_for_room<'a>(
&'a self,
room_id: &'a RoomId,
) -> impl Stream<Item = &'a RoomAliasId> + Send + 'a {
) -> impl Stream<Item = OwnedRoomAliasId> + 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<Item = (&RoomId, &str)> + Send + '_ {
pub fn all_local_aliases(&self) -> impl Stream<Item = (OwnedRoomId, &str)> + 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<bool> {
@@ -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::<RoomPowerLevelsEventContent>(
&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<OwnedUserId> {
@@ -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))
+2 -2
View File
@@ -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<Response> {
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
+2 -2
View File
@@ -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<Item = &RoomId> + Send {
pub fn public_rooms(&self) -> impl Stream<Item = OwnedRoomId> + Send {
self.db.publicroomids.keys().ignore_err()
}
@@ -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;
};
@@ -31,10 +31,7 @@ pub(super) async fn fetch_state<Pdu>(
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}"))?;
@@ -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);
}

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