Skip to main content

tuwunel_api/client/
state.rs

1use axum::extract::State;
2use futures::{FutureExt, TryFutureExt, TryStreamExt};
3use ruma::{
4	OwnedEventId, OwnedRoomAliasId, RoomId, UserId,
5	api::client::state::{
6		get_state_event_for_key::{self, v3::StateEventFormat},
7		get_state_events, send_state_event,
8	},
9	events::{
10		AnyStateEventContent, StateEventType,
11		room::{
12			canonical_alias::RoomCanonicalAliasEventContent,
13			history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
14			join_rules::{JoinRule, RoomJoinRulesEventContent},
15			member::{MembershipState, RoomMemberEventContent},
16			server_acl::RoomServerAclEventContent,
17		},
18	},
19	serde::Raw,
20};
21use serde_json::json;
22use tuwunel_core::{
23	Err, Result, err, is_false,
24	matrix::{Event, pdu::PduBuilder},
25	utils::{BoolExt, stream::TryBroadbandExt},
26};
27use tuwunel_service::Services;
28
29use crate::{Ruma, RumaResponse, client::with_membership};
30
31/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}/{stateKey}`
32///
33/// Sends a state event into the room.
34pub(crate) async fn send_state_event_for_key_route(
35	State(services): State<crate::State>,
36	body: Ruma<send_state_event::v3::Request>,
37) -> Result<send_state_event::v3::Response> {
38	let sender_user = body.sender_user();
39
40	Ok(send_state_event::v3::Response {
41		event_id: send_state_event_for_key_helper(
42			&services,
43			sender_user,
44			&body.room_id,
45			&body.event_type,
46			&body.body.body,
47			&body.state_key,
48			if body.appservice_info.is_some() {
49				body.timestamp
50			} else {
51				None
52			},
53		)
54		.await?,
55	})
56}
57
58/// # `PUT /_matrix/client/*/rooms/{roomId}/state/{eventType}`
59///
60/// Sends a state event into the room.
61pub(crate) async fn send_state_event_for_empty_key_route(
62	State(services): State<crate::State>,
63	body: Ruma<send_state_event::v3::Request>,
64) -> Result<RumaResponse<send_state_event::v3::Response>> {
65	send_state_event_for_key_route(State(services), body)
66		.boxed()
67		.await
68		.map(RumaResponse)
69}
70
71/// # `GET /_matrix/client/v3/rooms/{roomid}/state`
72///
73/// Get all state events for a room.
74///
75/// - If not joined: Only works if current room history visibility is world
76///   readable
77pub(crate) async fn get_state_events_route(
78	State(services): State<crate::State>,
79	body: Ruma<get_state_events::v3::Request>,
80) -> Result<get_state_events::v3::Response> {
81	let sender_user = body.sender_user();
82
83	if !services
84		.state_accessor
85		.user_can_see_state_events(sender_user, &body.room_id)
86		.await
87	{
88		return Err!(Request(Forbidden("You don't have permission to view the room state.")));
89	}
90
91	let encrypted = services
92		.state_accessor
93		.is_encrypted_room(&body.room_id)
94		.await;
95
96	let room_state = services
97		.state_accessor
98		.room_state_full_pdus(&body.room_id)
99		.map_ok(Event::into_pdu)
100		.broad_and_then(async |pdu| {
101			Ok(with_membership(&services, pdu, sender_user, encrypted).await)
102		})
103		.map_ok(Event::into_format)
104		.try_collect()
105		.await?;
106
107	Ok(get_state_events::v3::Response { room_state })
108}
109
110/// # `GET /_matrix/client/v3/rooms/{roomid}/state/{eventType}/{stateKey}`
111///
112/// Get single state event of a room with the specified state key.
113/// The optional query parameter `?format=event|content` allows returning the
114/// full room state event or just the state event's content (default behaviour)
115///
116/// - If not joined: Only works if current room history visibility is world
117///   readable
118pub(crate) async fn get_state_events_for_key_route(
119	State(services): State<crate::State>,
120	body: Ruma<get_state_event_for_key::v3::Request>,
121) -> Result<get_state_event_for_key::v3::Response> {
122	let sender_user = body.sender_user();
123
124	if !services
125		.state_accessor
126		.user_can_see_state_events(sender_user, &body.room_id)
127		.await
128	{
129		return Err!(Request(NotFound(debug_warn!(
130			"You don't have permission to view the room state."
131		))));
132	}
133
134	let event = services
135		.state_accessor
136		.room_state_get(&body.room_id, &body.event_type, &body.state_key)
137		.await
138		.map_err(|e| {
139			err!(Request(NotFound(debug_warn!(
140				room_id = ?body.room_id,
141				event_type = ?body.event_type,
142				"Failed to get state event: {e}.",
143			))))
144		})?;
145
146	let event_or_content = match body.format {
147		| StateEventFormat::Event => json!({
148			"content": event.content(),
149			"event_id": event.event_id(),
150			"origin_server_ts": event.origin_server_ts(),
151			"room_id": event.room_id(),
152			"sender": event.sender(),
153			"state_key": event.state_key(),
154			"type": event.kind(),
155			"unsigned": event.unsigned(),
156		}),
157
158		| _ => event.get_content_as_value(),
159	};
160
161	let event_or_content =
162		serde_json::value::to_raw_value(&event_or_content).expect("serializable JSON value");
163
164	Ok(get_state_event_for_key::v3::Response::new(event_or_content))
165}
166
167/// # `GET /_matrix/client/v3/rooms/{roomid}/state/{eventType}`
168///
169/// Get single state event of a room.
170/// The optional query parameter `?format=event|content` allows returning the
171/// full room state event or just the state event's content (default behaviour)
172///
173/// - If not joined: Only works if current room history visibility is world
174///   readable
175pub(crate) async fn get_state_events_for_empty_key_route(
176	State(services): State<crate::State>,
177	body: Ruma<get_state_event_for_key::v3::Request>,
178) -> Result<RumaResponse<get_state_event_for_key::v3::Response>> {
179	get_state_events_for_key_route(State(services), body)
180		.await
181		.map(RumaResponse)
182}
183
184async fn send_state_event_for_key_helper(
185	services: &Services,
186	sender: &UserId,
187	room_id: &RoomId,
188	event_type: &StateEventType,
189	json: &Raw<AnyStateEventContent>,
190	state_key: &str,
191	timestamp: Option<ruma::MilliSecondsSinceUnixEpoch>,
192) -> Result<OwnedEventId> {
193	allowed_to_send_state_event(services, room_id, event_type, state_key, json).await?;
194	let state_lock = services.state.mutex.lock(room_id).await;
195	let event_id = services
196		.timeline
197		.build_and_append_pdu(
198			PduBuilder {
199				event_type: event_type.to_string().into(),
200				content: serde_json::from_str(json.json().get())?,
201				state_key: Some(state_key.into()),
202				timestamp,
203				..Default::default()
204			},
205			sender,
206			room_id,
207			&state_lock,
208		)
209		.boxed()
210		.await?;
211
212	Ok(event_id)
213}
214
215async fn allowed_to_send_state_event(
216	services: &Services,
217	room_id: &RoomId,
218	event_type: &StateEventType,
219	state_key: &str,
220	json: &Raw<AnyStateEventContent>,
221) -> Result {
222	match event_type {
223		| StateEventType::RoomCreate => Err!(Request(BadJson(debug_warn!(
224			?room_id,
225			"You cannot update m.room.create after a room has been created."
226		)))),
227		| StateEventType::RoomServerAcl => validate_server_acl(services, room_id, json),
228		| StateEventType::RoomEncryption => validate_encryption(services),
229		| StateEventType::RoomJoinRules => validate_join_rules(services, room_id, json).await,
230		| StateEventType::RoomHistoryVisibility =>
231			validate_history_visibility(services, room_id, json).await,
232		| StateEventType::RoomCanonicalAlias =>
233			validate_canonical_alias(services, room_id, json).await,
234		| StateEventType::RoomMember => validate_member(services, room_id, state_key, json).await,
235		| _ => Ok(()),
236	}
237}
238
239fn validate_encryption(services: &Services) -> Result {
240	services
241		.config
242		.allow_encryption
243		.then_some(())
244		.ok_or_else(|| err!(Request(Forbidden("Encryption is disabled on this homeserver."))))
245}
246
247fn validate_server_acl(
248	services: &Services,
249	room_id: &RoomId,
250	json: &Raw<AnyStateEventContent>,
251) -> Result {
252	let acl_content = json
253		.deserialize_as_unchecked::<RoomServerAclEventContent>()
254		.map_err(|e| {
255			err!(Request(BadJson(debug_warn!("Room server ACL event is invalid: {e}"))))
256		})?;
257
258	if acl_content.allow_is_empty() {
259		return Err!(Request(BadJson(debug_warn!(
260			?room_id,
261			"Sending an ACL event with an empty allow key will permanently brick the room for \
262			 non-tuwunel's as this equates to no servers being allowed to participate in this \
263			 room."
264		))));
265	}
266
267	if acl_content.deny_contains("*") && acl_content.allow_contains("*") {
268		return Err!(Request(BadJson(debug_warn!(
269			?room_id,
270			"Sending an ACL event with a deny and allow key value of \"*\" will permanently \
271			 brick the room for non-tuwunel's as this equates to no servers being allowed to \
272			 participate in this room."
273		))));
274	}
275
276	let server_name = services.globals.server_name();
277	let self_allowed =
278		acl_content.is_allowed(server_name) || acl_content.allow_contains(server_name.as_str());
279
280	if acl_content.deny_contains("*") && !self_allowed {
281		return Err!(Request(BadJson(debug_warn!(
282			?room_id,
283			"Sending an ACL event with a deny key value of \"*\" and without your own server \
284			 name in the allow key will result in you being unable to participate in this room."
285		))));
286	}
287
288	if !acl_content.allow_contains("*") && !self_allowed {
289		return Err!(Request(BadJson(debug_warn!(
290			?room_id,
291			"Sending an ACL event for an allow key without \"*\" and without your own server \
292			 name in the allow key will result in you being unable to participate in this room."
293		))));
294	}
295
296	Ok(())
297}
298
299async fn validate_join_rules(
300	services: &Services,
301	room_id: &RoomId,
302	json: &Raw<AnyStateEventContent>,
303) -> Result {
304	let Ok(admin_room_id) = services.admin.get_admin_room().await else {
305		return Ok(());
306	};
307
308	if admin_room_id != room_id {
309		return Ok(());
310	}
311
312	let join_rule = json
313		.deserialize_as_unchecked::<RoomJoinRulesEventContent>()
314		.map_err(|e| {
315			err!(Request(BadJson(debug_warn!("Room join rules event is invalid: {e}"))))
316		})?;
317
318	if join_rule.join_rule == JoinRule::Public {
319		return Err!(Request(Forbidden(
320			"Admin room is a sensitive room, it cannot be made public"
321		)));
322	}
323
324	Ok(())
325}
326
327async fn validate_history_visibility(
328	services: &Services,
329	room_id: &RoomId,
330	json: &Raw<AnyStateEventContent>,
331) -> Result {
332	let Ok(admin_room_id) = services.admin.get_admin_room().await else {
333		return Ok(());
334	};
335
336	let visibility_content = json
337		.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>()
338		.map_err(|e| {
339			err!(Request(BadJson(debug_warn!("Room history visibility event is invalid: {e}"))))
340		})?;
341
342	if admin_room_id == room_id
343		&& visibility_content.history_visibility == HistoryVisibility::WorldReadable
344	{
345		return Err!(Request(Forbidden(
346			"Admin room is a sensitive room, it cannot be made world readable (public room \
347			 history)."
348		)));
349	}
350
351	Ok(())
352}
353
354async fn validate_canonical_alias(
355	services: &Services,
356	room_id: &RoomId,
357	json: &Raw<AnyStateEventContent>,
358) -> Result {
359	let canonical_alias_content = json
360		.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>()
361		.map_err(|e| {
362			err!(Request(InvalidParam(debug_warn!("Room canonical alias event is invalid: {e}"))))
363		})?;
364
365	let current_aliases: Vec<OwnedRoomAliasId> = services
366		.state_accessor
367		.room_state_get_content::<RoomCanonicalAliasEventContent>(
368			room_id,
369			&StateEventType::RoomCanonicalAlias,
370			"",
371		)
372		.await
373		.ok()
374		.map(|content| content.aliases().cloned().collect())
375		.unwrap_or_default();
376
377	let new_aliases = canonical_alias_content
378		.aliases()
379		.filter(|alias| !current_aliases.contains(alias));
380
381	for alias in new_aliases {
382		let (alias_room_id, _servers) = services
383			.alias
384			.resolve_alias(alias)
385			.await
386			.map_err(|e| err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}"))))?;
387
388		if alias_room_id != room_id {
389			return Err!(Request(BadAlias(
390				"Room alias {alias} does not belong to room {room_id}"
391			)));
392		}
393	}
394
395	Ok(())
396}
397
398async fn validate_member(
399	services: &Services,
400	room_id: &RoomId,
401	state_key: &str,
402	json: &Raw<AnyStateEventContent>,
403) -> Result {
404	let membership_content = json
405		.deserialize_as_unchecked::<RoomMemberEventContent>()
406		.map_err(|e| {
407			err!(Request(BadJson(
408				"Membership content must have a valid JSON body with at least a valid \
409				 membership state: {e}"
410			)))
411		})?;
412
413	let Ok(target_user) = UserId::parse(state_key) else {
414		return Err!(Request(BadJson("Membership event has invalid or non-existent state key")));
415	};
416
417	if membership_content.membership == MembershipState::Invite
418		&& services.globals.user_is_local(&target_user)
419		&& services.users.invites_blocked(&target_user).await
420	{
421		return Err!(Request(InviteBlocked("{target_user} has blocked invites.")));
422	}
423
424	let Some(authorising_user) = membership_content.join_authorized_via_users_server else {
425		return Ok(());
426	};
427
428	if membership_content.membership != MembershipState::Join {
429		return Err!(Request(BadJson(
430			"join_authorised_via_users_server is only for member joins"
431		)));
432	}
433
434	// Already joined or invited: no restricted-join authorisation needed.
435	if services
436		.state_cache
437		.user_membership(&target_user, room_id)
438		.await
439		.is_some_and(|m| matches!(m, MembershipState::Join | MembershipState::Invite))
440	{
441		return Ok(());
442	}
443
444	if !services.globals.user_is_local(&authorising_user) {
445		return Err!(Request(InvalidParam(
446			"Authorising user {authorising_user} does not belong to this homeserver"
447		)));
448	}
449
450	services
451		.state_cache
452		.is_joined(&authorising_user, room_id)
453		.map(is_false!())
454		.map(BoolExt::into_result)
455		.map_err(|()| {
456			err!(Request(InvalidParam(
457				"Authorising user {authorising_user} is not in the room. They cannot authorise \
458				 the join."
459			)))
460		})
461		.await
462}