1use axum::extract::State;
2use futures::{FutureExt, StreamExt, TryFutureExt};
3use ruma::{
4 CanonicalJsonObject, CanonicalJsonValue, MilliSecondsSinceUnixEpoch, OwnedEventId,
5 OwnedRoomAliasId, RoomId, UserId,
6 api::client::state::{
7 get_state_event_for_key::{self, v3::StateEventFormat},
8 get_state_events, send_state_event,
9 },
10 events::{
11 AnyStateEventContent, StateEventType,
12 invite_permission_config::InvitePermission,
13 room::{
14 canonical_alias::RoomCanonicalAliasEventContent,
15 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
16 join_rules::{JoinRule, RoomJoinRulesEventContent},
17 member::{MembershipState, RoomMemberEventContent},
18 server_acl::RoomServerAclEventContent,
19 },
20 },
21 serde::Raw,
22};
23use serde_json::{json, value::to_raw_value};
24use tuwunel_core::{
25 Err, Result, err, is_false,
26 result::NotFound,
27 utils::{BoolExt, json::serialized_len, stream::BroadbandExt},
28};
29use tuwunel_matrix::{
30 Event,
31 pdu::{PduBuilder, PduEvent},
32};
33use tuwunel_service::{Services, profile::check_member_content};
34
35use crate::{Ruma, RumaResponse, with_membership};
36
37pub(crate) async fn send_state_event_for_key_route(
41 State(services): State<crate::State>,
42 body: Ruma<send_state_event::v3::Request>,
43) -> Result<send_state_event::v3::Response> {
44 let sender_user = body.sender_user();
45
46 Ok(send_state_event::v3::Response {
47 event_id: send_state_event_for_key_helper(
48 &services,
49 sender_user,
50 &body.room_id,
51 &body.event_type,
52 &body.body.body,
53 &body.state_key,
54 if body.appservice_info.is_some() {
55 body.timestamp
56 } else {
57 None
58 },
59 )
60 .await?,
61 })
62}
63
64pub(crate) async fn get_state_events_route(
71 State(services): State<crate::State>,
72 body: Ruma<get_state_events::v3::Request>,
73) -> Result<get_state_events::v3::Response> {
74 let sender_user = body.sender_user();
75
76 if !services
77 .state_accessor
78 .user_can_see_state_events(sender_user, &body.room_id)
79 .await
80 {
81 return Err!(Request(Forbidden("You don't have permission to view the room state.")));
82 }
83
84 let encrypted = services
85 .state_accessor
86 .is_encrypted_room(&body.room_id)
87 .await;
88
89 let shortstatehash = services
90 .state_accessor
91 .user_visible_shortstatehash(sender_user, &body.room_id, None)
92 .map(|result| {
93 result?.ok_or_else(|| {
94 err!(Request(Forbidden("You don't have permission to view the room state.")))
95 })
96 })
97 .await?;
98
99 let room_state = services
100 .state_accessor
101 .state_full_pdus(shortstatehash)
102 .map(Event::into_pdu)
103 .map(|pdu| pdu.without_private_unless_sender(Some(sender_user)))
104 .broad_then(async |pdu| with_membership(&services, pdu, sender_user, encrypted).await)
105 .map(Event::into_format)
106 .collect()
107 .await;
108
109 Ok(get_state_events::v3::Response { room_state })
110}
111
112pub(crate) async fn get_state_events_for_key_route(
121 State(services): State<crate::State>,
122 body: Ruma<get_state_event_for_key::v3::Request>,
123) -> Result<get_state_event_for_key::v3::Response> {
124 let sender_user = body.sender_user();
125
126 if !services
127 .state_accessor
128 .user_can_see_state_events(sender_user, &body.room_id)
129 .await
130 {
131 return Err!(Request(NotFound(debug_warn!(
132 "You don't have permission to view the room state."
133 ))));
134 }
135
136 let failed = |e| {
137 err!(Request(NotFound(debug_warn!(
138 room_id = ?body.room_id,
139 event_type = ?body.event_type,
140 "Failed to get state event: {e}.",
141 ))))
142 };
143
144 let shortstatehash = services
145 .state_accessor
146 .user_visible_shortstatehash(sender_user, &body.room_id, None)
147 .map_err(failed)
148 .map(|result| {
149 result?.ok_or_else(|| {
150 err!(Request(NotFound(debug_warn!(
151 "You don't have permission to view the room state."
152 ))))
153 })
154 })
155 .await?;
156
157 let event = services
158 .state_accessor
159 .state_get(shortstatehash, &body.event_type, &body.state_key)
160 .map_err(failed)
161 .map_ok(|event| event.without_private_unless_sender(Some(sender_user)))
162 .await?;
163
164 let event_or_content = match body.format {
165 | StateEventFormat::Event => json!({
166 "content": event.content(),
167 "event_id": event.event_id(),
168 "origin_server_ts": event.origin_server_ts(),
169 "room_id": event.room_id(),
170 "sender": event.sender(),
171 "state_key": event.state_key(),
172 "type": event.kind(),
173 "unsigned": event.unsigned(),
174 }),
175
176 | _ => event.get_content_as_value(),
177 };
178
179 let event_or_content = to_raw_value(&event_or_content).expect("serializable JSON value");
180
181 Ok(get_state_event_for_key::v3::Response::new(event_or_content))
182}
183
184pub(crate) async fn get_state_events_for_empty_key_route(
193 State(services): State<crate::State>,
194 body: Ruma<get_state_event_for_key::v3::Request>,
195) -> Result<RumaResponse<get_state_event_for_key::v3::Response>> {
196 get_state_events_for_key_route(State(services), body)
197 .await
198 .map(RumaResponse)
199}
200
201async fn send_state_event_for_key_helper(
202 services: &Services,
203 sender: &UserId,
204 room_id: &RoomId,
205 event_type: &StateEventType,
206 json: &Raw<AnyStateEventContent>,
207 state_key: &str,
208 timestamp: Option<MilliSecondsSinceUnixEpoch>,
209) -> Result<OwnedEventId> {
210 allowed_to_send_state_event(services, sender, room_id, event_type, state_key, json).await?;
211
212 let state_lock = services.state.mutex.lock(room_id).await;
213
214 if *event_type == StateEventType::RoomMember {
215 check_member_content_size(services, sender, room_id, state_key, json).await?;
216 }
217
218 let current = match state_dedup_eligible(event_type, timestamp.as_ref()) {
219 | false => None,
220 | true => services
221 .state_accessor
222 .room_state_get(room_id, event_type, state_key)
223 .await
224 .optional()?,
225 };
226
227 if let Some(current) = current
228 && current.sender() == sender
229 {
230 let content = json.deserialize_as_unchecked::<CanonicalJsonObject>()?;
231
232 if is_duplicate_state(event_type, sender, &content, ¤t)?
233 && services
234 .state_cache
235 .is_joined(sender, room_id)
236 .await
237 {
238 return Ok(current.event_id().to_owned());
239 }
240 }
241
242 let event_id = services
243 .timeline
244 .build_and_append_pdu(
245 PduBuilder {
246 event_type: event_type.to_string().into(),
247 content: serde_json::from_str(json.json().get())?,
248 state_key: Some(state_key.into()),
249 timestamp,
250 ..Default::default()
251 },
252 sender,
253 room_id,
254 &state_lock,
255 )
256 .await?;
257
258 Ok(event_id)
259}
260
261#[tracing::instrument(level = "debug", skip_all)]
268pub(crate) async fn check_member_content_size(
269 services: &Services,
270 sender: &UserId,
271 room_id: &RoomId,
272 state_key: &str,
273 json: &Raw<AnyStateEventContent>,
274) -> Result {
275 let content = json.deserialize_as_unchecked::<CanonicalJsonObject>()?;
276 let max_bytes = services.config.profile.member_event_max_bytes;
277 let length = serialized_len(&content)?;
278
279 let string_field = |field: &str| {
281 content
282 .get(field)
283 .and_then(CanonicalJsonValue::as_str)
284 };
285
286 let check = |displayname, avatar_url| {
287 check_member_content(max_bytes, length, displayname, avatar_url)
288 };
289
290 if check(string_field("displayname"), string_field("avatar_url")).is_ok() {
291 return Ok(());
292 }
293
294 let stored = services
295 .state_accessor
296 .room_state_get_content::<CanonicalJsonObject>(
297 room_id,
298 &StateEventType::RoomMember,
299 state_key,
300 )
301 .map(Result::ok)
302 .await;
303
304 if sender.as_str() == state_key && stored.as_ref() == Some(&content) {
306 return Ok(());
307 }
308
309 let is_new = |field: &str| {
310 stored
311 .as_ref()
312 .and_then(|stored| stored.get(field))
313 .ne(&content.get(field))
314 };
315
316 let changed = |field: &str| string_field(field).filter(|_| is_new(field));
317
318 check(changed("displayname"), changed("avatar_url"))
319}
320
321fn state_dedup_eligible(
322 event_type: &StateEventType,
323 timestamp: Option<&MilliSecondsSinceUnixEpoch>,
324) -> bool {
325 timestamp.is_none() && !matches!(event_type, StateEventType::RoomMember)
326}
327
328fn is_duplicate_state(
338 event_type: &StateEventType,
339 sender: &UserId,
340 content: &CanonicalJsonObject,
341 current: &PduEvent,
342) -> Result<bool> {
343 if matches!(event_type, StateEventType::RoomMember) || current.sender() != sender {
344 return Ok(false);
345 }
346
347 let current_content = current.content.deserialize()?;
348
349 Ok(current_content == *content)
350}
351
352pub(crate) async fn allowed_to_send_state_event(
360 services: &Services,
361 sender: &UserId,
362 room_id: &RoomId,
363 event_type: &StateEventType,
364 state_key: &str,
365 json: &Raw<AnyStateEventContent>,
366) -> Result {
367 let suspended = services.users.is_suspended(sender).await;
368
369 if suspended && !matches!(event_type, StateEventType::RoomMember) {
370 return Err!(Request(UserSuspended("Account is suspended.")));
371 }
372
373 match event_type {
374 | StateEventType::RoomCreate => Err!(Request(BadJson(debug_warn!(
375 ?room_id,
376 "You cannot update m.room.create after a room has been created."
377 )))),
378 | StateEventType::RoomServerAcl => validate_server_acl(services, room_id, json),
379 | StateEventType::RoomEncryption => validate_encryption(services),
380 | StateEventType::RoomJoinRules => validate_join_rules(services, room_id, json).await,
381 | StateEventType::RoomHistoryVisibility =>
382 validate_history_visibility(services, room_id, json).await,
383 | StateEventType::RoomCanonicalAlias =>
384 validate_canonical_alias(services, room_id, json).await,
385 | StateEventType::RoomMember =>
386 validate_member(services, sender, room_id, state_key, json, suspended).await,
387 | _ => Ok(()),
388 }
389}
390
391fn validate_encryption(services: &Services) -> Result {
392 services
393 .config
394 .encryption
395 .allow
396 .then_some(())
397 .ok_or_else(|| err!(Request(Forbidden("Encryption is disabled on this homeserver."))))
398}
399
400fn validate_server_acl(
401 services: &Services,
402 room_id: &RoomId,
403 json: &Raw<AnyStateEventContent>,
404) -> Result {
405 let acl_content = json
406 .deserialize_as_unchecked::<RoomServerAclEventContent>()
407 .map_err(|e| {
408 err!(Request(BadJson(debug_warn!("Room server ACL event is invalid: {e}"))))
409 })?;
410
411 if acl_content.allow_is_empty() {
412 return Err!(Request(BadJson(debug_warn!(
413 ?room_id,
414 "Sending an ACL event with an empty allow key will permanently brick the room for \
415 non-tuwunel's as this equates to no servers being allowed to participate in this \
416 room."
417 ))));
418 }
419
420 if acl_content.deny_contains("*") && acl_content.allow_contains("*") {
421 return Err!(Request(BadJson(debug_warn!(
422 ?room_id,
423 "Sending an ACL event with a deny and allow key value of \"*\" will permanently \
424 brick the room for non-tuwunel's as this equates to no servers being allowed to \
425 participate in this room."
426 ))));
427 }
428
429 let server_name = services.globals.server_name();
430 let self_allowed =
431 acl_content.is_allowed(server_name) || acl_content.allow_contains(server_name.as_str());
432
433 if acl_content.deny_contains("*") && !self_allowed {
434 return Err!(Request(BadJson(debug_warn!(
435 ?room_id,
436 "Sending an ACL event with a deny key value of \"*\" and without your own server \
437 name in the allow key will result in you being unable to participate in this room."
438 ))));
439 }
440
441 if !acl_content.allow_contains("*") && !self_allowed {
442 return Err!(Request(BadJson(debug_warn!(
443 ?room_id,
444 "Sending an ACL event for an allow key without \"*\" and without your own server \
445 name in the allow key will result in you being unable to participate in this room."
446 ))));
447 }
448
449 Ok(())
450}
451
452async fn validate_join_rules(
453 services: &Services,
454 room_id: &RoomId,
455 json: &Raw<AnyStateEventContent>,
456) -> Result {
457 let Ok(admin_room_id) = services.admin.get_admin_room().await else {
458 return Ok(());
459 };
460
461 if admin_room_id != room_id {
462 return Ok(());
463 }
464
465 let join_rule = json
466 .deserialize_as_unchecked::<RoomJoinRulesEventContent>()
467 .map_err(|e| {
468 err!(Request(BadJson(debug_warn!("Room join rules event is invalid: {e}"))))
469 })?;
470
471 if join_rule.join_rule == JoinRule::Public {
472 return Err!(Request(Forbidden(
473 "Admin room is a sensitive room, it cannot be made public"
474 )));
475 }
476
477 Ok(())
478}
479
480async fn validate_history_visibility(
481 services: &Services,
482 room_id: &RoomId,
483 json: &Raw<AnyStateEventContent>,
484) -> Result {
485 let Ok(admin_room_id) = services.admin.get_admin_room().await else {
486 return Ok(());
487 };
488
489 let visibility_content = json
490 .deserialize_as_unchecked::<RoomHistoryVisibilityEventContent>()
491 .map_err(|e| {
492 err!(Request(BadJson(debug_warn!("Room history visibility event is invalid: {e}"))))
493 })?;
494
495 if admin_room_id == room_id
496 && visibility_content.history_visibility == HistoryVisibility::WorldReadable
497 {
498 return Err!(Request(Forbidden(
499 "Admin room is a sensitive room, it cannot be made world readable (public room \
500 history)."
501 )));
502 }
503
504 Ok(())
505}
506
507async fn validate_canonical_alias(
508 services: &Services,
509 room_id: &RoomId,
510 json: &Raw<AnyStateEventContent>,
511) -> Result {
512 let canonical_alias_content = json
513 .deserialize_as_unchecked::<RoomCanonicalAliasEventContent>()
514 .map_err(|e| {
515 err!(Request(InvalidParam(debug_warn!("Room canonical alias event is invalid: {e}"))))
516 })?;
517
518 let current_aliases: Vec<OwnedRoomAliasId> = services
519 .state_accessor
520 .room_state_get_content::<RoomCanonicalAliasEventContent>(
521 room_id,
522 &StateEventType::RoomCanonicalAlias,
523 "",
524 )
525 .await
526 .ok()
527 .map(|content| content.aliases().cloned().collect())
528 .unwrap_or_default();
529
530 let new_aliases = canonical_alias_content
531 .aliases()
532 .filter(|alias| !current_aliases.contains(alias));
533
534 for alias in new_aliases {
535 let (alias_room_id, _servers) = services
536 .alias
537 .resolve_alias(alias)
538 .await
539 .map_err(|e| err!(Request(BadAlias("Failed resolving alias \"{alias}\": {e}"))))?;
540
541 if alias_room_id != room_id {
542 return Err!(Request(BadAlias(
543 "Room alias {alias} does not belong to room {room_id}"
544 )));
545 }
546 }
547
548 Ok(())
549}
550
551async fn validate_member(
552 services: &Services,
553 sender: &UserId,
554 room_id: &RoomId,
555 state_key: &str,
556 json: &Raw<AnyStateEventContent>,
557 suspended: bool,
558) -> Result {
559 let membership_content = json
560 .deserialize_as_unchecked::<RoomMemberEventContent>()
561 .map_err(|e| {
562 err!(Request(BadJson(
563 "Membership content must have a valid JSON body with at least a valid \
564 membership state: {e}"
565 )))
566 })?;
567
568 let Ok(target_user) = UserId::parse(state_key) else {
569 return Err!(Request(BadJson("Membership event has invalid or non-existent state key")));
570 };
571
572 if suspended
573 && (membership_content.membership != MembershipState::Leave || target_user != sender)
574 {
575 return Err!(Request(UserSuspended("Account is suspended.")));
576 }
577
578 if !matches!(membership_content.membership, MembershipState::Leave | MembershipState::Ban)
579 && services.metadata.is_banned(room_id).await
580 && !services.admin.user_is_admin(sender).await
581 {
582 return Err!(Request(Forbidden("This room is banned on this homeserver.")));
583 }
584
585 if membership_content.membership == MembershipState::Invite
586 && services.globals.user_is_local(&target_user)
587 && services
588 .users
589 .invite_permission(sender, &target_user)
590 .await
591 .eq(&InvitePermission::Block)
592 {
593 return Err!(Request(InviteBlocked("{target_user} has blocked this invite.")));
594 }
595
596 let Some(authorising_user) = membership_content.join_authorized_via_users_server else {
597 return Ok(());
598 };
599
600 if membership_content.membership != MembershipState::Join {
601 return Err!(Request(BadJson(
602 "join_authorised_via_users_server is only for member joins"
603 )));
604 }
605
606 if services
608 .state_cache
609 .user_membership(&target_user, room_id)
610 .await
611 .is_some_and(|m| matches!(m, MembershipState::Join | MembershipState::Invite))
612 {
613 return Ok(());
614 }
615
616 if !services.globals.user_is_local(&authorising_user) {
617 return Err!(Request(InvalidParam(
618 "Authorising user {authorising_user} does not belong to this homeserver"
619 )));
620 }
621
622 services
623 .state_cache
624 .is_joined(&authorising_user, room_id)
625 .map(is_false!())
626 .map(BoolExt::into_result)
627 .map_err(|()| {
628 err!(Request(InvalidParam(
629 "Authorising user {authorising_user} is not in the room. They cannot authorise \
630 the join."
631 )))
632 })
633 .await
634}
635
636#[cfg(test)]
637mod tests {
638 use ruma::user_id;
639 use serde_json::{Value as JsonValue, from_str, from_value};
640
641 use super::*;
642
643 fn current_state(sender: &str, content: &JsonValue) -> PduEvent {
644 from_value(json!({
645 "type": "m.room.history_visibility",
646 "content": content,
647 "state_key": "",
648 "event_id": "$event:example.com",
649 "room_id": "!room:example.com",
650 "sender": sender,
651 "prev_events": [],
652 "auth_events": [],
653 "origin_server_ts": 1,
654 "depth": 1,
655 "hashes": { "sha256": "thishashcoversallfieldsincasethisisredacted" },
656 }))
657 .expect("valid pdu")
658 }
659
660 #[test]
661 fn identical_state_content_is_duplicate() {
662 let sender = user_id!("@alice:example.com");
663 let current = current_state(
664 sender.as_str(),
665 &json!({ "history_visibility": "shared", "extra": true }),
666 );
667
668 let content = from_str::<CanonicalJsonObject>(
669 r#"{ "extra": true, "history_visibility": "shared" }"#,
670 )
671 .expect("canonical content");
672
673 assert!(
674 is_duplicate_state(
675 &StateEventType::RoomHistoryVisibility,
676 sender,
677 &content,
678 ¤t,
679 )
680 .expect("comparison")
681 );
682 }
683
684 #[test]
685 fn changed_state_content_is_not_duplicate() {
686 let sender = user_id!("@alice:example.com");
687 let current = current_state(sender.as_str(), &json!({ "history_visibility": "shared" }));
688 let content =
689 from_str(r#"{ "history_visibility": "world_readable" }"#).expect("canonical content");
690
691 assert!(
692 !is_duplicate_state(
693 &StateEventType::RoomHistoryVisibility,
694 sender,
695 &content,
696 ¤t,
697 )
698 .expect("comparison")
699 );
700 }
701
702 #[test]
703 fn different_sender_is_not_duplicate() {
704 let current =
705 current_state("@alice:example.com", &json!({ "history_visibility": "shared" }));
706
707 let content =
708 from_str(r#"{ "history_visibility": "shared" }"#).expect("canonical content");
709
710 assert!(
711 !is_duplicate_state(
712 &StateEventType::RoomHistoryVisibility,
713 user_id!("@bob:example.com"),
714 &content,
715 ¤t,
716 )
717 .expect("comparison")
718 );
719 }
720
721 #[test]
722 fn member_state_is_not_duplicate() {
723 let sender = user_id!("@alice:example.com");
724 let current = current_state(sender.as_str(), &json!({ "membership": "join" }));
725 let content = from_str(r#"{ "membership": "join" }"#).expect("canonical content");
726
727 assert!(
728 !is_duplicate_state(&StateEventType::RoomMember, sender, &content, ¤t)
729 .expect("comparison")
730 );
731 }
732
733 #[test]
734 fn timestamped_state_is_not_eligible_for_dedup() {
735 let event_type = StateEventType::RoomHistoryVisibility;
736 let timestamp = MilliSecondsSinceUnixEpoch::now();
737
738 assert!(state_dedup_eligible(&event_type, None));
739 assert!(!state_dedup_eligible(&event_type, Some(×tamp)));
740 }
741}