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, 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	// Figure out preset. We need it for preset specific events
63	let preset = body
64		.preset
65		.clone()
66		.unwrap_or(match &body.visibility {
67			| room::Visibility::Public => RoomPreset::PublicChat,
68			| _ => RoomPreset::PrivateChat, // Room visibility should not be custom
69		});
70
71	// Determine room version
72	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	// Error on existing alias before committing to creation.
90	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	// Increment and hold the counter; the room will sync atomically to clients
100	// which is preferable.
101	let next_count = services.globals.next_count();
102
103	// 1. Create the create event.
104	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	// 2. Let the room creator join
116	apply_creator_join_pdu(&services, &body, sender_user, &room_id, &state_lock)
117		.boxed()
118		.await?;
119
120	// 3. Power levels
121	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	// 4. Canonical room alias
134	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	// 5. Events set by preset
141	let initial_state =
142		apply_preset_state_pdus(&services, &body, &preset, sender_user, &room_id, &state_lock)
143			.boxed()
144			.await?;
145
146	// 6. Events listed in initial_state
147	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	// 7. Events implied by name and topic
159	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 inviting anyone with room creation and invite check passes
167	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			// client/appservice workaround: if a user sends an initial_state event with a
335			// state event in there with the content of literally `{}` (not null or empty
336			// string), let's just skip it over and warn.
337			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						// according to spec "invite" is the default
357						| _ => 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	// 5.1 Join Rules
378	services
379		.timeline
380		.build_and_append_pdu(join_rule_pdubuilder, sender_user, room_id, state_lock)
381		.boxed()
382		.await?;
383
384	// 5.2 History Visibility
385	services
386		.timeline
387		.build_and_append_pdu(history_visibility_pdubuilder, sender_user, room_id, state_lock)
388		.boxed()
389		.await?;
390
391	// 5.3 Guest Access
392	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
465/// Whether `initial_state` already configures the room's encryption.
466///
467/// The last entry at the empty state key is the one that survives state
468/// resolution, so it alone decides whether the server's forced default is
469/// displaced, and only by naming a string `algorithm`. The raw field is read
470/// rather than deserialized so an escaped string still counts as one.
471fn 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	// 8. Events implied by invite (and TODO: invite_3pid)
524	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
543/// Gate an invitee against the sender's own ignore list.
544///
545/// The invitee's own invite permission is not consulted here: `local_invite`
546/// is the authoritative check and refuses a blocked invite the same way any
547/// other failed invite is handled, while an ignored one proceeds and is
548/// withheld from the invitee afterwards.
549fn 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	// 1. The room create event, using a placeholder room_id
686	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	// The real room_id is now the event_id.
707	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	// 1. The room create event
787	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
806/// creates the power_levels_content for the PDU builder
807fn 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	// secure proper defaults of sensitive/dangerous permissions that moderators
822	// (power level 50) should not have easy access to
823	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	// always allow users to respond (not post new) to polls. this is primarily
838	// useful in read-only announcement rooms that post a public poll.
839	power_levels_content["events"]["org.matrix.msc3381.poll.response"] = json!(0);
840	power_levels_content["events"]["m.poll.response"] = json!(0);
841
842	// public_chat: pin invite and call-setup events at PL 50. Synapse pins
843	// invite and m.call.invite here; the MSC3401 entries are tuwunel-only.
844	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
875/// Replace each top-level power-levels key wholesale; no deep merge.
876fn 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
886/// if a room is being created with a room alias, run our checks
887async 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	// Basic checks on the room alias validity
899	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	// check if room alias is forbidden
911	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
955/// if a room is being created with a custom room ID, run our checks against it
956async fn custom_room_id_check(services: &Services, custom_room_id: &str) -> Result<OwnedRoomId> {
957	// apply forbidden room alias checks to custom room IDs too
958	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	// check if room ID doesn't already exist instead of erroring on auth check
987	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}