1use std::collections::BTreeMap;
2
3use axum::extract::State;
4use futures::FutureExt;
5use itertools::Itertools;
6use ruma::{
7 CanonicalJsonObject, EventEncryptionAlgorithm, Int, OwnedRoomAliasId, OwnedRoomId,
8 OwnedUserId, RoomId, RoomVersionId,
9 api::client::room::{
10 self,
11 create_room::{
12 self, RoomPowerLevelsContentOverride,
13 v3::{CreationContent, RoomPreset},
14 },
15 },
16 events::{
17 StateEventType, TimelineEventType,
18 room::{
19 canonical_alias::RoomCanonicalAliasEventContent,
20 create::RoomCreateEventContent,
21 encryption::RoomEncryptionEventContent,
22 guest_access::{GuestAccess, RoomGuestAccessEventContent},
23 history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
24 join_rules::{JoinRule, RoomJoinRulesEventContent},
25 member::{MembershipState, RoomMemberEventContent},
26 name::RoomNameEventContent,
27 power_levels::RoomPowerLevelsEventContent,
28 topic::RoomTopicEventContent,
29 },
30 },
31 int,
32 room_version_rules::{RoomIdFormatVersion, RoomVersionRules},
33 serde::{JsonObject, Raw},
34};
35use serde::Deserialize;
36use serde_json::{
37 json,
38 value::{RawValue, to_raw_value},
39};
40use tuwunel_core::{
41 Err, Result, debug_info, debug_warn, err, info,
42 matrix::{StateKey, pdu::PduBuilder, room_version},
43 utils::{BoolExt, option::OptionExt},
44 warn,
45};
46use tuwunel_service::{Services, appservice::RegistrationInfo, rooms::state::RoomMutexGuard};
47
48use crate::{Ruma, client::utils::invite_check};
49
50pub(crate) async fn create_room_route(
67 State(services): State<crate::State>,
68 body: Ruma<create_room::v3::Request>,
69) -> Result<create_room::v3::Response> {
70 can_create_room_check(&services, &body).await?;
71 can_publish_directory_check(&services, &body).await?;
72
73 let preset = body
75 .preset
76 .clone()
77 .unwrap_or(match &body.visibility {
78 | room::Visibility::Public => RoomPreset::PublicChat,
79 | _ => RoomPreset::PrivateChat, });
81
82 let (room_version, version_rules) = body
84 .room_version
85 .as_ref()
86 .map_or(Ok(&services.server.config.default_room_version), |version| {
87 services
88 .config
89 .supported_room_version(version)
90 .then_ok_or_else(version, || {
91 err!(Request(UnsupportedRoomVersion(
92 "This server does not support room version {version:?}"
93 )))
94 })
95 })
96 .and_then(|version| Ok((version, room_version::rules(version)?)))?;
97
98 let alias = body
100 .room_alias_name
101 .as_ref()
102 .map_async(|alias| room_alias_check(&services, alias, body.appservice_info.as_ref()))
103 .await
104 .transpose()?;
105
106 let next_count = services.globals.next_count();
109
110 let (room_id, state_lock) = match version_rules.room_id_format {
112 | RoomIdFormatVersion::V1 =>
113 create_create_event_legacy(&services, &body, room_version, &version_rules).await?,
114 | RoomIdFormatVersion::V2 =>
115 create_create_event(&services, &body, &preset, room_version, &version_rules)
116 .await
117 .map_err(|e| {
118 err!(Request(InvalidParam("Error while creating m.room.create event: {e}")))
119 })?,
120 };
121
122 let sender_user = body.sender_user();
124 services
125 .timeline
126 .build_and_append_pdu(
127 PduBuilder::state(sender_user.to_string(), &RoomMemberEventContent {
128 displayname: services.users.displayname(sender_user).await.ok(),
129 avatar_url: services.users.avatar_url(sender_user).await.ok(),
130 blurhash: services.users.blurhash(sender_user).await.ok(),
131 is_direct: Some(body.is_direct),
132 ..RoomMemberEventContent::new(MembershipState::Join)
133 }),
134 sender_user,
135 &room_id,
136 &state_lock,
137 )
138 .boxed()
139 .await?;
140
141 let mut users = if !version_rules
143 .authorization
144 .explicitly_privilege_room_creators
145 {
146 BTreeMap::from_iter([(sender_user.to_owned(), int!(100))])
147 } else {
148 BTreeMap::new()
149 };
150
151 if preset == RoomPreset::TrustedPrivateChat {
152 for invite in &body.invite {
153 if services
154 .users
155 .user_is_ignored(sender_user, invite)
156 .await
157 {
158 continue;
159 } else if services
160 .users
161 .user_is_ignored(invite, sender_user)
162 .await
163 {
164 continue;
167 }
168
169 if !version_rules
170 .authorization
171 .additional_room_creators
172 {
173 users.insert(invite.clone(), int!(100));
174 }
175 }
176 }
177
178 let power_levels_content = default_power_levels_content(
179 &version_rules,
180 body.power_level_content_override.as_ref(),
181 &preset,
182 users,
183 )?;
184
185 services
186 .timeline
187 .build_and_append_pdu(
188 PduBuilder {
189 event_type: TimelineEventType::RoomPowerLevels,
190 content: to_raw_value(&power_levels_content)?,
191 state_key: Some(StateKey::new()),
192 ..Default::default()
193 },
194 sender_user,
195 &room_id,
196 &state_lock,
197 )
198 .boxed()
199 .await?;
200
201 if let Some(room_alias_id) = &alias {
203 services
204 .timeline
205 .build_and_append_pdu(
206 PduBuilder::state(String::new(), &RoomCanonicalAliasEventContent {
207 alias: Some(room_alias_id.to_owned()),
208 alt_aliases: vec![],
209 }),
210 sender_user,
211 &room_id,
212 &state_lock,
213 )
214 .boxed()
215 .await?;
216 }
217
218 let mut initial_state = body
220 .initial_state
221 .iter()
222 .map(|state| Ok(state.deserialize_as_unchecked::<InitialEvent>()?))
223 .filter_ok(|event| {
224 services.config.allow_encryption || event.event_type != StateEventType::RoomEncryption
225 })
226 .filter_ok(|event| {
227 if event.content.get() == "{}" {
231 debug_warn!("skipping empty initial state event of type {}", event.event_type);
232 false
233 } else {
234 true
235 }
236 })
237 .filter_ok(|event| body.name.is_none() || event.event_type != StateEventType::RoomName)
238 .filter_ok(|event| body.topic.is_none() || event.event_type != StateEventType::RoomTopic)
239 .collect::<Result<Vec<_>>>()?;
240
241 let join_rule_pdubuilder =
242 take_initial(&mut initial_state, &StateEventType::RoomJoinRules, "")
243 .map(Into::into)
244 .unwrap_or_else(|| {
245 PduBuilder::state(
246 String::new(),
247 &RoomJoinRulesEventContent::new(match preset {
248 | RoomPreset::PublicChat => JoinRule::Public,
249 | _ => JoinRule::Invite,
251 }),
252 )
253 });
254
255 let history_visibility_pdubuilder =
256 take_initial(&mut initial_state, &StateEventType::RoomHistoryVisibility, "")
257 .map(Into::into)
258 .unwrap_or_else(|| {
259 PduBuilder::state(
260 String::new(),
261 &RoomHistoryVisibilityEventContent::new(HistoryVisibility::Shared),
262 )
263 });
264
265 let guest_access_pdubuilder =
266 take_initial(&mut initial_state, &StateEventType::RoomGuestAccess, "")
267 .map(Into::into)
268 .unwrap_or_else(|| {
269 PduBuilder::state(
270 String::new(),
271 &RoomGuestAccessEventContent::new(match preset {
272 | RoomPreset::PublicChat => GuestAccess::Forbidden,
273 | _ => GuestAccess::CanJoin,
274 }),
275 )
276 });
277
278 services
280 .timeline
281 .build_and_append_pdu(join_rule_pdubuilder, sender_user, &room_id, &state_lock)
282 .boxed()
283 .await?;
284
285 services
287 .timeline
288 .build_and_append_pdu(history_visibility_pdubuilder, sender_user, &room_id, &state_lock)
289 .boxed()
290 .await?;
291
292 services
294 .timeline
295 .build_and_append_pdu(guest_access_pdubuilder, sender_user, &room_id, &state_lock)
296 .boxed()
297 .await?;
298
299 let is_encrypted = initial_state
301 .iter()
302 .any(|event| event.event_type == StateEventType::RoomEncryption);
303
304 for event in initial_state {
305 services
306 .timeline
307 .build_and_append_pdu(event.into(), sender_user, &room_id, &state_lock)
308 .boxed()
309 .await?;
310 }
311
312 if services.config.allow_encryption && !is_encrypted {
313 use RoomPreset::*;
314
315 let config = services
316 .config
317 .encryption_enabled_by_default_for_room_type
318 .as_deref();
319
320 let should_encrypt = match config {
321 | Some("all") => true,
322 | Some("invite") => matches!(preset, PrivateChat | TrustedPrivateChat),
323 | _ => false,
324 };
325
326 if should_encrypt {
327 let algorithm = EventEncryptionAlgorithm::MegolmV1AesSha2;
328 let content = RoomEncryptionEventContent::new(algorithm);
329 services
330 .timeline
331 .build_and_append_pdu(
332 PduBuilder::state(String::new(), &content),
333 sender_user,
334 &room_id,
335 &state_lock,
336 )
337 .boxed()
338 .await?;
339 }
340 }
341
342 if let Some(name) = &body.name {
344 services
345 .timeline
346 .build_and_append_pdu(
347 PduBuilder::state(String::new(), &RoomNameEventContent::new(name.clone())),
348 sender_user,
349 &room_id,
350 &state_lock,
351 )
352 .boxed()
353 .await?;
354 }
355
356 if let Some(topic) = &body.topic {
357 services
358 .timeline
359 .build_and_append_pdu(
360 PduBuilder::state(String::new(), &RoomTopicEventContent::new(topic.clone())),
361 sender_user,
362 &room_id,
363 &state_lock,
364 )
365 .boxed()
366 .await?;
367 }
368
369 drop(next_count);
370 drop(state_lock);
371
372 if (!body.invite.is_empty() || !body.invite_3pid.is_empty())
374 && invite_check(&services, sender_user, &room_id)
375 .await
376 .is_ok()
377 {
378 for user_id in &body.invite {
380 if services
381 .users
382 .user_is_ignored(sender_user, user_id)
383 .await
384 {
385 continue;
386 } else if services
387 .users
388 .user_is_ignored(user_id, sender_user)
389 .await
390 {
391 continue;
394 }
395
396 if let Err(e) = services
397 .membership
398 .invite(sender_user, user_id, &room_id, None, body.is_direct)
399 .boxed()
400 .await
401 {
402 warn!(%e, "Failed to send invite");
403 }
404 }
405 }
406
407 if let Some(alias) = alias {
409 services
410 .alias
411 .set_alias_by(&alias, &room_id, sender_user)?;
412 }
413
414 if body.visibility == room::Visibility::Public {
415 services.directory.set_public(&room_id);
416
417 if services.server.config.admin_room_notices {
418 services
419 .admin
420 .send_text(&format!("{sender_user} made {room_id} public to the room directory"))
421 .await;
422 }
423 info!("{sender_user} made {0} public to the room directory", &room_id);
424 }
425
426 info!("{sender_user} created a room with room ID {room_id}");
427
428 Ok(create_room::v3::Response::new(room_id))
429}
430
431async fn create_create_event(
432 services: &Services,
433 body: &Ruma<create_room::v3::Request>,
434 preset: &RoomPreset,
435 room_version: &RoomVersionId,
436 version_rules: &RoomVersionRules,
437) -> Result<(OwnedRoomId, RoomMutexGuard)> {
438 let _sender_user = body.sender_user();
439
440 let mut create_content = match &body.creation_content {
441 | Some(content) => {
442 let mut content = content
443 .deserialize_as_unchecked::<CanonicalJsonObject>()
444 .map_err(|e| {
445 err!(Request(BadJson(error!(
446 "Failed to deserialise content as canonical JSON: {e}"
447 ))))
448 })?;
449
450 if !services.config.federate_created_rooms
451 && (!services.config.allow_federation || !content.contains_key("m.federate"))
452 {
453 content.insert("m.federate".into(), json!(false).try_into()?);
454 }
455
456 content.insert(
457 "room_version".into(),
458 json!(room_version.as_str())
459 .try_into()
460 .map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
461 );
462
463 content
464 },
465 | None => {
466 let content = RoomCreateEventContent::new_v11();
467
468 let mut content =
469 serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
470
471 if !services.config.federate_created_rooms {
472 content.insert("m.federate".into(), json!(false).try_into()?);
473 }
474
475 content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
476 content
477 },
478 };
479
480 if version_rules
481 .authorization
482 .additional_room_creators
483 {
484 let mut additional_creators = body
485 .creation_content
486 .as_ref()
487 .and_then(|c| {
488 c.deserialize_as_unchecked::<CreationContent>()
489 .ok()
490 })
491 .unwrap_or_default()
492 .additional_creators;
493
494 if *preset == RoomPreset::TrustedPrivateChat {
495 additional_creators.extend(body.invite.clone());
496 }
497
498 additional_creators.sort();
499 additional_creators.dedup();
500 if !additional_creators.is_empty() {
501 create_content
502 .insert("additional_creators".into(), json!(additional_creators).try_into()?);
503 }
504 }
505
506 let room_id = ruma::room_id!("!thiswillbereplaced").to_owned();
508 let state_lock = services.state.mutex.lock(&room_id).await;
509 let create_event_id = services
510 .timeline
511 .build_and_append_pdu(
512 PduBuilder {
513 event_type: TimelineEventType::RoomCreate,
514 content: to_raw_value(&create_content)?,
515 state_key: Some(StateKey::new()),
516 ..Default::default()
517 },
518 body.sender_user(),
519 &room_id,
520 &state_lock,
521 )
522 .boxed()
523 .await?;
524
525 drop(state_lock);
526
527 let room_id = OwnedRoomId::from_parts('!', create_event_id.localpart(), None)?;
529 let state_lock = services.state.mutex.lock(&room_id).await;
530
531 Ok((room_id, state_lock))
532}
533
534async fn create_create_event_legacy(
535 services: &Services,
536 body: &Ruma<create_room::v3::Request>,
537 room_version: &RoomVersionId,
538 _version_rules: &RoomVersionRules,
539) -> Result<(OwnedRoomId, RoomMutexGuard)> {
540 let room_id: OwnedRoomId = match &body.room_id {
541 | None => RoomId::new_v1(&services.server.name),
542 | Some(custom_id) => custom_room_id_check(services, custom_id).await?,
543 };
544
545 let state_lock = services.state.mutex.lock(&room_id).await;
546
547 let _short_id = services
548 .short
549 .get_or_create_shortroomid(&room_id)
550 .await;
551
552 let create_content = match &body.creation_content {
553 | Some(content) => {
554 use RoomVersionId::*;
555
556 let mut content = content
557 .deserialize_as_unchecked::<CanonicalJsonObject>()
558 .map_err(|e| {
559 err!(Request(BadJson(error!(
560 "Failed to deserialise content as canonical JSON: {e}"
561 ))))
562 })?;
563
564 match room_version {
565 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 => {
566 content.insert(
567 "creator".into(),
568 json!(body.sender_user())
569 .try_into()
570 .map_err(|e| {
571 err!(Request(BadJson(debug_error!(
572 "Invalid creation content: {e}"
573 ))))
574 })?,
575 );
576 },
577 | _ => {
578 },
580 }
581
582 if !services.config.federate_created_rooms
583 && (!services.config.allow_federation || !content.contains_key("m.federate"))
584 {
585 content.insert("m.federate".into(), json!(false).try_into()?);
586 }
587
588 content.insert(
589 "room_version".into(),
590 json!(room_version.as_str())
591 .try_into()
592 .map_err(|e| err!(Request(BadJson("Invalid creation content: {e}"))))?,
593 );
594
595 content
596 },
597 | None => {
598 use RoomVersionId::*;
599
600 let content = match room_version {
601 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 =>
602 RoomCreateEventContent::new_v1(body.sender_user().to_owned()),
603 | _ => RoomCreateEventContent::new_v11(),
604 };
605
606 let mut content =
607 serde_json::from_str::<CanonicalJsonObject>(to_raw_value(&content)?.get())?;
608
609 if !services.config.federate_created_rooms {
610 content.insert("m.federate".into(), json!(false).try_into()?);
611 }
612
613 content.insert("room_version".into(), json!(room_version.as_str()).try_into()?);
614 content
615 },
616 };
617
618 services
620 .timeline
621 .build_and_append_pdu(
622 PduBuilder {
623 event_type: TimelineEventType::RoomCreate,
624 content: to_raw_value(&create_content)?,
625 state_key: Some(StateKey::new()),
626 ..Default::default()
627 },
628 body.sender_user(),
629 &room_id,
630 &state_lock,
631 )
632 .boxed()
633 .await?;
634
635 Ok((room_id, state_lock))
636}
637
638fn default_power_levels_content(
640 version_rules: &RoomVersionRules,
641 power_level_content_override: Option<&Raw<RoomPowerLevelsContentOverride>>,
642 preset: &RoomPreset,
643 users: BTreeMap<OwnedUserId, Int>,
644) -> Result<serde_json::Value> {
645 use serde_json::to_value;
646
647 let mut power_levels_content = RoomPowerLevelsEventContent::new(&version_rules.authorization);
648 power_levels_content.users = users;
649
650 let mut power_levels_content = to_value(power_levels_content)?;
651
652 power_levels_content["events"]["m.room.power_levels"] = json!(100);
655 power_levels_content["events"]["m.room.server_acl"] = json!(100);
656 power_levels_content["events"]["m.room.encryption"] = json!(100);
657 power_levels_content["events"]["m.room.history_visibility"] = json!(100);
658
659 if version_rules
660 .authorization
661 .explicitly_privilege_room_creators
662 {
663 power_levels_content["events"]["m.room.tombstone"] = json!(150);
664 } else {
665 power_levels_content["events"]["m.room.tombstone"] = json!(100);
666 }
667
668 power_levels_content["events"]["org.matrix.msc3381.poll.response"] = json!(0);
671 power_levels_content["events"]["m.poll.response"] = json!(0);
672
673 if *preset == RoomPreset::PublicChat {
676 power_levels_content["invite"] = json!(50);
677 power_levels_content["events"]["m.call.invite"] = json!(50);
678 power_levels_content["events"]["m.call"] = json!(50);
679 power_levels_content["events"]["m.call.member"] = json!(50);
680 power_levels_content["events"]["org.matrix.msc3401.call"] = json!(50);
681 power_levels_content["events"]["org.matrix.msc3401.call.member"] = json!(50);
682 }
683
684 if let Some(power_level_content_override) = power_level_content_override {
685 let json: JsonObject = serde_json::from_str(power_level_content_override.json().get())
686 .map_err(|e| err!(Request(BadJson("Invalid power_level_content_override: {e:?}"))))?;
687
688 for (key, value) in json {
689 power_levels_content[key] = value;
690 }
691 }
692
693 Ok(power_levels_content)
694}
695
696async fn room_alias_check(
698 services: &Services,
699 room_alias_name: &str,
700 appservice_info: Option<&RegistrationInfo>,
701) -> Result<OwnedRoomAliasId> {
702 if room_alias_name.contains(':') {
704 return Err!(Request(InvalidParam(
705 "Room alias contained `:` which is not allowed. Please note that this expects a \
706 localpart, not the full room alias.",
707 )));
708 } else if room_alias_name.contains(char::is_whitespace) {
709 return Err!(Request(InvalidParam(
710 "Room alias contained spaces which is not a valid room alias.",
711 )));
712 }
713
714 if services
716 .config
717 .forbidden_alias_names
718 .is_match(room_alias_name)
719 {
720 return Err!(Request(Unknown("Room alias name is forbidden.")));
721 }
722
723 let server_name = services.globals.server_name();
724 let full_room_alias = OwnedRoomAliasId::parse(format!("#{room_alias_name}:{server_name}"))
725 .map_err(|e| {
726 err!(Request(InvalidParam(debug_error!(
727 ?e,
728 ?room_alias_name,
729 "Failed to parse room alias.",
730 ))))
731 })?;
732
733 if services
734 .alias
735 .resolve_local_alias(&full_room_alias)
736 .await
737 .is_ok()
738 {
739 return Err!(Request(RoomInUse("Room alias already exists.")));
740 }
741
742 if let Some(info) = appservice_info {
743 if !info.aliases.is_match(full_room_alias.as_str()) {
744 return Err!(Request(Exclusive("Room alias is not in namespace.")));
745 }
746 } else if services
747 .appservice
748 .is_exclusive_alias(&full_room_alias)
749 .await
750 {
751 return Err!(Request(Exclusive("Room alias reserved by appservice.",)));
752 }
753
754 debug_info!("Full room alias: {full_room_alias}");
755
756 Ok(full_room_alias)
757}
758
759async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
761 if services
763 .config
764 .forbidden_alias_names
765 .is_match(custom_room_id)
766 {
767 return Err!(Request(Unknown("Custom room ID is forbidden.")));
768 }
769
770 if custom_room_id.contains(':') {
771 return Err!(Request(InvalidParam(
772 "Custom room ID contained `:` which is not allowed. Please note that this expects a \
773 localpart, not the full room ID.",
774 )));
775 } else if custom_room_id.contains(char::is_whitespace) {
776 return Err!(Request(InvalidParam(
777 "Custom room ID contained spaces which is not valid."
778 )));
779 }
780
781 let server_name = services.globals.server_name();
782 let full_room_id = format!("!{custom_room_id}:{server_name}");
783
784 let room_id = OwnedRoomId::parse(full_room_id)
785 .inspect(|full_room_id| debug_info!(?full_room_id, "Full custom room ID"))
786 .inspect_err(|e| {
787 warn!(?e, ?custom_room_id, "Failed to create room with custom room ID");
788 })?;
789
790 if services
792 .short
793 .get_shortroomid(&room_id)
794 .await
795 .is_ok()
796 {
797 return Err!(Request(RoomInUse("Room with that custom room ID already exists",)));
798 }
799
800 Ok(room_id)
801}
802
803async fn can_publish_directory_check(
804 services: &Services,
805 body: &Ruma<create_room::v3::Request>,
806) -> Result {
807 if !services
808 .server
809 .config
810 .lockdown_public_room_directory
811 || body.appservice_info.is_some()
812 || body.visibility != room::Visibility::Public
813 || services
814 .admin
815 .user_is_admin(body.sender_user())
816 .await
817 {
818 return Ok(());
819 }
820
821 let msg = format!(
822 "Non-admin user {} tried to publish new to the directory while \
823 lockdown_public_room_directory is enabled",
824 body.sender_user(),
825 );
826
827 warn!("{msg}");
828 if services.server.config.admin_room_notices {
829 services.admin.notice(&msg).await;
830 }
831
832 Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")))
833}
834
835async fn can_create_room_check(
836 services: &Services,
837 body: &Ruma<create_room::v3::Request>,
838) -> Result {
839 if !services.config.allow_room_creation
840 && body.appservice_info.is_none()
841 && !services
842 .admin
843 .user_is_admin(body.sender_user())
844 .await
845 {
846 return Err!(Request(Forbidden("Room creation has been disabled.",)));
847 }
848
849 Ok(())
850}
851
852#[derive(Deserialize)]
853struct InitialEvent {
854 #[serde(rename = "type")]
855 event_type: StateEventType,
856
857 #[serde(default = "StateKey::new")]
858 state_key: StateKey,
859
860 content: Box<RawValue>,
861}
862
863impl From<InitialEvent> for PduBuilder {
864 fn from(value: InitialEvent) -> Self {
865 Self {
866 event_type: value.event_type.into(),
867 content: value.content,
868 unsigned: None,
869 state_key: Some(value.state_key),
870 redacts: None,
871 timestamp: None,
872 }
873 }
874}
875
876fn take_initial(
877 initial_state: &mut Vec<InitialEvent>,
878 event_type: &StateEventType,
879 state_key: &str,
880) -> Option<InitialEvent> {
881 initial_state
882 .extract_if(.., |event| &event.event_type == event_type && event.state_key == state_key)
883 .next()
884}