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