feat: Enhance invite security checks & do away with stripped state
This commit is contained in:
parent
1237e60aaf
commit
04980b3ee7
7 changed files with 281 additions and 166 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -4063,7 +4063,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
|||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
|
|
@ -4083,7 +4083,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4095,7 +4095,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
|
|
@ -4118,7 +4118,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -4150,7 +4150,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
|
|
@ -4175,7 +4175,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
|
|
@ -4197,7 +4197,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.17",
|
||||
|
|
@ -4206,7 +4206,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4216,7 +4216,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
|
|
@ -4231,7 +4231,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4243,7 +4243,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=27abe0dcd33fd4056efc94bab3582646b31b6ce9#27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=e60876b6ff2d207a46aa8910a02474268bac8592#e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
124
Cargo.toml
124
Cargo.toml
|
|
@ -33,11 +33,11 @@ features = ["serde"]
|
|||
[workspace.dependencies.smallvec]
|
||||
version = "1.14.0"
|
||||
features = [
|
||||
"const_generics",
|
||||
"const_new",
|
||||
"serde",
|
||||
"union",
|
||||
"write",
|
||||
"const_generics",
|
||||
"const_new",
|
||||
"serde",
|
||||
"union",
|
||||
"write",
|
||||
]
|
||||
|
||||
[workspace.dependencies.smallstr]
|
||||
|
|
@ -96,13 +96,13 @@ version = "1.11.1"
|
|||
version = "0.7.9"
|
||||
default-features = false
|
||||
features = [
|
||||
"form",
|
||||
"http1",
|
||||
"http2",
|
||||
"json",
|
||||
"matched-path",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"form",
|
||||
"http1",
|
||||
"http2",
|
||||
"json",
|
||||
"matched-path",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[workspace.dependencies.axum-extra]
|
||||
|
|
@ -149,10 +149,10 @@ features = ["aws_lc_rs"]
|
|||
version = "0.12.15"
|
||||
default-features = false
|
||||
features = [
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"hickory-dns",
|
||||
"http2",
|
||||
"rustls-tls-native-roots",
|
||||
"socks",
|
||||
"hickory-dns",
|
||||
"http2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.serde]
|
||||
|
|
@ -188,18 +188,18 @@ default-features = false
|
|||
version = "0.25.5"
|
||||
default-features = false
|
||||
features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
"jpeg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
]
|
||||
|
||||
[workspace.dependencies.blurhash]
|
||||
version = "0.2.3"
|
||||
default-features = false
|
||||
features = [
|
||||
"fast-linear-to-srgb",
|
||||
"image",
|
||||
"fast-linear-to-srgb",
|
||||
"image",
|
||||
]
|
||||
|
||||
# logging
|
||||
|
|
@ -229,13 +229,13 @@ default-features = false
|
|||
version = "4.5.35"
|
||||
default-features = false
|
||||
features = [
|
||||
"derive",
|
||||
"env",
|
||||
"error-context",
|
||||
"help",
|
||||
"std",
|
||||
"string",
|
||||
"usage",
|
||||
"derive",
|
||||
"env",
|
||||
"error-context",
|
||||
"help",
|
||||
"std",
|
||||
"string",
|
||||
"usage",
|
||||
]
|
||||
|
||||
[workspace.dependencies.futures]
|
||||
|
|
@ -247,15 +247,15 @@ features = ["std", "async-await"]
|
|||
version = "1.44.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"fs",
|
||||
"net",
|
||||
"macros",
|
||||
"sync",
|
||||
"signal",
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
"io-util",
|
||||
"tracing",
|
||||
"fs",
|
||||
"net",
|
||||
"macros",
|
||||
"sync",
|
||||
"signal",
|
||||
"time",
|
||||
"rt-multi-thread",
|
||||
"io-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[workspace.dependencies.tokio-metrics]
|
||||
|
|
@ -280,18 +280,18 @@ default-features = false
|
|||
version = "1.6.0"
|
||||
default-features = false
|
||||
features = [
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
"server",
|
||||
"http1",
|
||||
"http2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.hyper-util]
|
||||
version = "=0.1.17"
|
||||
default-features = false
|
||||
features = [
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"tokio",
|
||||
"server-auto",
|
||||
"server-graceful",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
# to support multiple variations of setting a config option
|
||||
|
|
@ -310,9 +310,9 @@ features = ["env", "toml"]
|
|||
version = "0.25.1"
|
||||
default-features = false
|
||||
features = [
|
||||
"serde",
|
||||
"system-config",
|
||||
"tokio",
|
||||
"serde",
|
||||
"system-config",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
# Used for conduwuit::Error type
|
||||
|
|
@ -351,7 +351,7 @@ version = "0.1.2"
|
|||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "27abe0dcd33fd4056efc94bab3582646b31b6ce9"
|
||||
rev = "e60876b6ff2d207a46aa8910a02474268bac8592"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
|
|
@ -381,13 +381,13 @@ features = [
|
|||
"unstable-msc4095",
|
||||
"unstable-msc4121",
|
||||
"unstable-msc4125",
|
||||
"unstable-msc4155",
|
||||
"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-msc4155"
|
||||
]
|
||||
|
||||
[workspace.dependencies.rust-rocksdb]
|
||||
|
|
@ -395,11 +395,11 @@ git = "https://forgejo.ellis.link/continuwuation/rust-rocksdb-zaidoon1"
|
|||
rev = "61d9d23872197e9ace4a477f2617d5c9f50ecb23"
|
||||
default-features = false
|
||||
features = [
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"lz4",
|
||||
"zstd",
|
||||
"bzip2",
|
||||
"multi-threaded-cf",
|
||||
"mt_static",
|
||||
"lz4",
|
||||
"zstd",
|
||||
"bzip2",
|
||||
]
|
||||
|
||||
[workspace.dependencies.sha2]
|
||||
|
|
@ -458,16 +458,16 @@ git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
|||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemallocator]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
rev = "82af58d6a13ddd5dcdc7d4e91eae3b63292995b8"
|
||||
default-features = false
|
||||
features = [
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
"background_threads_runtime_support",
|
||||
"unprefixed_malloc_on_supported_platforms",
|
||||
]
|
||||
[workspace.dependencies.tikv-jemalloc-ctl]
|
||||
git = "https://forgejo.ellis.link/continuwuation/jemallocator"
|
||||
|
|
@ -491,9 +491,9 @@ default-features = false
|
|||
version = "0.1.2"
|
||||
default-features = false
|
||||
features = [
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
"static",
|
||||
"gcc",
|
||||
"light",
|
||||
]
|
||||
|
||||
[workspace.dependencies.rustyline-async]
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ pub(crate) async fn invite_helper(
|
|||
)
|
||||
.await?;
|
||||
|
||||
let invite_room_state = services.rooms.state.summary_stripped(&pdu, room_id).await;
|
||||
let invite_room_state = services.rooms.state.summary(&pdu, room_id).await;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,172 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use conduwuit::{
|
||||
Err, Error, PduEvent, Result, err,
|
||||
matrix::{Event, event::gen_event_id},
|
||||
Err, Error, EventTypeExt, PduEvent, Result, RoomVersion, err,
|
||||
matrix::{Event, StateKey, event::gen_event_id},
|
||||
utils::{self, hash::sha256},
|
||||
warn,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonValue, OwnedUserId, UserId,
|
||||
CanonicalJsonValue, OwnedUserId, RoomId, RoomVersionId, ServerName, UserId,
|
||||
api::{client::error::ErrorKind, federation::membership::create_invite},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
serde::JsonObject,
|
||||
events::{
|
||||
AnyStateEvent, StateEventType,
|
||||
room::{
|
||||
create::RoomCreateEventContent,
|
||||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
use service::{Services, rooms::timeline::pdu_fits};
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// Ensures that the state received from the invite endpoint is sane, correct,
|
||||
/// and complies with the room version's requirements.
|
||||
async fn check_invite_state(
|
||||
services: &Services,
|
||||
stripped_state: &Vec<Raw<AnyStateEvent>>,
|
||||
room_id: &RoomId,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> Result<()> {
|
||||
let room_version = RoomVersion::new(room_version_id).map_err(|e| {
|
||||
err!(Request(UnsupportedRoomVersion("Invalid room version provided: {e}")))
|
||||
})?;
|
||||
let mut room_state: HashMap<(StateEventType, StateKey), PduEvent> = HashMap::new();
|
||||
|
||||
// Build the room state from the provided state events,
|
||||
// ensuring that there's no duplicates. We need to check that m.room.create is
|
||||
// present and lines up with the other things we've been told, and then verify
|
||||
// any signatures present to ensure this isn't forged.
|
||||
for raw_event in stripped_state {
|
||||
let event = raw_event
|
||||
.deserialize_as::<PduEvent>()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid state event: {e}"))))?;
|
||||
if event.state_key().is_none() {
|
||||
return Err!(Request(InvalidParam("State event missing event type.")));
|
||||
}
|
||||
let key = event
|
||||
.event_type()
|
||||
.with_state_key(event.state_key().unwrap());
|
||||
if room_state.contains_key(&key) {
|
||||
return Err!(Request(InvalidParam("Duplicate state event found for {key:?}")));
|
||||
}
|
||||
|
||||
// verify the event
|
||||
let canonical = utils::to_canonical_object(raw_event)?;
|
||||
if !pdu_fits(&mut canonical.clone()) {
|
||||
return Err!(Request(InvalidParam("An invite state event PDU is too large")));
|
||||
}
|
||||
services
|
||||
.server_keys
|
||||
.verify_event(&canonical, Some(room_version_id))
|
||||
.await
|
||||
.map_err(|e| err!(Request(InvalidParam("Signature failed verification: {e}"))))?;
|
||||
|
||||
// Ensure all events are in the same room
|
||||
if event.room_id_or_hash() != room_id {
|
||||
return Err!(Request(InvalidParam(
|
||||
"State event room ID for {} does not match the expected room ID {}.",
|
||||
event.event_id,
|
||||
room_id,
|
||||
)));
|
||||
}
|
||||
room_state.insert(key, event);
|
||||
}
|
||||
|
||||
// verify m.room.create is present, has a matching room ID, and a matching room
|
||||
// version.
|
||||
let create_event = room_state
|
||||
.get(&(StateEventType::RoomCreate, "".into()))
|
||||
.ok_or_else(|| err!(Request(MissingParam("Missing m.room.create in stripped state."))))?;
|
||||
let create_event_content: RoomCreateEventContent = create_event
|
||||
.get_content()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid m.room.create content: {e}"))))?;
|
||||
// Room v12 removed room IDs over federation, so we'll need to see if the event
|
||||
// ID matches the room ID instead.
|
||||
if room_version.room_ids_as_hashes {
|
||||
let given_room_id = create_event.event_id().as_str().replace('$', "!");
|
||||
if given_room_id != room_id.as_str() {
|
||||
return Err!(Request(InvalidParam(
|
||||
"m.room.create event ID does not match the room ID."
|
||||
)));
|
||||
}
|
||||
} else if create_event.room_id().unwrap() != room_id {
|
||||
return Err!(Request(InvalidParam("m.room.create room ID does not match the room ID.")));
|
||||
}
|
||||
|
||||
// Make sure the room version matches
|
||||
if &create_event_content.room_version != room_version_id {
|
||||
return Err!(Request(InvalidParam(
|
||||
"m.room.create room version does not match the given room version."
|
||||
)));
|
||||
}
|
||||
|
||||
// Looks solid
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures that the invite event received from the invite endpoint is sane,
|
||||
/// correct, and complies with the room version's requirements.
|
||||
/// Returns the invited user ID on success.
|
||||
async fn check_invite_event(
|
||||
services: &Services,
|
||||
invite_event: &PduEvent,
|
||||
origin: &ServerName,
|
||||
room_id: &RoomId,
|
||||
room_version_id: &RoomVersionId,
|
||||
) -> Result<OwnedUserId> {
|
||||
// Check: The event sender is not a user ID on the origin server.
|
||||
if invite_event.sender.server_name() != origin {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Invite event sender's server does not match the origin server."
|
||||
)));
|
||||
}
|
||||
// Check: The `state_key` is not a user ID on the receiving server.
|
||||
let state_key: &UserId = invite_event
|
||||
.state_key()
|
||||
.ok_or_else(|| err!(Request(MissingParam("Invite event missing state_key."))))?
|
||||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
|
||||
if !services.globals.server_is_ours(state_key.server_name()) {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Invite event state_key does not belong to this homeserver."
|
||||
)));
|
||||
}
|
||||
|
||||
// Check: The event's room ID matches the expected room ID.
|
||||
if let Some(evt_room_id) = invite_event.room_id() {
|
||||
if evt_room_id != room_id {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Invite event room ID does not match the expected room ID."
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err!(Request(MissingParam("Invite event missing room ID.")));
|
||||
}
|
||||
|
||||
// Check: the membership really is "invite"
|
||||
let content = invite_event.get_content::<RoomMemberEventContent>()?;
|
||||
if content.membership != MembershipState::Invite {
|
||||
return Err!(Request(InvalidParam("Invite event is not a membership invite.")));
|
||||
}
|
||||
|
||||
// Check: signature is valid
|
||||
services
|
||||
.server_keys
|
||||
.verify_event(&utils::to_canonical_object(invite_event)?, Some(room_version_id))
|
||||
.await
|
||||
.map_err(|e| {
|
||||
err!(Request(InvalidParam("Invite event signature failed verification: {e}")))
|
||||
})?;
|
||||
|
||||
Ok(state_key.to_owned())
|
||||
}
|
||||
|
||||
/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
|
||||
///
|
||||
/// Invites a remote user to a room.
|
||||
|
|
@ -61,57 +212,39 @@ pub(crate) async fn create_invite_route(
|
|||
let mut signed_event = utils::to_canonical_object(&body.event)
|
||||
.map_err(|_| err!(Request(InvalidParam("Invite event is invalid."))))?;
|
||||
|
||||
// Ensure this is a membership event
|
||||
if signed_event
|
||||
.get("type")
|
||||
.expect("event must have a type")
|
||||
.as_str()
|
||||
.expect("type must be a string")
|
||||
!= "m.room.member"
|
||||
{
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send non-membership event to invite endpoint."
|
||||
// We need to hash and sign the event before we can generate the event ID.
|
||||
// It is important that this signed event does not get sent back to the caller
|
||||
// until we've verified this isn't incorrect.
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut signed_event, &body.room_version)
|
||||
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
||||
let event_id = gen_event_id(&signed_event.clone(), &body.room_version)?;
|
||||
if event_id != body.event_id {
|
||||
return Err!(Request(InvalidParam(
|
||||
"Event ID does not match the generated event ID ({} vs {}).",
|
||||
event_id,
|
||||
body.event_id,
|
||||
)));
|
||||
}
|
||||
|
||||
let content: RoomMemberEventContent = serde_json::from_value(
|
||||
signed_event
|
||||
.get("content")
|
||||
.ok_or_else(|| err!(Request(BadJson("Event missing content property"))))?
|
||||
.clone()
|
||||
.into(),
|
||||
)
|
||||
.map_err(|e| err!(Request(BadJson(warn!("Event content is empty or invalid: {e}")))))?;
|
||||
let pdu = PduEvent::from_id_val(&event_id, signed_event.clone())
|
||||
.map_err(|e| err!(Request(InvalidParam("Invite event is invalid: {e}"))))?;
|
||||
|
||||
// Ensure this is an invite membership event
|
||||
if content.membership != MembershipState::Invite {
|
||||
return Err!(Request(BadJson(
|
||||
"Not allowed to send a non-invite membership event to invite endpoint."
|
||||
)));
|
||||
}
|
||||
// Check the invite event is valid.
|
||||
let recipient_user =
|
||||
check_invite_event(&services, &pdu, body.origin(), &body.room_id, &body.room_version)
|
||||
.await?;
|
||||
|
||||
// Ensure the sending user isn't a lying bozo
|
||||
let sender_server = signed_event
|
||||
.get("sender")
|
||||
.try_into()
|
||||
.map(UserId::server_name)
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
|
||||
if sender_server != body.origin() {
|
||||
return Err!(Request(Forbidden("Sender's server does not match the origin server.",)));
|
||||
}
|
||||
|
||||
// Ensure the target user belongs to this server
|
||||
let recipient_user: OwnedUserId = signed_event
|
||||
.get("state_key")
|
||||
.try_into()
|
||||
.map(UserId::to_owned)
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid state_key property: {e}"))))?;
|
||||
|
||||
if !services
|
||||
.globals
|
||||
.server_is_ours(recipient_user.server_name())
|
||||
// Make sure the room isn't banned and we allow invites
|
||||
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
return Err!(Request(InvalidParam("User does not belong to this homeserver.")));
|
||||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
||||
}
|
||||
if services.rooms.metadata.is_banned(&body.room_id).await
|
||||
&& !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
// Make sure we're not ACL'ed from their room.
|
||||
|
|
@ -121,13 +254,9 @@ pub(crate) async fn create_invite_route(
|
|||
.acl_check(recipient_user.server_name(), &body.room_id)
|
||||
.await?;
|
||||
|
||||
services
|
||||
.server_keys
|
||||
.hash_and_sign_event(&mut signed_event, &body.room_version)
|
||||
.map_err(|e| err!(Request(InvalidParam("Failed to sign event: {e}"))))?;
|
||||
|
||||
// Generate event id
|
||||
let event_id = gen_event_id(&signed_event, &body.room_version)?;
|
||||
// And check the invite state is valid.
|
||||
check_invite_state(&services, &body.invite_room_state, &body.room_id, &body.room_version)
|
||||
.await?;
|
||||
|
||||
// Add event_id back
|
||||
signed_event.insert("event_id".to_owned(), CanonicalJsonValue::String(event_id.to_string()));
|
||||
|
|
@ -137,27 +266,8 @@ pub(crate) async fn create_invite_route(
|
|||
.try_into()
|
||||
.map_err(|e| err!(Request(InvalidParam("Invalid sender property: {e}"))))?;
|
||||
|
||||
if services.rooms.metadata.is_banned(&body.room_id).await
|
||||
&& !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
|
||||
}
|
||||
|
||||
if services.config.block_non_admin_invites && !services.users.is_admin(&recipient_user).await
|
||||
{
|
||||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
||||
}
|
||||
|
||||
let mut invite_state = body.invite_room_state.clone();
|
||||
|
||||
let mut event: JsonObject = serde_json::from_str(body.event.get())
|
||||
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
|
||||
|
||||
event.insert("event_id".to_owned(), "$placeholder".into());
|
||||
|
||||
let pdu: PduEvent = serde_json::from_value(event.into())
|
||||
.map_err(|e| err!(Request(BadJson("Invalid invite event PDU: {e}"))))?;
|
||||
|
||||
invite_state.push(pdu.to_format());
|
||||
|
||||
// If we are active in the room, the remote server will notify us about the
|
||||
|
|
|
|||
|
|
@ -175,11 +175,7 @@ pub(crate) async fn create_knock_event_v1_route(
|
|||
.send_pdu_room(&body.room_id, &pdu_id)
|
||||
.await?;
|
||||
|
||||
let knock_room_state = services
|
||||
.rooms
|
||||
.state
|
||||
.summary_stripped(&pdu, &body.room_id)
|
||||
.await;
|
||||
let knock_room_state = services.rooms.state.summary(&pdu, &body.room_id).await;
|
||||
|
||||
Ok(send_knock::v1::Response { knock_room_state })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ use futures::{
|
|||
use ruma::{
|
||||
EventId, OwnedEventId, OwnedRoomId, RoomId, RoomVersionId, UserId,
|
||||
events::{
|
||||
AnyStrippedStateEvent, StateEventType, TimelineEventType,
|
||||
room::create::RoomCreateEventContent,
|
||||
AnyStateEvent, StateEventType, TimelineEventType, room::create::RoomCreateEventContent,
|
||||
},
|
||||
serde::Raw,
|
||||
};
|
||||
|
|
@ -307,12 +306,22 @@ impl Service {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a summary of the room state for invites and knock responses.
|
||||
///
|
||||
/// This used to return stripped state, but now returns complete events.
|
||||
///
|
||||
/// Returns:
|
||||
///
|
||||
/// - m.room.create
|
||||
/// - m.room.join_rules
|
||||
/// - m.room.canonical_alias
|
||||
/// - m.room.name
|
||||
/// - m.room.avatar
|
||||
/// - m.room.member (of the event sender)
|
||||
/// - m.room.encryption
|
||||
/// - m.room.topic
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
pub async fn summary_stripped<'a, E>(
|
||||
&self,
|
||||
event: &'a E,
|
||||
room_id: &RoomId,
|
||||
) -> Vec<Raw<AnyStrippedStateEvent>>
|
||||
pub async fn summary<'a, E>(&self, event: &'a E, room_id: &RoomId) -> Vec<Raw<AnyStateEvent>>
|
||||
where
|
||||
E: Event + Send + Sync,
|
||||
&'a E: Event + Send,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use conduwuit::{Err, Event, Pdu, Result, implement, is_not_empty, utils::ReadyExt, warn};
|
||||
use conduwuit::{Err, Event, Pdu, Result, implement, is_not_empty, utils::ReadyExt};
|
||||
use database::{Json, serialize_key};
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
OwnedServerName, RoomId, UserId,
|
||||
events::{
|
||||
AnyStrippedStateEvent, GlobalAccountDataEventType, RoomAccountDataEventType,
|
||||
StateEventType,
|
||||
AnyStateEvent, AnyStrippedStateEvent, GlobalAccountDataEventType,
|
||||
RoomAccountDataEventType, StateEventType,
|
||||
direct::DirectEvent,
|
||||
invite_permission_config::FilterLevel,
|
||||
room::{
|
||||
|
|
@ -334,7 +334,7 @@ pub async fn mark_as_invited(
|
|||
user_id: &UserId,
|
||||
room_id: &RoomId,
|
||||
sender_user: &UserId,
|
||||
last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
|
||||
last_state: Option<Vec<Raw<AnyStateEvent>>>,
|
||||
invite_via: Option<Vec<OwnedServerName>>,
|
||||
) -> Result<()> {
|
||||
// return an error for blocked invites. ignored invites aren't handled here
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue