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 => {
224			return Err!(Request(BadJson(debug_warn!(
225				?room_id,
226				"You cannot update m.room.create after a room has been created."
227			))));
228		},
229		| StateEventType::RoomServerAcl => {
230			// prevents common ACL paw-guns as ACL management is difficult and prone to
231			// irreversible mistakes
232			match json.deserialize_as_unchecked::<RoomServerAclEventContent>() {
233				| Ok(acl_content) => {
234					if acl_content.allow_is_empty() {
235						return Err!(Request(BadJson(debug_warn!(
236							?room_id,
237							"Sending an ACL event with an empty allow key will permanently \
238							 brick the room for non-tuwunel's as this equates to no servers \
239							 being allowed to participate in this room."
240						))));
241					}
242
243					if acl_content.deny_contains("*") && acl_content.allow_contains("*") {
244						return Err!(Request(BadJson(debug_warn!(
245							?room_id,
246							"Sending an ACL event with a deny and allow key value of \"*\" will \
247							 permanently brick the room for non-tuwunel's as this equates to no \
248							 servers being allowed to participate in this room."
249						))));
250					}
251
252					if acl_content.deny_contains("*")
253						&& !acl_content.is_allowed(services.globals.server_name())
254						&& !acl_content.allow_contains(services.globals.server_name().as_str())
255					{
256						return Err!(Request(BadJson(debug_warn!(
257							?room_id,
258							"Sending an ACL event with a deny key value of \"*\" and without \
259							 your own server name in the allow key will result in you being \
260							 unable to participate in this room."
261						))));
262					}
263
264					if !acl_content.allow_contains("*")
265						&& !acl_content.is_allowed(services.globals.server_name())
266						&& !acl_content.allow_contains(services.globals.server_name().as_str())
267					{
268						return Err!(Request(BadJson(debug_warn!(
269							?room_id,
270							"Sending an ACL event for an allow key without \"*\" and without \
271							 your own server name in the allow key will result in you being \
272							 unable to participate in this room."
273						))));
274					}
275				},
276				| Err(e) => {
277					return Err!(Request(BadJson(debug_warn!(
278						"Room server ACL event is invalid: {e}"
279					))));
280				},
281			}
282		},
283		| StateEventType::RoomEncryption =>
284		// Forbid m.room.encryption if encryption is disabled
285			if !services.config.allow_encryption {
286				return Err!(Request(Forbidden("Encryption is disabled on this homeserver.")));
287			},
288		| StateEventType::RoomJoinRules => {
289			// admin room is a sensitive room, it should not ever be made public
290			if let Ok(admin_room_id) = services.admin.get_admin_room().await
291				&& admin_room_id == room_id
292			{
293				match json.deserialize_as_unchecked::<RoomJoinRulesEventContent>() {
294					| Ok(join_rule) =>
295						if join_rule.join_rule == JoinRule::Public {
296							return Err!(Request(Forbidden(
297								"Admin room is a sensitive room, it cannot be made public"
298							)));
299						},
300					| Err(e) => {
301						return Err!(Request(BadJson(debug_warn!(
302							"Room join rules event is invalid: {e}"
303						))));
304					},
305				}
306			}
307		},
308		| StateEventType::RoomHistoryVisibility => {
309			// admin room is a sensitive room, it should not ever be made world readable
310			if let Ok(admin_room_id) = services.admin.get_admin_room().await {
311				match json.deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>() {
312					| Ok(visibility_content) => {
313						if admin_room_id == room_id
314							&& visibility_content.history_visibility
315								== HistoryVisibility::WorldReadable
316						{
317							return Err!(Request(Forbidden(
318								"Admin room is a sensitive room, it cannot be made world \
319								 readable (public room history)."
320							)));
321						}
322					},
323					| Err(e) => {
324						return Err!(Request(BadJson(debug_warn!(
325							"Room history visibility event is invalid: {e}"
326						))));
327					},
328				}
329			}
330		},
331		| StateEventType::RoomCanonicalAlias => {
332			match json.deserialize_as_unchecked::<RoomCanonicalAliasEventContent>() {
333				| Ok(canonical_alias_content) => {
334					let current_event = services
335						.state_accessor
336						.room_state_get_content::<RoomCanonicalAliasEventContent>(
337							room_id,
338							&StateEventType::RoomCanonicalAlias,
339							"",
340						)
341						.await
342						.ok();
343
344					let current_aliases: Vec<OwnedRoomAliasId> = current_event
345						.map(|content| content.aliases().cloned().collect())
346						.unwrap_or_default();
347
348					let aliases = canonical_alias_content
349						.aliases()
350						.filter(|alias| !current_aliases.contains(alias));
351
352					for alias in aliases {
353						let (alias_room_id, _servers) = services
354							.alias
355							.resolve_alias(alias)
356							.await
357							.map_err(|e| {
358								err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}")))
359							})?;
360
361						if alias_room_id != room_id {
362							return Err!(Request(BadAlias(
363								"Room alias {alias} does not belong to room {room_id}"
364							)));
365						}
366					}
367				},
368				| Err(e) => {
369					return Err!(Request(InvalidParam(debug_warn!(
370						"Room canonical alias event is invalid: {e}"
371					))));
372				},
373			}
374		},
375		| StateEventType::RoomMember => {
376			match json.deserialize_as_unchecked::<RoomMemberEventContent>() {
377				| Ok(membership_content) => {
378					let Ok(target_user) = UserId::parse(state_key) else {
379						return Err!(Request(BadJson(
380							"Membership event has invalid or non-existent state key"
381						)));
382					};
383
384					if membership_content.membership == MembershipState::Invite
385						&& services.globals.user_is_local(&target_user)
386						&& services.users.invites_blocked(&target_user).await
387					{
388						return Err!(Request(InviteBlocked(
389							"{target_user} has blocked invites."
390						)));
391					}
392
393					if let Some(authorising_user) =
394						membership_content.join_authorized_via_users_server
395					{
396						if membership_content.membership != MembershipState::Join {
397							return Err!(Request(BadJson(
398								"join_authorised_via_users_server is only for member joins"
399							)));
400						}
401
402						if !services.globals.user_is_local(&authorising_user) {
403							return Err!(Request(InvalidParam(
404								"Authorising user {authorising_user} does not belong to this \
405								 homeserver"
406							)));
407						}
408
409						services
410							.state_cache
411							.is_joined(&authorising_user, room_id)
412							.map(is_false!())
413							.map(BoolExt::into_result)
414							.map_err(|()| {
415								err!(Request(InvalidParam(
416									"Authorising user {authorising_user} is not in the room. \
417									 They cannot authorise the join."
418								)))
419							})
420							.await?;
421					}
422				},
423				| Err(e) => {
424					return Err!(Request(BadJson(
425						"Membership content must have a valid JSON body with at least a valid \
426						 membership state: {e}"
427					)));
428				},
429			}
430		},
431		| _ => (),
432	}
433
434	Ok(())
435}