Skip to main content

tuwunel_api/server/
send_knock.rs

1use axum::extract::State;
2use futures::{FutureExt, future::try_join};
3use ruma::{
4	OwnedServerName, OwnedUserId,
5	RoomVersionId::*,
6	api::federation::membership::create_knock_event,
7	events::{
8		StateEventType,
9		room::member::{MembershipState, RoomMemberEventContent},
10	},
11	serde::JsonObject,
12};
13use tuwunel_core::{
14	Err, Result, at, err,
15	matrix::{event::gen_event_id_canonical_json, pdu::PduEvent},
16	warn,
17};
18
19use crate::Ruma;
20
21/// # `PUT /_matrix/federation/v1/send_knock/{roomId}/{eventId}`
22///
23/// Submits a signed knock event.
24pub(crate) async fn create_knock_event_v1_route(
25	State(services): State<crate::State>,
26	body: Ruma<create_knock_event::v1::Request>,
27) -> Result<create_knock_event::v1::Response> {
28	if let Some(server) = body.room_id.server_name()
29		&& services
30			.config
31			.is_forbidden_remote_server_name(server)
32	{
33		warn!(
34			"Server {} tried knocking room ID {} which has a server name that is globally \
35			 forbidden. Rejecting.",
36			body.origin(),
37			&body.room_id,
38		);
39		return Err!(Request(Forbidden("Server is banned on this homeserver.")));
40	}
41
42	if !services.metadata.exists(&body.room_id).await {
43		return Err!(Request(NotFound("Room is unknown to this server.")));
44	}
45
46	// ACL check origin server
47	services
48		.event_handler
49		.acl_check(body.origin(), &body.room_id)
50		.await?;
51
52	let room_version_id = services
53		.state
54		.get_room_version(&body.room_id)
55		.await?;
56
57	if matches!(room_version_id, V1 | V2 | V3 | V4 | V5 | V6) {
58		return Err!(Request(Forbidden("Room version does not support knocking.")));
59	}
60
61	let Ok((event_id, value)) = gen_event_id_canonical_json(&body.pdu, &room_version_id) else {
62		// Event could not be converted to canonical json
63		return Err!(Request(InvalidParam("Could not convert event to canonical json.")));
64	};
65
66	let event_type: StateEventType = serde_json::from_value(
67		value
68			.get("type")
69			.ok_or_else(|| err!(Request(InvalidParam("Event has no event type."))))?
70			.clone()
71			.into(),
72	)
73	.map_err(|e| err!(Request(InvalidParam("Event has invalid event type: {e}"))))?;
74
75	if event_type != StateEventType::RoomMember {
76		return Err!(Request(InvalidParam(
77			"Not allowed to send non-membership state event to knock endpoint.",
78		)));
79	}
80
81	let content: RoomMemberEventContent = serde_json::from_value(
82		value
83			.get("content")
84			.ok_or_else(|| err!(Request(InvalidParam("Membership event has no content"))))?
85			.clone()
86			.into(),
87	)
88	.map_err(|e| err!(Request(InvalidParam("Event has invalid membership content: {e}"))))?;
89
90	if content.membership != MembershipState::Knock {
91		return Err!(Request(InvalidParam(
92			"Not allowed to send a non-knock membership event to knock endpoint."
93		)));
94	}
95
96	// ACL check sender server name
97	let sender: OwnedUserId = serde_json::from_value(
98		value
99			.get("sender")
100			.ok_or_else(|| err!(Request(InvalidParam("Event has no sender user ID."))))?
101			.clone()
102			.into(),
103	)
104	.map_err(|e| err!(Request(BadJson("Event sender is not a valid user ID: {e}"))))?;
105
106	services
107		.event_handler
108		.acl_check(sender.server_name(), &body.room_id)
109		.await?;
110
111	// check if origin server is trying to send for another server
112	if sender.server_name() != body.origin() {
113		return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
114	}
115
116	let state_key: OwnedUserId = serde_json::from_value(
117		value
118			.get("state_key")
119			.ok_or_else(|| err!(Request(InvalidParam("Event does not have a state_key"))))?
120			.clone()
121			.into(),
122	)
123	.map_err(|e| err!(Request(BadJson("Event does not have a valid state_key: {e}"))))?;
124
125	if state_key != sender {
126		return Err!(Request(InvalidParam("state_key does not match sender user of event.")));
127	}
128
129	let origin: OwnedServerName = serde_json::from_value(
130		value
131			.get("origin")
132			.ok_or_else(|| err!(Request(BadJson("Event does not have an origin server name."))))?
133			.clone()
134			.into(),
135	)
136	.map_err(|e| err!(Request(BadJson("Event has an invalid origin server name: {e}"))))?;
137
138	let mut event: JsonObject = serde_json::from_str(body.pdu.get())
139		.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
140
141	event.insert("event_id".to_owned(), "$placeholder".into());
142
143	let pdu: PduEvent = serde_json::from_value(event.into())
144		.map_err(|e| err!(Request(InvalidParam("Invalid knock event PDU: {e}"))))?;
145
146	let mutex_lock = services
147		.event_handler
148		.mutex_federation
149		.lock(&body.room_id)
150		.await;
151
152	let pdu_id = services
153		.event_handler
154		.handle_incoming_pdu(&origin, &body.room_id, &event_id, value.clone(), true)
155		.boxed()
156		.await?
157		.map(at!(0))
158		.ok_or_else(|| err!(Request(InvalidParam("Could not accept as timeline event."))))?;
159
160	drop(mutex_lock);
161
162	let broadcast = services
163		.sending
164		.send_pdu_room(&body.room_id, &pdu_id);
165
166	let knock_room_state = services.state.summary_stripped(&pdu).map(Ok);
167
168	let (knock_room_state, ()) = try_join(knock_room_state, broadcast).await?;
169
170	Ok(create_knock_event::v1::Response {
171		knock_room_state: knock_room_state
172			.into_iter()
173			.map(Into::into)
174			.collect(),
175	})
176}