1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use futures::{FutureExt, StreamExt};
5use itertools::Itertools;
6use ruma::{
7 CanonicalJsonObject, CanonicalJsonValue, EventEncryptionAlgorithm, Int, OwnedRoomAliasId,
8 OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, RoomVersionId, UserId,
9 api::client::room::{
10 self,
11 create_room::{
12 self, RoomPowerLevelsContentOverride,
13 v3::{CreationContent, RoomPreset},
14 },
15 },
16 events::{
17 StateEventType, TimelineEventType,
18 ignored_user_list::IgnoredUserListEventContent,
19 room::{
20 canonical_alias::RoomCanonicalAliasEventContent,
21 create::{PreviousRoom, RoomCreateEventContent},
22 encryption::RoomEncryptionEventContent,
23 guest_access::{GuestAccess, RoomGuestAccessEventContent},
24 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
25 join_rules::{JoinRule, RoomJoinRulesEventContent},
26 member::{MembershipState, RoomMemberEventContent},
27 name::RoomNameEventContent,
28 power_levels::RoomPowerLevelsEventContent,
29 topic::RoomTopicEventContent,
30 },
31 },
32 int,
33 room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
34 serde::{JsonObject, Raw},
35};
36use serde::Deserialize;
37use serde_json::{
38 Value as JsonValue, json,
39 value::{RawValue as RawJsonValue, to_raw_value},
40};
41use tuwunel_core::{
42 Err, Error, Result, debug_info, debug_warn, err, info,
43 utils::{
44 BoolExt, IterStream, ReadyExt, json::serialized_len, option::OptionExt, result::FlatOk,
45 },
46 warn,
47};
48use tuwunel_matrix::{
49 StateKey,
50 pdu::{Content, PduBuilder},
51 room_version,
52};
53use tuwunel_service::{
54 Services, appservice::RegistrationInfo, profile::check_member_content,
55 rooms::state::RoomMutexGuard,
56};
57
58use crate::{Ruma, utils::invite_check};
59
60pub(crate) async fn create_room_route(
61 State(services): State<crate::State>,
62 body: Ruma<create_room::v3::Request>,
63) -> Result<create_room::v3::Response> {
64 can_create_room_check(&services, &body).await?;
65 can_publish_directory_check(&services, &body).await?;
66 let initial_events = parse_initial_state(&body)?;
67
68 check_initial_member_content(&services, &initial_events)?;
69
70 let preset = body
72 .preset
73 .clone()
74 .unwrap_or(match &body.visibility {
75 | room::Visibility::Public => RoomPreset::PublicChat,
76 | _ => RoomPreset::PrivateChat, });
78
79 let (room_version, version_rules) = body
81 .room_version
82 .as_ref()
83 .map_or(Ok(&services.server.config.rooms.default_version), |version| {
84 services
85 .config
86 .supported_room_version(version)
87 .then_ok_or_else(version, || {
88 err!(Request(UnsupportedRoomVersion(
89 "This server does not support room version {version:?}"
90 )))
91 })
92 })
93 .and_then(|version| Ok((version, room_version::rules(version)?)))?;
94
95 let sender_user = body.sender_user();
96
97 let alias = body
99 .room_alias_name
100 .as_ref()
101 .map_async(|alias| {
102 room_alias_check(&services, alias, sender_user, body.appservice_info.as_ref())
103 })
104 .await
105 .transpose()?;
106
107 let next_count = services.globals.next_count();
110
111 let (room_id, state_lock) = match version_rules.room_id_format {
113 | RoomIdFormatVersion::V1 =>
114 create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
115 | RoomIdFormatVersion::V2 =>
116 create_create_event(&services, &body, &preset, room_version, &version_rules)
117 .await
118 .map_err(|e| match e {
119 | Error::Request(..) | Error::BadRequest(..) => e,
120 | e => err!(Request(InvalidParam(
121 "Error while creating m.room.create event: {e}"
122 ))),
123 })?,
124 };
125
126 apply_creator_join_pdu(&services, &body, sender_user, &room_id, &state_lock)
128 .boxed() .await?;
130
131 apply_power_levels_pdu(
133 &services,
134 &body,
135 &preset,
136 &version_rules,
137 sender_user,
138 &room_id,
139 &state_lock,
140 )
141 .boxed() .await?;
143
144 if let Some(room_alias_id) = &alias {
146 apply_canonical_alias_pdu(&services, room_alias_id, sender_user, &room_id, &state_lock)
147 .boxed() .await?;
149 }
150
151 let initial_state = apply_preset_state_pdus(
153 &services,
154 initial_events,
155 &preset,
156 sender_user,
157 &room_id,
158 &state_lock,
159 )
160 .boxed() .await?;
162
163 apply_initial_state_pdus(
165 &services,
166 initial_state,
167 &preset,
168 sender_user,
169 &room_id,
170 &state_lock,
171 )
172 .boxed() .await?;
174
175 apply_name_and_topic_pdus(&services, &body, sender_user, &room_id, &state_lock)
177 .boxed() .await?;
179
180 drop(next_count);
181 drop(state_lock);
182
183 if (!body.invite.is_empty() || !body.invite_3pid.is_empty())
185 && invite_check(&services, sender_user, &room_id)
186 .await
187 .is_ok()
188 {
189 process_invites(&services, &body, sender_user, &room_id)
190 .boxed() .await;
192 }
193
194 finalize_alias_and_directory(&services, &body, alias.as_deref(), sender_user, &room_id)
195 .await?;
196
197 copy_creator_predecessor_push_rule(
198 &services,
199 body.creation_content.as_ref(),
200 sender_user,
201 &room_id,
202 )
203 .await;
204
205 info!("{sender_user} created a room with room ID {room_id}");
206
207 Ok(create_room::v3::Response::new(room_id))
208}
209
210async fn apply_creator_join_pdu(
211 services: &Services,
212 body: &Ruma<create_room::v3::Request>,
213 sender_user: &UserId,
214 room_id: &RoomId,
215 state_lock: &RoomMutexGuard,
216) -> Result {
217 let content = services
218 .profile
219 .fill_content(sender_user, RoomMemberEventContent {
220 is_direct: body.is_direct,
221 ..RoomMemberEventContent::new(MembershipState::Join)
222 })
223 .await;
224
225 services
226 .timeline
227 .build_and_append_pdu(
228 PduBuilder::state(sender_user.to_string(), &content),
229 sender_user,
230 room_id,
231 state_lock,
232 )
233 .await
234 .map(|_| ())
235}
236
237async fn apply_power_levels_pdu(
238 services: &Services,
239 body: &Ruma<create_room::v3::Request>,
240 preset: &RoomPreset,
241 version_rules: &RoomVersionRules,
242 sender_user: &UserId,
243 room_id: &RoomId,
244 state_lock: &RoomMutexGuard,
245) -> Result {
246 let users =
247 build_power_levels_users(services, body, preset, version_rules, sender_user).await;
248
249 let default_override = services
250 .config
251 .rooms
252 .default_power_level_content_override
253 .as_ref();
254
255 let power_levels_content = default_power_levels_content(
256 version_rules,
257 default_override,
258 body.power_level_content_override.as_ref(),
259 preset,
260 users,
261 )?;
262
263 services
264 .timeline
265 .build_and_append_pdu(
266 PduBuilder {
267 event_type: TimelineEventType::RoomPowerLevels,
268 content: to_raw_value(&power_levels_content)?.into(),
269 state_key: Some(StateKey::new()),
270 ..Default::default()
271 },
272 sender_user,
273 room_id,
274 state_lock,
275 )
276 .await
277 .map(|_| ())
278}
279
280async fn build_power_levels_users(
281 services: &Services,
282 body: &Ruma<create_room::v3::Request>,
283 preset: &RoomPreset,
284 version_rules: &RoomVersionRules,
285 sender_user: &UserId,
286) -> BTreeMap<OwnedUserId, Int> {
287 let seed = version_rules
288 .authorization
289 .explicitly_privilege_room_creators
290 .or(|| (sender_user.to_owned(), int!(100)))
291 .into_iter()
292 .collect::<BTreeMap<_, _>>();
293
294 let trusted_invitees = *preset == RoomPreset::TrustedPrivateChat
295 && !version_rules
296 .authorization
297 .additional_room_creators;
298
299 if !trusted_invitees {
300 return seed;
301 }
302
303 let ignored = services.users.ignored_users(sender_user).await;
304
305 body.invite
306 .iter()
307 .filter(|invite| invite_allowed(ignored.as_ref(), invite))
308 .fold(seed, |mut users, invite| {
309 users.insert(invite.clone(), int!(100));
310 users
311 })
312}
313
314async fn apply_canonical_alias_pdu(
315 services: &Services,
316 room_alias_id: &RoomAliasId,
317 sender_user: &UserId,
318 room_id: &RoomId,
319 state_lock: &RoomMutexGuard,
320) -> Result {
321 services
322 .timeline
323 .build_and_append_pdu(
324 PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
325 alias: Some(room_alias_id.to_owned()),
326 alt_aliases: vec![],
327 }),
328 sender_user,
329 room_id,
330 state_lock,
331 )
332 .await
333 .map(|_| ())
334}
335
336async fn apply_preset_state_pdus(
337 services: &Services,
338 initial_events: impl IntoIterator<Item = InitialEvent>,
339 preset: &RoomPreset,
340 sender_user: &UserId,
341 room_id: &RoomId,
342 state_lock: &RoomMutexGuard,
343) -> Result<Vec<InitialEvent>> {
344 let mut initial_state = initial_events
345 .into_iter()
346 .filter(|event| {
347 services.config.encryption.allow || event.event_type != StateEventType::RoomEncryption
348 })
349 .filter(|event| {
350 if is_empty_content(event) {
354 debug_warn!("skipping empty initial state event of type {}", event.event_type);
355 false
356 } else {
357 true
358 }
359 })
360 .collect::<Vec<_>>();
361
362 let join_rule_pdubuilder =
363 take_initial(&mut initial_state, &StateEventType::RoomJoinRules, "")
364 .map(Into::into)
365 .unwrap_or_else(|| {
366 PduBuilder::state(
367 String::new(),
368 &RoomJoinRulesEventContent::new(match preset {
369 | RoomPreset::PublicChat => JoinRule::Public,
370 | _ => JoinRule::Invite,
372 }),
373 )
374 });
375
376 let history_visibility_pdubuilder =
377 take_initial(&mut initial_state, &StateEventType::RoomHistoryVisibility, "")
378 .map(Into::into)
379 .unwrap_or_else(|| {
380 PduBuilder::state(
381 String::new(),
382 &RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
383 )
384 });
385
386 let guest_access = guest_access_pdu(
387 take_initial(&mut initial_state, &StateEventType::RoomGuestAccess, "").map(Into::into),
388 preset,
389 );
390
391 services
393 .timeline
394 .build_and_append_pdu(join_rule_pdubuilder, sender_user, room_id, state_lock)
395 .boxed() .await?;
397
398 services
400 .timeline
401 .build_and_append_pdu(history_visibility_pdubuilder, sender_user, room_id, state_lock)
402 .boxed() .await?;
404
405 if let Some(guest_access) = guest_access {
407 services
408 .timeline
409 .build_and_append_pdu(guest_access, sender_user, room_id, state_lock)
410 .boxed() .await?;
412 }
413
414 Ok(initial_state)
415}
416
417fn guest_access_pdu(initial: Option<PduBuilder>, preset: &RoomPreset) -> Option<PduBuilder> {
418 let can_join = || {
419 PduBuilder::state(String::new(), &RoomGuestAccessEventContent::new(GuestAccess::CanJoin))
420 };
421
422 initial.or_else(|| preset.ne(&RoomPreset::PublicChat).then(can_join))
423}
424
425async fn apply_initial_state_pdus(
426 services: &Services,
427 initial_state: Vec<InitialEvent>,
428 preset: &RoomPreset,
429 sender_user: &UserId,
430 room_id: &RoomId,
431 state_lock: &RoomMutexGuard,
432) -> Result {
433 let is_encrypted = encrypts_room(&initial_state);
434
435 for event in initial_state {
436 services
437 .timeline
438 .build_and_append_pdu(event.into(), sender_user, room_id, state_lock)
439 .boxed() .await?;
441 }
442
443 if !services.config.encryption.allow || is_encrypted {
444 return Ok(());
445 }
446
447 let config = services
448 .config
449 .encryption
450 .enabled_by_default_for_room_type
451 .as_deref();
452
453 let should_encrypt = match config {
454 | Some("all") => true,
455 | Some("invite") =>
456 matches!(preset, RoomPreset::PrivateChat | RoomPreset::TrustedPrivateChat),
457 | _ => false,
458 };
459
460 if !should_encrypt {
461 return Ok(());
462 }
463
464 let algorithm = EventEncryptionAlgorithm::MegolmV1AesSha2;
465 let content = RoomEncryptionEventContent::new(algorithm);
466 services
467 .timeline
468 .build_and_append_pdu(
469 PduBuilder::state(String::new(), &content),
470 sender_user,
471 room_id,
472 state_lock,
473 )
474 .boxed() .await?;
476
477 Ok(())
478}
479
480fn encrypts_room(initial_state: &[InitialEvent]) -> bool {
487 initial_state
488 .iter()
489 .rfind(|event| {
490 event.event_type == StateEventType::RoomEncryption && event.state_key.is_empty()
491 })
492 .and_then(|event| event.content.get_field("algorithm").flat_ok())
493 .is_some_and(|algorithm: &RawJsonValue| algorithm.get().starts_with('"'))
494}
495
496async fn apply_name_and_topic_pdus(
497 services: &Services,
498 body: &Ruma<create_room::v3::Request>,
499 sender_user: &UserId,
500 room_id: &RoomId,
501 state_lock: &RoomMutexGuard,
502) -> Result {
503 if let Some(name) = &body.name {
504 services
505 .timeline
506 .build_and_append_pdu(
507 PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
508 sender_user,
509 room_id,
510 state_lock,
511 )
512 .boxed() .await?;
514 }
515
516 if let Some(topic) = &body.topic {
517 services
518 .timeline
519 .build_and_append_pdu(
520 PduBuilder::state(String::new(), &RoomTopicEventContent::new(topic.clone())),
521 sender_user,
522 room_id,
523 state_lock,
524 )
525 .boxed() .await?;
527 }
528
529 Ok(())
530}
531
532async fn process_invites(
533 services: &Services,
534 body: &Ruma<create_room::v3::Request>,
535 sender_user: &UserId,
536 room_id: &RoomId,
537) {
538 let ignored = services.users.ignored_users(sender_user).await;
540
541 body.invite
542 .iter()
543 .stream()
544 .ready_filter(|user_id| invite_allowed(ignored.as_ref(), user_id))
545 .for_each(async |user_id| {
546 if let Err(e) = services
547 .membership
548 .invite(sender_user, user_id, room_id, None, body.is_direct)
549 .boxed() .await
551 {
552 warn!(%e, "Failed to send invite");
553 }
554 })
555 .await;
556}
557
558fn invite_allowed(ignored: Option<&IgnoredUserListEventContent>, invitee: &UserId) -> bool {
565 ignored.is_none_or(|content| !content.ignored_users.contains_key(invitee))
566}
567
568async fn finalize_alias_and_directory(
569 services: &Services,
570 body: &Ruma<create_room::v3::Request>,
571 alias: Option<&RoomAliasId>,
572 sender_user: &UserId,
573 room_id: &RoomId,
574) -> Result {
575 if let Some(alias) = alias {
576 services
577 .alias
578 .set_alias_by(alias, room_id, sender_user)?;
579 }
580
581 if body.visibility == room::Visibility::Public {
582 services.directory.set_public(room_id, alias);
583
584 services
585 .admin
586 .notify_loud(&format!("{sender_user} made {room_id} public to the room directory"))
587 .await;
588
589 info!("{sender_user} made {0} public to the room directory", room_id);
590 }
591
592 Ok(())
593}
594
595async fn copy_creator_predecessor_push_rule(
596 services: &Services,
597 creation_content: Option<&Raw<CreationContent>>,
598 sender_user: &UserId,
599 room_id: &RoomId,
600) {
601 let Some(from_room) = creation_content
602 .and_then(|content| {
603 content
604 .get_field::<PreviousRoom>("predecessor")
605 .ok()
606 .flatten()
607 })
608 .map(|predecessor| predecessor.room_id)
609 else {
610 return;
611 };
612
613 services
614 .account_data
615 .copy_room_push_rule(sender_user, &from_room, room_id)
616 .await
617 .inspect_err(|e| warn!(%e, "Failed to copy predecessor push rules"))
618 .ok();
619}
620
621async fn create_create_event(
622 services: &Services,
623 body: &Ruma<create_room::v3::Request>,
624 preset: &RoomPreset,
625 room_version: &RoomVersionId,
626 version_rules: &RoomVersionRules,
627) -> Result<(OwnedRoomId, RoomMutexGuard)> {
628 let _sender_user = body.sender_user();
629
630 let mut create_content = match &body.creation_content {
631 | Some(content) => {
632 let mut content = content
633 .deserialize_as_unchecked::<CanonicalJsonObject>()
634 .map_err(|e| {
635 err!(Request(BadJson(error!(
636 "Failed to deserialise content as canonical JSON: {e}"
637 ))))
638 })?;
639
640 if !services.config.rooms.federate_by_default
641 && (!services.config.federation.enable || !content.contains_key("m.federate"))
642 {
643 content.insert("m.federate".into(), json!(false).try_into()?);
644 }
645
646 content.insert(
647 "room_version".into(),
648 json!(room_version.as_str())
649 .try_into()
650 .map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
651 );
652
653 content
654 },
655 | None => {
656 let content = RoomCreateEventContent::new_v11();
657
658 let mut content =
659 serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
660
661 if !services.config.rooms.federate_by_default {
662 content.insert("m.federate".into(), json!(false).try_into()?);
663 }
664
665 content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
666 content
667 },
668 };
669
670 if version_rules
671 .authorization
672 .additional_room_creators
673 {
674 let mut additional_creators = body
675 .creation_content
676 .as_ref()
677 .and_then(|c| {
678 c.deserialize_as_unchecked::<CreationContent>()
679 .ok()
680 })
681 .unwrap_or_default()
682 .additional_creators;
683
684 if *preset == RoomPreset::TrustedPrivateChat {
685 additional_creators.extend(body.invite.clone());
686 }
687
688 let additional_creators = additional_creators
689 .into_iter()
690 .sorted()
691 .dedup()
692 .collect_vec();
693
694 if !additional_creators.is_empty() {
695 create_content
696 .insert("additional_creators".into(), json!(additional_creators).try_into()?);
697 }
698 }
699
700 let expected_room_id = body
701 .room_id
702 .as_deref()
703 .map(expected_room_id_check)
704 .transpose()?;
705
706 let create_event_id = services
707 .timeline
708 .build_and_append_create_pdu(
709 create_content,
710 body.sender_user(),
711 body.origin_server_ts,
712 expected_room_id,
713 )
714 .boxed() .await?;
716
717 let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
719 let state_lock = services.state.mutex.lock(&room_id).await;
720
721 Ok((room_id, state_lock))
722}
723
724fn expected_room_id_check(expected: &str) -> Result<&str> {
726 let hashed = expected.starts_with('!') && !expected.contains(':');
727
728 hashed.then_some(expected).ok_or_else(|| {
729 err!(Request(InvalidParam(
730 "This room version derives the room ID from the create event; only that ID can be \
731 expected."
732 )))
733 })
734}
735
736async fn create_create_event_legacy(
737 services: &Services,
738 body: &Ruma<create_room::v3::Request>,
739 room_version: &RoomVersionId,
740 version_rules: &RoomVersionRules,
741) -> Result<(OwnedRoomId, RoomMutexGuard)> {
742 let room_id: OwnedRoomId = match &body.room_id {
743 | None => RoomId::new_v1(&services.server.name),
744 | Some(custom_id) => custom_room_id_check(services, custom_id).await?,
745 };
746
747 let state_lock = services.state.mutex.lock(&room_id).await;
748
749 let _short_id = services
750 .short
751 .get_or_create_shortroomid(&room_id)
752 .await;
753
754 let create_content = match &body.creation_content {
755 | Some(content) => {
756 let mut content = content
757 .deserialize_as_unchecked::<CanonicalJsonObject>()
758 .map_err(|e| {
759 err!(Request(BadJson(error!(
760 "Failed to deserialise content as canonical JSON: {e}"
761 ))))
762 })?;
763
764 if !version_rules.authorization.use_room_create_sender {
765 content.insert(
766 "creator".into(),
767 json!(body.sender_user())
768 .try_into()
769 .map_err(|e| {
770 err!(Request(BadJson(debug_error!("Invalid creation content: {e}"))))
771 })?,
772 );
773 }
774
775 if !services.config.rooms.federate_by_default
776 && (!services.config.federation.enable || !content.contains_key("m.federate"))
777 {
778 content.insert("m.federate".into(), json!(false).try_into()?);
779 }
780
781 content.insert(
782 "room_version".into(),
783 json!(room_version.as_str())
784 .try_into()
785 .map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
786 );
787
788 content
789 },
790 | None => {
791 let content = if !version_rules.authorization.use_room_create_sender {
792 RoomCreateEventContent::new_v1(body.sender_user().to_owned())
793 } else {
794 RoomCreateEventContent::new_v11()
795 };
796
797 let mut content =
798 serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
799
800 if !services.config.rooms.federate_by_default {
801 content.insert("m.federate".into(), json!(false).try_into()?);
802 }
803
804 content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
805 content
806 },
807 };
808
809 services
811 .timeline
812 .build_and_append_pdu(
813 PduBuilder {
814 event_type: TimelineEventType::RoomCreate,
815 content: to_raw_value(&create_content)?.into(),
816 state_key: Some(StateKey::new()),
817 ..Default::default()
818 },
819 body.sender_user(),
820 &room_id,
821 &state_lock,
822 )
823 .boxed() .await?;
825
826 Ok((room_id, state_lock))
827}
828
829fn default_power_levels_content(
831 version_rules: &RoomVersionRules,
832 default_power_level_content_override: Option<&JsonValue>,
833 power_level_content_override: Option<&Raw<RoomPowerLevelsContentOverride>>,
834 preset: &RoomPreset,
835 users: BTreeMap<OwnedUserId, Int>,
836) -> Result<JsonValue> {
837 use serde_json::to_value;
838
839 let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
840 power_levels_content.users = users;
841
842 let mut power_levels_content = to_value(power_levels_content)?;
843
844 power_levels_content["events"]["m.room.power_levels"] = json!(100);
847 power_levels_content["events"]["m.room.server_acl"] = json!(100);
848 power_levels_content["events"]["m.room.encryption"] = json!(100);
849 power_levels_content["events"]["m.room.history_visibility"] = json!(100);
850
851 if version_rules
852 .authorization
853 .explicitly_privilege_room_creators
854 {
855 power_levels_content["events"]["m.room.tombstone"] = json!(150);
856 } else {
857 power_levels_content["events"]["m.room.tombstone"] = json!(100);
858 }
859
860 power_levels_content["events"]["org.matrix.msc3381.poll.response"] = json!(0);
863 power_levels_content["events"]["m.poll.response"] = json!(0);
864
865 if *preset == RoomPreset::PublicChat {
868 power_levels_content["invite"] = json!(50);
869 power_levels_content["events"]["m.call.invite"] = json!(50);
870 power_levels_content["events"]["m.call"] = json!(50);
871 power_levels_content["events"]["m.call.member"] = json!(50);
872 power_levels_content["events"]["org.matrix.msc3401.call"] = json!(50);
873 power_levels_content["events"]["org.matrix.msc3401.call.member"] = json!(50);
874 }
875
876 if let Some(default_power_level_content_override) = default_power_level_content_override {
877 let overrides = default_power_level_content_override
878 .as_object()
879 .expect("default_power_level_content_override is validated at startup")
880 .iter()
881 .map(|(key, value)| (key.clone(), value.clone()));
882
883 merge_power_level_content_override(&mut power_levels_content, overrides);
884 }
885
886 if let Some(power_level_content_override) = power_level_content_override {
887 let overrides: JsonObject =
888 serde_json::from_str(power_level_content_override.json().get()).map_err(|e| {
889 err!(Request(BadJson("Invalid power_level_content_override: {e:?}")))
890 })?;
891
892 merge_power_level_content_override(&mut power_levels_content, overrides);
893 }
894
895 Ok(power_levels_content)
896}
897
898fn merge_power_level_content_override(
900 power_levels_content: &mut JsonValue,
901 overrides: impl IntoIterator<Item = (String, JsonValue)>,
902) {
903 power_levels_content
904 .as_object_mut()
905 .expect("power levels content must serialize to an object")
906 .extend(overrides);
907}
908
909async fn room_alias_check(
911 services: &Services,
912 room_alias_name: &str,
913 sender_user: &UserId,
914 appservice_info: Option<&RegistrationInfo>,
915) -> Result<OwnedRoomAliasId> {
916 services
917 .alias
918 .creation_check(sender_user, appservice_info)
919 .await?;
920
921 if room_alias_name.contains(':') {
923 return Err!(Request(InvalidParam(
924 "Room alias contained `:` which is not allowed. Please note that this expects a \
925 localpart, not the full room alias.",
926 )));
927 } else if room_alias_name.contains(char::is_whitespace) {
928 return Err!(Request(InvalidParam(
929 "Room alias contained spaces which is not a valid room alias.",
930 )));
931 }
932
933 if services
935 .config
936 .rooms
937 .forbidden_alias_names
938 .is_match(room_alias_name)
939 {
940 return Err!(Request(Unknown("Room alias name is forbidden.")));
941 }
942
943 let server_name = services.globals.server_name();
944 let full_room_alias = OwnedRoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
945 .map_err(|e| {
946 err!(Request(InvalidParam(debug_error!(
947 ?e,
948 ?room_alias_name,
949 "Failed to parse room alias.",
950 ))))
951 })?;
952
953 if services
954 .alias
955 .resolve_local_alias(&full_room_alias)
956 .await
957 .is_ok()
958 {
959 return Err!(Request(RoomInUse("Room alias already exists.")));
960 }
961
962 if let Some(info) = appservice_info {
963 if !info.aliases.is_match(full_room_alias.as_str()) {
964 return Err!(Request(Exclusive("Room alias is not in namespace.")));
965 }
966 } else if services
967 .appservice
968 .is_exclusive_alias(&full_room_alias)
969 .await
970 {
971 return Err!(Request(Exclusive("Room alias reserved by appservice.",)));
972 }
973
974 debug_info!("Full room alias: {full_room_alias}");
975
976 Ok(full_room_alias)
977}
978
979async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
981 if services
983 .config
984 .rooms
985 .forbidden_alias_names
986 .is_match(custom_room_id)
987 {
988 return Err!(Request(Unknown("Custom room ID is forbidden.")));
989 }
990
991 if custom_room_id.contains(':') {
992 return Err!(Request(InvalidParam(
993 "Custom room ID contained `:` which is not allowed. Please note that this expects a \
994 localpart, not the full room ID.",
995 )));
996 } else if custom_room_id.contains(char::is_whitespace) {
997 return Err!(Request(InvalidParam(
998 "Custom room ID contained spaces which is not valid."
999 )));
1000 }
1001
1002 let server_name = services.globals.server_name();
1003 let full_room_id = format!("!{custom_room_id}:{server_name}");
1004
1005 let room_id = OwnedRoomId::parse(full_room_id)
1006 .inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
1007 .inspect_err(|e| {
1008 warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
1009 })?;
1010
1011 if services
1013 .short
1014 .get_shortroomid(&room_id)
1015 .await
1016 .is_ok()
1017 {
1018 return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
1019 }
1020
1021 Ok(room_id)
1022}
1023
1024async fn can_publish_directory_check(
1025 services: &Services,
1026 body: &Ruma<create_room::v3::Request>,
1027) -> Result {
1028 if !services.server.config.directory.rooms.lockdown
1029 || body.appservice_info.is_some()
1030 || body.visibility != room::Visibility::Public
1031 || services
1032 .admin
1033 .user_is_admin(body.sender_user())
1034 .await
1035 {
1036 return Ok(());
1037 }
1038
1039 let msg = format!(
1040 "Non-admin user {} tried to publish new to the directory while directory.rooms.lockdown \
1041 is enabled",
1042 body.sender_user(),
1043 );
1044
1045 warn!("{msg}");
1046 services.admin.notify(&msg).await;
1047
1048 Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
1049}
1050
1051fn parse_initial_state(body: &Ruma<create_room::v3::Request>) -> Result<Vec<InitialEvent>> {
1054 body.initial_state
1055 .iter()
1056 .map(|state| Ok(state.deserialize_as_unchecked::<InitialEvent>()?))
1057 .filter_ok(|event| body.name.is_none() || event.event_type != StateEventType::RoomName)
1058 .filter_ok(|event| body.topic.is_none() || event.event_type != StateEventType::RoomTopic)
1059 .collect::<Result<Vec<_>>>()
1060}
1061
1062fn check_initial_member_content(services: &Services, initial_events: &[InitialEvent]) -> Result {
1063 let max_bytes = services.config.profile.member_event_max_bytes;
1064
1065 initial_events
1066 .iter()
1067 .filter(|event| event.event_type == StateEventType::RoomMember)
1068 .filter(|event| !is_empty_content(event))
1069 .try_for_each(|event| {
1070 event
1072 .content
1073 .deserialize_as_unchecked::<RoomMemberEventContent>()
1074 .map_err(|e| err!(Request(BadJson("Invalid initial member event: {e}"))))?;
1075
1076 let content = event.content.deserialize()?;
1077 let displayname = content
1078 .get("displayname")
1079 .and_then(CanonicalJsonValue::as_str);
1080
1081 let avatar_url = content
1082 .get("avatar_url")
1083 .and_then(CanonicalJsonValue::as_str);
1084
1085 check_member_content(max_bytes, serialized_len(&content)?, displayname, avatar_url)
1086 })
1087}
1088
1089fn is_empty_content(event: &InitialEvent) -> bool { event.content.json().get() == "{}" }
1090
1091async fn can_create_room_check(
1092 services: &Services,
1093 body: &Ruma<create_room::v3::Request>,
1094) -> Result {
1095 if !services.config.rooms.allow_creation
1096 && body.appservice_info.is_none()
1097 && !services
1098 .admin
1099 .user_is_admin(body.sender_user())
1100 .await
1101 {
1102 return Err!(Request(Forbidden("Room creation has been disabled.",)));
1103 }
1104
1105 Ok(())
1106}
1107
1108#[derive(Deserialize)]
1109struct InitialEvent {
1110 #[serde(rename = "type")]
1111 event_type: StateEventType,
1112
1113 #[serde(default = "StateKey::new")]
1114 state_key: StateKey,
1115
1116 content: Content,
1117}
1118
1119impl From<InitialEvent> for PduBuilder {
1120 fn from(value: InitialEvent) -> Self {
1121 Self {
1122 event_type: value.event_type.into(),
1123 content: value.content,
1124 unsigned: None,
1125 state_key: Some(value.state_key),
1126 redacts: None,
1127 timestamp: None,
1128 }
1129 }
1130}
1131
1132fn take_initial(
1133 initial_state: &mut Vec<InitialEvent>,
1134 event_type: &StateEventType,
1135 state_key: &str,
1136) -> Option<InitialEvent> {
1137 initial_state
1138 .extract_if(.., |event| &event.event_type == event_type && event.state_key == state_key)
1139 .next()
1140}
1141
1142#[cfg(test)]
1143mod tests {
1144 use tuwunel_matrix::room_version::rules;
1145
1146 use super::*;
1147
1148 fn guest_access(pdu: &PduBuilder) -> GuestAccess {
1149 pdu.content
1150 .deserialize_as_unchecked::<RoomGuestAccessEventContent>()
1151 .expect("guest access content")
1152 .guest_access
1153 }
1154
1155 #[test]
1156 fn default_power_levels_content_applies_server_default_override() {
1157 let version_rules = rules(&RoomVersionId::V11).expect("supported room version");
1158
1159 let content = default_power_levels_content(
1160 &version_rules,
1161 Some(&json!({ "users_default": 50 })),
1162 None,
1163 &RoomPreset::PrivateChat,
1164 BTreeMap::new(),
1165 )
1166 .expect("power levels content");
1167
1168 assert_eq!(content["users_default"], json!(50));
1169 }
1170
1171 #[test]
1172 fn request_override_wins_over_server_default_override() {
1173 let version_rules = rules(&RoomVersionId::V11).expect("supported room version");
1174 let request_override =
1175 Raw::from_json(to_raw_value(&json!({ "users_default": 75 })).expect("raw json"));
1176
1177 let content = default_power_levels_content(
1178 &version_rules,
1179 Some(&json!({ "users_default": 50 })),
1180 Some(&request_override),
1181 &RoomPreset::PrivateChat,
1182 BTreeMap::new(),
1183 )
1184 .expect("power levels content");
1185
1186 assert_eq!(content["users_default"], json!(75));
1187 }
1188
1189 #[test]
1190 fn default_override_preserves_explicit_user_power_levels() {
1191 let version_rules = rules(&RoomVersionId::V11).expect("supported room version");
1192 let creator = OwnedUserId::try_from("@alice:example.com").expect("valid user id");
1193 let users = BTreeMap::from([(creator.clone(), int!(100))]);
1194
1195 let content = default_power_levels_content(
1196 &version_rules,
1197 Some(&json!({ "users_default": 50 })),
1198 None,
1199 &RoomPreset::PrivateChat,
1200 users,
1201 )
1202 .expect("power levels content");
1203
1204 assert_eq!(content["users_default"], json!(50));
1205 assert_eq!(content["users"][creator.as_str()], json!(100));
1206 }
1207
1208 #[test]
1209 fn public_chat_omits_default_guest_access() {
1210 assert!(guest_access_pdu(None, &RoomPreset::PublicChat).is_none());
1211 }
1212
1213 #[test]
1214 fn private_presets_default_to_guest_access() {
1215 for preset in [RoomPreset::PrivateChat, RoomPreset::TrustedPrivateChat] {
1216 let pdu = guest_access_pdu(None, &preset).expect("guest access pdu");
1217
1218 assert_eq!(pdu.event_type, TimelineEventType::RoomGuestAccess);
1219 assert_eq!(pdu.state_key.as_deref(), Some(""));
1220 assert_eq!(guest_access(&pdu), GuestAccess::CanJoin);
1221 }
1222 }
1223
1224 #[test]
1225 fn explicit_guest_access_survives_public_preset() {
1226 let explicit = PduBuilder::state(
1227 String::new(),
1228 &RoomGuestAccessEventContent::new(GuestAccess::Forbidden),
1229 );
1230
1231 let pdu = guest_access_pdu(Some(explicit), &RoomPreset::PublicChat)
1232 .expect("explicit guest access pdu");
1233
1234 assert_eq!(guest_access(&pdu), GuestAccess::Forbidden);
1235 }
1236
1237 #[test]
1238 fn encryption_needs_a_string_algorithm() {
1239 for content in [
1240 r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#,
1241 r#"{"algorithm":"\u006d.megolm.v1.aes-sha2"}"#,
1242 r#"{"algorithm":"whatever","rotation_period_ms":604800000}"#,
1243 r#"{"algorithm" : "m.megolm.v1.aes-sha2"}"#,
1244 ] {
1245 assert!(encrypts_room(&[initial_state_event("m.room.encryption", "", content)]));
1246 }
1247 }
1248
1249 fn initial_state_event(event_type: &str, state_key: &str, content: &str) -> InitialEvent {
1250 let json =
1251 format!(r#"{{"type":"{event_type}","state_key":"{state_key}","content":{content}}}"#);
1252
1253 serde_json::from_str(&json).expect("initial state event")
1254 }
1255
1256 #[test]
1257 fn contentless_encryption_leaves_the_forced_default() {
1258 for content in ["{}", "{ }", r#"{"x":1}"#, r#"{"algorithm":1}"#, r#"{"algorithm":null}"#]
1259 {
1260 assert!(!encrypts_room(&[initial_state_event("m.room.encryption", "", content)]));
1261 }
1262 }
1263
1264 #[test]
1265 fn the_last_entry_at_the_empty_state_key_decides() {
1266 let valid = r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#;
1267 let junk = r#"{"x":1}"#;
1268 let event = |content| initial_state_event("m.room.encryption", "", content);
1269
1270 assert!(!encrypts_room(&[event(valid), event(junk)]));
1271 assert!(encrypts_room(&[event(junk), event(valid)]));
1272 assert!(encrypts_room(&[
1273 event(valid),
1274 initial_state_event("m.room.encryption", "x", junk)
1275 ]));
1276 }
1277
1278 #[test]
1279 fn a_foreign_state_key_never_encrypts() {
1280 let content = r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#;
1281 let event = initial_state_event("m.room.encryption", "x", content);
1282
1283 assert!(!encrypts_room(&[event]));
1284 }
1285
1286 #[test]
1287 fn other_event_types_never_encrypt() {
1288 let event =
1289 initial_state_event("m.room.name", "", r#"{"algorithm":"m.megolm.v1.aes-sha2"}"#);
1290
1291 assert!(!encrypts_room(&[event]));
1292 }
1293}