From 0c37a542d411dc5ef41278d2cf4c1f9b674a98e6 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 11 Jul 2026 08:24:02 +0100 Subject: [PATCH] feat: Give send_leave the invite treatment --- src/api/server/send_leave.rs | 135 ++++++++--------------------------- 1 file changed, 31 insertions(+), 104 deletions(-) diff --git a/src/api/server/send_leave.rs b/src/api/server/send_leave.rs index 121683224..cbbb4d475 100644 --- a/src/api/server/send_leave.rs +++ b/src/api/server/send_leave.rs @@ -1,18 +1,11 @@ use axum::extract::State; -use conduwuit::{Err, Result, err, info, matrix::event::gen_event_id_canonical_json}; -use conduwuit_service::Services; +use conduwuit::{Err, Result, debug_info, err}; use futures::FutureExt; use ruma::{ - OwnedRoomId, OwnedUserId, RoomId, ServerName, - api::federation::membership::create_leave_event, - events::{ - StateEventType, - room::member::{MembershipState, RoomMemberEventContent}, - }, + api::federation::membership::create_leave_event, events::room::member::MembershipState, }; -use serde_json::value::RawValue as RawJsonValue; -use crate::Ruma; +use crate::{Ruma, server::utils::validate_any_membership_event}; /// # `PUT /_matrix/federation/v2/send_leave/{roomId}/{eventId}` /// @@ -21,17 +14,8 @@ pub(crate) async fn create_leave_event_v2_route( State(services): State, body: Ruma, ) -> Result { - create_leave_event(&services, &body.identity, &body.room_id, &body.pdu).await?; - - Ok(create_leave_event::v2::Response::new()) -} - -async fn create_leave_event( - services: &Services, - origin: &ServerName, - room_id: &RoomId, - pdu: &RawJsonValue, -) -> Result { + let room_id = body.room_id.as_ref(); + let origin = &body.identity; if !services.rooms.metadata.exists(room_id).await { return Err!(Request(NotFound("Room is unknown to this server."))); } @@ -42,9 +26,10 @@ async fn create_leave_event( .server_in_room(services.globals.server_name(), room_id) .await { - info!( + debug_info!( origin = origin.as_str(), - "Refusing to serve backfill for room we aren't participating in" + room_id = %room_id, + "Refusing to send_leave for room we aren't participating in" ); return Err!(Request(NotFound("This server is not participating in that room."))); } @@ -56,91 +41,31 @@ async fn create_leave_event( .acl_check(origin, room_id) .await?; - // We do not add the event_id field to the pdu here because of signature and - // hashes checks let room_version = services.rooms.state.get_room_version(room_id).await?; + let create_event = services + .rooms + .state_accessor + .get_room_create_event(room_id) + .await; let room_version_rules = room_version.rules().unwrap(); - let Ok((event_id, value)) = gen_event_id_canonical_json(pdu, &room_version_rules) else { - // Event could not be converted to canonical json - return Err!(Request(BadJson("Could not convert event to canonical json."))); - }; - - let event_room_id: OwnedRoomId = serde_json::from_value( - serde_json::to_value( - value - .get("room_id") - .ok_or_else(|| err!(Request(BadJson("Event missing room_id property."))))?, - ) - .expect("CanonicalJson is valid json value"), + let (value, membership, sender, target) = validate_any_membership_event( + &services, + &body.pdu, + &room_version_rules, + create_event.event_id.clone(), + body.room_id.clone(), + body.event_id.clone(), ) - .map_err(|e| err!(Request(BadJson(warn!("room_id field is not a valid room ID: {e}")))))?; - - if event_room_id != room_id { - return Err!(Request(BadJson("Event room_id does not match request path room ID."))); + .await?; + if membership != MembershipState::Leave { + return Err!(Request(InvalidParam("Invalid membership (expected `leave`)"))); } - - let content: RoomMemberEventContent = serde_json::from_value( - value - .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}")))))?; - - if content.membership != MembershipState::Leave { - return Err!(Request(BadJson( - "Not allowed to send a non-leave membership event to leave endpoint." - ))); + if sender.server_name() != body.identity { + return Err!(Request(InvalidParam("Sender belongs to a different server"))); } - - let event_type: StateEventType = serde_json::from_value( - value - .get("type") - .ok_or_else(|| err!(Request(BadJson("Event missing type property."))))? - .clone() - .into(), - ) - .map_err(|e| err!(Request(BadJson(warn!("Event has invalid state event type: {e}")))))?; - - if event_type != StateEventType::RoomMember { - return Err!(Request(BadJson( - "Not allowed to send non-membership state event to leave endpoint." - ))); - } - - // ACL check sender server name - let sender: OwnedUserId = serde_json::from_value( - value - .get("sender") - .ok_or_else(|| err!(Request(BadJson("Event missing sender property."))))? - .clone() - .into(), - ) - .map_err(|e| err!(Request(BadJson(warn!("sender property is not a valid user ID: {e}")))))?; - - services - .rooms - .event_handler - .acl_check(sender.server_name(), room_id) - .await?; - - if sender.server_name() != origin { - return Err!(Request(BadJson("Not allowed to leave on behalf of another server/user."))); - } - - let state_key: OwnedUserId = serde_json::from_value( - value - .get("state_key") - .ok_or_else(|| err!(Request(BadJson("Event missing state_key property."))))? - .clone() - .into(), - ) - .map_err(|e| err!(Request(BadJson(warn!("State key is not a valid user ID: {e}")))))?; - - if state_key != sender { - return Err!(Request(BadJson("State key does not match sender user."))); + if sender != target { + return Err!(Request(InvalidParam("Sender does not match state key"))); } let mutex_lock = services @@ -153,7 +78,7 @@ async fn create_leave_event( let pdu_id = services .rooms .event_handler - .handle_incoming_pdu(origin, room_id, &event_id, value, false) + .handle_incoming_pdu(origin, room_id, &body.event_id, value, false) .boxed() .await? .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?; @@ -164,5 +89,7 @@ async fn create_leave_event( .sending .send_pdu_room(room_id, &pdu_id) .boxed() - .await + .await?; + + Ok(create_leave_event::v2::Response::new()) }