Skip to main content

tuwunel_api_client/room/
create.rs

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	// Figure out preset. We need it for preset specific events
71	let preset = body
72		.preset
73		.clone()
74		.unwrap_or(match &body.visibility {
75			| room::Visibility::Public => RoomPreset::PublicChat,
76			| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
77		});
78
79	// Determine room version
80	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	// Error on existing alias before committing to creation.
98	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	// Increment and hold the counter; the room will sync atomically to clients
108	// which is preferable.
109	let next_count = services.globals.next_count();
110
111	// 1. Create the create event.
112	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	// 2. Let the room creator join
127	apply_creator_join_pdu(&services, &body, sender_user, &room_id, &state_lock)
128		.boxed() // size firewall
129		.await?;
130
131	// 3. Power levels
132	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() // size firewall
142	.await?;
143
144	// 4. Canonical room alias
145	if let Some(room_alias_id) = &alias {
146		apply_canonical_alias_pdu(&services, room_alias_id, sender_user, &room_id, &state_lock)
147			.boxed() // cold arm
148			.await?;
149	}
150
151	// 5. Events set by preset
152	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() // size firewall
161	.await?;
162
163	// 6. Events listed in initial_state
164	apply_initial_state_pdus(
165		&services,
166		initial_state,
167		&preset,
168		sender_user,
169		&room_id,
170		&state_lock,
171	)
172	.boxed() // size firewall
173	.await?;
174
175	// 7. Events implied by name and topic
176	apply_name_and_topic_pdus(&services, &body, sender_user, &room_id, &state_lock)
177		.boxed() // size firewall
178		.await?;
179
180	drop(next_count);
181	drop(state_lock);
182
183	// if inviting anyone with room creation and invite check passes
184	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() // size firewall
191			.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			// client/appservice workaround: if a user sends an initial_state event with a
351			// state event in there with the content of literally `{}` (not null or empty
352			// string), let's just skip it over and warn.
353			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						// according to spec "invite" is the default
371						| _ => 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	// 5.1 Join Rules
392	services
393		.timeline
394		.build_and_append_pdu(join_rule_pdubuilder, sender_user, room_id, state_lock)
395		.boxed() // size firewall
396		.await?;
397
398	// 5.2 History Visibility
399	services
400		.timeline
401		.build_and_append_pdu(history_visibility_pdubuilder, sender_user, room_id, state_lock)
402		.boxed() // size firewall
403		.await?;
404
405	// 5.3 Guest Access
406	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() // size firewall
411			.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() // size firewall
440			.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() // cold arm
475		.await?;
476
477	Ok(())
478}
479
480/// Whether `initial_state` already configures the room's encryption.
481///
482/// The last entry at the empty state key is the one that survives state
483/// resolution, so it alone decides whether the server's forced default is
484/// displaced, and only by naming a string `algorithm`. The raw field is read
485/// rather than deserialized so an escaped string still counts as one.
486fn 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() // size firewall
513			.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() // cold arm
526			.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	// 8. Events implied by invite (and TODO: invite_3pid)
539	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() // size firewall
550				.await
551			{
552				warn!(%e, "Failed to send invite");
553			}
554		})
555		.await;
556}
557
558/// Gate an invitee against the sender's own ignore list.
559///
560/// The invitee's own invite permission is not consulted here: `local_invite`
561/// is the authoritative check and refuses a blocked invite the same way any
562/// other failed invite is handled, while an ignored one proceeds and is
563/// withheld from the invitee afterwards.
564fn 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() // size firewall
715		.await?;
716
717	// The real room_id is now the event_id.
718	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
724// These versions derive the room ID from the create event, so only a hash-form ID is admitted.
725fn 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	// 1. The room create event
810	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() // size firewall
824		.await?;
825
826	Ok((room_id, state_lock))
827}
828
829/// creates the power_levels_content for the PDU builder
830fn 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	// secure proper defaults of sensitive/dangerous permissions that moderators
845	// (power level 50) should not have easy access to
846	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	// always allow users to respond (not post new) to polls. this is primarily
861	// useful in read-only announcement rooms that post a public poll.
862	power_levels_content["events"]["org.matrix.msc3381.poll.response"] = json!(0);
863	power_levels_content["events"]["m.poll.response"] = json!(0);
864
865	// public_chat: pin invite and call-setup events at PL 50. Synapse pins
866	// invite and m.call.invite here; the MSC3401 entries are tuwunel-only.
867	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
898/// Replace each top-level power-levels key wholesale; no deep merge.
899fn 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
909/// if a room is being created with a room alias, run our checks
910async 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	// Basic checks on the room alias validity
922	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	// check if room alias is forbidden
934	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
979/// if a room is being created with a custom room ID, run our checks against it
980async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
981	// apply forbidden room alias checks to custom room IDs too
982	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	// check if room ID doesn't already exist instead of erroring on auth check
1012	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
1051// The entries are parsed once, before any PDU is appended, so a refusal cannot leave a
1052// half-built room behind.
1053fn 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			// Appending parses this too, but by then the room exists.
1071			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}