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
31pub(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
58pub(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
71pub(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
110pub(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
167pub(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 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}