use axum::extract::State; use conduwuit::{Err, Event, Result, debug_info, err, matrix::pdu::PduEvent, warn}; use futures::FutureExt; use ruma::{ api::federation::membership::create_knock_event, events::room::member::MembershipState, }; use crate::{Ruma, server::utils::validate_any_membership_event}; /// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}` /// /// Submits a signed knock event. pub(crate) async fn create_knock_event_v1_route( State(services): State, body: Ruma, ) -> Result { if services .moderation .is_remote_server_forbidden(&body.identity) { warn!( "Server {} tried knocking room ID {} who has a server name that is globally \ forbidden. Rejecting.", body.identity, &body.room_id, ); return Err!(Request(Forbidden("Federation denied with {}", body.identity))); } if !services.rooms.metadata.exists(&body.room_id).await { return Err!(Request(NotFound("Room is unknown to this server."))); } if !services .rooms .state_cache .server_in_room(services.globals.server_name(), &body.room_id) .await { debug_info!( origin = body.identity.as_str(), room_id = %body.room_id, "Refusing to serve send_knock for room we aren't participating in" ); return Err!(Request(NotFound("This server is not participating in that room."))); } // ACL check origin server services .rooms .event_handler .acl_check(&body.identity, &body.room_id) .await?; let room_version = services.rooms.state.get_room_version(&body.room_id).await?; let create_event = services .rooms .state_accessor .get_room_create_event(&body.room_id) .await; let room_version_rules = room_version.rules().unwrap(); if !room_version_rules.authorization.knocking { return Err!(Request(Forbidden("Room version does not support knocking."))); } let (mut event, target_membership, sender, target) = validate_any_membership_event( &services, &body.pdu, &room_version_rules, create_event.event_id().to_owned(), body.room_id.clone(), body.event_id.clone(), ) .await?; if target_membership != MembershipState::Knock { return Err!(Request(InvalidParam("Invalid membership (expected `knock`)"))); } if sender.server_name() != body.identity { return Err!(Request(InvalidParam("Sender belongs to a different server"))); } if sender != target { return Err!(Request(InvalidParam("Sender does not match state key"))); } event.insert("event_id".to_owned(), body.event_id.as_str().into()); let pdu = PduEvent::from_id_val(&body.event_id, event.clone()) .map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?; let mutex_lock = services .rooms .event_handler .mutex_federation .lock(body.room_id.as_str()) .await; let pdu_id = services .rooms .event_handler .handle_incoming_pdu(sender.server_name(), &body.room_id, &body.event_id, event, false) .boxed() .await? .ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?; drop(mutex_lock); services .sending .send_pdu_room(&body.room_id, &pdu_id) .await?; let knock_room_state = services .rooms .state .summary_stripped(&pdu, &body.room_id, &sender, true) .await; Ok(create_knock_event::v1::Response::new(knock_room_state)) }