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