Skip to main content

tuwunel_service/rooms/state_res/
event_auth.rs

1mod auth_types;
2mod room_member;
3#[cfg(test)]
4mod tests;
5
6use futures::{
7	FutureExt, TryStreamExt,
8	future::{join3, try_join},
9};
10use ruma::{
11	EventId, Int, OwnedEventId, OwnedUserId,
12	api::error::ErrorKind::InvalidParam,
13	events::{
14		StateEventType, TimelineEventType,
15		room::{member::MembershipState, power_levels::UserPowerLevel},
16	},
17	room_version_rules::{AuthorizationRules, RoomVersionRules},
18};
19use tuwunel_core::{
20	Err, Error, Result, err,
21	matrix::{Event, StateKey},
22	trace,
23	utils::stream::{IterStream, TryReadyExt},
24};
25
26pub use self::auth_types::{AuthTypes, auth_types_for_event};
27use self::room_member::check_room_member;
28#[cfg(test)]
29use super::test_utils;
30use super::{
31	FetchStateExt, TypeStateKey, events,
32	events::{
33		RoomCreateEvent, RoomMemberEvent, RoomPowerLevelsEvent,
34		power_levels::{self, RoomPowerLevelsEventOptionExt, RoomPowerLevelsIntField},
35	},
36};
37
38#[tracing::instrument(
39	level = "debug",
40	skip_all,
41	fields(
42		event_id = ?incoming_event.event_id(),
43	)
44)]
45pub async fn auth_check<FetchEvent, EventFut, FetchState, StateFut, Pdu>(
46	rules: &RoomVersionRules,
47	incoming_event: &Pdu,
48	fetch_event: &FetchEvent,
49	fetch_state: &FetchState,
50) -> Result
51where
52	FetchEvent: Fn(OwnedEventId) -> EventFut + Sync,
53	EventFut: Future<Output = Result<Pdu>> + Send,
54	FetchState: Fn(StateEventType, StateKey) -> StateFut + Sync,
55	StateFut: Future<Output = Result<Pdu>> + Send,
56	Pdu: Event,
57{
58	let dependent = check_state_dependent_auth_rules(rules, incoming_event, fetch_state);
59
60	let independent = check_state_independent_auth_rules(rules, incoming_event, fetch_event);
61
62	match try_join(independent, dependent).await {
63		| Err(e) if matches!(e, Error::Request(InvalidParam, ..)) => Err(e),
64		| Err(e) => Err!(Request(Forbidden("Auth check failed: {e}"))),
65		| Ok(_) => Ok(()),
66	}
67}
68
69/// Check whether the incoming event passes the state-independent [authorization
70/// rules] for the given room version rules.
71///
72/// The state-independent rules are the first few authorization rules that check
73/// an incoming `m.room.create` event (which cannot have `auth_events`), and the
74/// list of `auth_events` of other events.
75///
76/// This method only needs to be called once, when the event is received.
77///
78/// # Errors
79///
80/// If the check fails, this returns an `Err(_)` with a description of the check
81/// that failed.
82///
83/// [authorization rules]: https://spec.matrix.org/latest/server-server-api/#authorization-rules
84#[tracing::instrument(
85	name = "independent",
86	level = "debug",
87	skip_all,
88	fields(
89		sender = ?incoming_event.sender(),
90	)
91)]
92pub(super) async fn check_state_independent_auth_rules<Fetch, Fut, Pdu>(
93	rules: &RoomVersionRules,
94	incoming_event: &Pdu,
95	fetch_event: &Fetch,
96) -> Result
97where
98	Fetch: Fn(OwnedEventId) -> Fut + Sync,
99	Fut: Future<Output = Result<Pdu>> + Send,
100	Pdu: Event,
101{
102	// Since v1, if type is m.room.create:
103	if *incoming_event.event_type() == TimelineEventType::RoomCreate {
104		let room_create_event = RoomCreateEvent::new(incoming_event.clone());
105		return check_room_create(&room_create_event, &rules.authorization);
106	}
107
108	let expected_auth_types = auth_types_for_event(
109		incoming_event.event_type(),
110		incoming_event.sender(),
111		incoming_event.state_key(),
112		incoming_event.content(),
113		&rules.authorization,
114		false,
115	)?;
116
117	// Since v1, considering auth_events:
118	let seen_auth_types = Vec::with_capacity(expected_auth_types.len());
119	let seen_auth_types = incoming_event
120		.auth_events()
121		.try_stream()
122		.and_then(async |event_id: &EventId| match fetch_event(event_id.to_owned()).await {
123			| Ok(auth_event) => Ok(auth_event),
124			| Err(e) if e.is_not_found() => Err!(Request(NotFound("auth event {event_id}: {e}"))),
125			| Err(e) => Err(e),
126		})
127		.ready_try_fold(seen_auth_types, |mut seen_auth_types, auth_event| {
128			let event_id = auth_event.event_id();
129
130			// The auth event must be in the same room as the incoming event.
131			if auth_event.room_id() != incoming_event.room_id() {
132				return Err!("auth event {event_id} not in the same room");
133			}
134
135			let state_key = auth_event
136				.state_key()
137				.ok_or_else(|| err!("auth event {event_id} has no `state_key`"))?;
138
139			let event_type = auth_event.event_type();
140			let key: TypeStateKey = (event_type.to_cow_str().into(), state_key.into());
141
142			// Since v1, if there are duplicate entries for a given type and state_key pair,
143			// reject.
144			if seen_auth_types.contains(&key) {
145				return Err!(
146					"duplicate auth event {event_id} for ({event_type}, {state_key}) pair"
147				);
148			}
149
150			// Since v1, if there are entries whose type and state_key don’t match those
151			// specified by the auth events selection algorithm described in the server
152			// specification, reject.
153			if !expected_auth_types.contains(&key) {
154				return Err!(
155					"unexpected auth event {event_id} with ({event_type}, {state_key}) pair"
156				);
157			}
158
159			// Since v1, if there are entries which were themselves rejected under the
160			// checks performed on receipt of a PDU, reject.
161			if auth_event.rejected() {
162				return Err!("rejected auth event {event_id}");
163			}
164
165			seen_auth_types.push(key);
166			Ok(seen_auth_types)
167		})
168		.await?;
169
170	// Since v1, if there is no m.room.create event among the entries, reject.
171	if !rules
172		.authorization
173		.room_create_event_id_as_room_id
174		&& !seen_auth_types
175			.iter()
176			.any(|(event_type, _)| *event_type == StateEventType::RoomCreate)
177	{
178		return Err!("no `m.room.create` event in auth events");
179	}
180
181	// Since `org.matrix.hydra.11`, the room_id must be the reference hash of an
182	// accepted m.room.create event.
183	if rules
184		.authorization
185		.room_create_event_id_as_room_id
186	{
187		let room_create_event_id = incoming_event
188			.room_id()
189			.as_event_id()
190			.map_err(|e| {
191				err!(Request(InvalidParam(
192					"could not construct `m.room.create` event ID from room ID: {e}"
193				)))
194			})?;
195
196		let Ok(room_create_event) = fetch_event(room_create_event_id.clone()).await else {
197			return Err!(Request(NotFound(
198				"failed to find `m.room.create` event {room_create_event_id}"
199			)));
200		};
201
202		if room_create_event.rejected() {
203			return Err!("rejected `m.room.create` event {room_create_event_id}");
204		}
205	}
206
207	Ok(())
208}
209
210/// Check whether the incoming event passes the state-dependent [authorization
211/// rules] for the given room version rules.
212///
213/// The state-dependent rules are all the remaining rules not checked by
214/// [`check_state_independent_auth_rules()`].
215///
216/// This method should be called several times for an event, to perform the
217/// [checks on receipt of a PDU].
218///
219/// The `fetch_state` closure should gather state from a state snapshot. We need
220/// to know if the event passes auth against some state not a recursive
221/// collection of auth_events fields.
222///
223/// This assumes that `ruma_signatures::verify_event()` was called previously,
224/// as some authorization rules depend on the signatures being valid on the
225/// event.
226///
227/// # Errors
228///
229/// If the check fails, this returns an `Err(_)` with a description of the check
230/// that failed.
231///
232/// [authorization rules]: https://spec.matrix.org/latest/server-server-api/#authorization-rules
233/// [checks on receipt of a PDU]: https://spec.matrix.org/latest/server-server-api/#checks-performed-on-receipt-of-a-pdu
234#[tracing::instrument(
235	name = "dependent",
236	level = "debug",
237	skip_all,
238	fields(
239		sender = ?incoming_event.sender(),
240	)
241)]
242pub(super) async fn check_state_dependent_auth_rules<Fetch, Fut, Pdu>(
243	rules: &RoomVersionRules,
244	incoming_event: &Pdu,
245	fetch_state: &Fetch,
246) -> Result
247where
248	Fetch: Fn(StateEventType, StateKey) -> Fut + Sync,
249	Fut: Future<Output = Result<Pdu>> + Send,
250	Pdu: Event,
251{
252	// There are no state-dependent auth rules for create events.
253	if *incoming_event.event_type() == TimelineEventType::RoomCreate {
254		trace!("allowing `m.room.create` event");
255		return Ok(());
256	}
257
258	let sender = incoming_event.sender();
259	let (room_create_event, sender_membership, current_room_power_levels_event) = join3(
260		fetch_state.room_create_event(),
261		fetch_state.user_membership(sender),
262		fetch_state.room_power_levels_event(),
263	)
264	.await;
265
266	// Since v1, if the create event content has the field m.federate set to false
267	// and the sender domain of the event does not match the sender domain of the
268	// create event, reject.
269	let room_create_event = room_create_event?;
270	let federate = room_create_event.federate()?;
271	if !federate
272		&& room_create_event.sender().server_name() != incoming_event.sender().server_name()
273	{
274		return Err!(
275			"room is not federated and event's sender domain does not match `m.room.create` \
276			 event's sender domain"
277		);
278	}
279
280	// v1-v5, if type is m.room.aliases:
281	if rules.authorization.special_case_room_aliases
282		&& incoming_event.event_type().to_cow_str() == "m.room.aliases"
283	{
284		trace!("starting m.room.aliases check");
285		// v1-v5, if event has no state_key, reject.
286		//
287		// v1-v5, if sender's domain doesn't match state_key, reject.
288		if incoming_event.state_key() != Some(sender.server_name().as_str()) {
289			return Err!(
290				"server name of the `state_key` of `m.room.aliases` event does not match the \
291				 server name of the sender"
292			);
293		}
294
295		// Otherwise, allow.
296		trace!("`m.room.aliases` event was allowed");
297		return Ok(());
298	}
299
300	// Since v1, if type is m.room.member:
301	if *incoming_event.event_type() == TimelineEventType::RoomMember {
302		let room_member_event = RoomMemberEvent::new(incoming_event.clone());
303		return check_room_member(
304			&room_member_event,
305			&rules.authorization,
306			&room_create_event,
307			fetch_state,
308		)
309		.boxed()
310		.await;
311	}
312
313	// Since v1, if the sender's current membership state is not join, reject.
314	let sender_membership = sender_membership?;
315	if sender_membership != MembershipState::Join {
316		return Err!("sender's membership `{sender_membership}` is not `join`");
317	}
318
319	let creators = room_create_event.creators(&rules.authorization)?;
320	let sender_power_level = current_room_power_levels_event.user_power_level(
321		sender,
322		creators.clone(),
323		&rules.authorization,
324	)?;
325
326	// Since v1, if type is m.room.third_party_invite:
327	if *incoming_event.event_type() == TimelineEventType::RoomThirdPartyInvite {
328		// Since v1, allow if and only if sender's current power level is greater than
329		// or equal to the invite level.
330		let invite_power_level = current_room_power_levels_event
331			.get_as_int_or_default(RoomPowerLevelsIntField::Invite, &rules.authorization)?;
332
333		if sender_power_level < invite_power_level {
334			return Err!(
335				"sender does not have enough power ({sender_power_level:?}) to send invites \
336				 ({invite_power_level}) in this room"
337			);
338		}
339
340		trace!("`m.room.third_party_invite` event was allowed");
341		return Ok(());
342	}
343
344	// Since v1, if the event type's required power level is greater than the
345	// sender's power level, reject.
346	let event_type_power_level = current_room_power_levels_event.event_power_level(
347		incoming_event.event_type(),
348		incoming_event.state_key(),
349		&rules.authorization,
350	)?;
351
352	if sender_power_level < event_type_power_level {
353		return Err!(
354			"sender does not have enough power ({sender_power_level:?}) for `{}` event type \
355			 ({event_type_power_level})",
356			incoming_event.event_type()
357		);
358	}
359
360	// Since v1, if the event has a state_key that starts with an @ and does not
361	// match the sender, reject.
362	if incoming_event
363		.state_key()
364		.is_some_and(|k| k.starts_with('@'))
365		&& incoming_event.state_key() != Some(incoming_event.sender().as_str())
366	{
367		return Err!("sender cannot send event with `state_key` matching another user's ID");
368	}
369
370	// If type is m.room.power_levels
371	if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
372		let room_power_levels_event = RoomPowerLevelsEvent::new(incoming_event.clone());
373		return check_room_power_levels(
374			&room_power_levels_event,
375			current_room_power_levels_event.as_ref(),
376			&rules.authorization,
377			sender_power_level,
378			creators,
379		);
380	}
381
382	// v1-v2, if type is m.room.redaction:
383	if rules.authorization.special_case_room_redaction
384		&& *incoming_event.event_type() == TimelineEventType::RoomRedaction
385	{
386		return check_room_redaction(
387			incoming_event,
388			current_room_power_levels_event.as_ref(),
389			&rules.authorization,
390			sender_power_level,
391		);
392	}
393
394	// Otherwise, allow.
395	trace!("allowing event passed all checks");
396	Ok(())
397}
398
399/// Check whether the given event passes the `m.room.create` authorization
400/// rules.
401#[tracing::instrument(level = "trace", skip_all)]
402fn check_room_create<Pdu>(
403	room_create_event: &RoomCreateEvent<Pdu>,
404	rules: &AuthorizationRules,
405) -> Result
406where
407	Pdu: Event,
408{
409	// Since v1, if it has any previous events, reject.
410	if room_create_event.prev_events().next().is_some() {
411		return Err!("`m.room.create` event cannot have previous events");
412	}
413
414	if rules.room_create_event_id_as_room_id {
415		let Ok(room_create_event_id) = room_create_event.room_id().as_event_id() else {
416			return Err!(Request(InvalidParam(
417				"Failed to create `event_id` out of `m.room.create` synthetic `room_id`"
418			)));
419		};
420
421		if room_create_event_id != room_create_event.event_id() {
422			return Err!(Request(InvalidParam(
423				"`m.room.create` has mismatching synthetic `room_id` and `event_id`"
424			)));
425		}
426	} else {
427		// v1-v11, if the domain of the room_id does not match the domain of the sender,
428		// reject.
429		let Some(room_id_server_name) = room_create_event.room_id().server_name() else {
430			return Err!("Invalid `ServerName` for `room_id` in `m.room.create` event");
431		};
432
433		if room_id_server_name != room_create_event.sender().server_name() {
434			return Err!(
435				"Mismatched `ServerName` for `room_id` in `m.room.create` with `sender`"
436			);
437		}
438	}
439
440	// Since v1, if `content.room_version` is present and is not a recognized
441	// version, reject.
442	//
443	// This check is assumed to be done before calling auth_check because we have an
444	// AuthorizationRules, which means that we recognized the version.
445
446	// v1-v10, if content has no creator field, reject.
447	if !rules.use_room_create_sender && !room_create_event.has_creator()? {
448		return Err!("missing `creator` field in `m.room.create` event");
449	}
450
451	// Otherwise, allow.
452	trace!("`m.room.create` event was allowed");
453	Ok(())
454}
455
456/// Check whether the given event passes the `m.room.power_levels` authorization
457/// rules.
458#[tracing::instrument(level = "trace", skip_all)]
459fn check_room_power_levels<Creators, Pdu>(
460	room_power_levels_event: &RoomPowerLevelsEvent<Pdu>,
461	current_room_power_levels_event: Option<&RoomPowerLevelsEvent<Pdu>>,
462	rules: &AuthorizationRules,
463	sender_power_level: impl Into<UserPowerLevel>,
464	mut room_creators: Creators,
465) -> Result
466where
467	Creators: Iterator<Item = OwnedUserId> + Clone,
468	Pdu: Event,
469{
470	let sender_power_level = sender_power_level.into();
471
472	// Since v10, if any of the properties users_default, events_default,
473	// state_default, ban, redact, kick, or invite in content are present and not
474	// an integer, reject.
475	let new_int_fields = room_power_levels_event.int_fields_map(rules)?;
476
477	// Since v10, if either of the properties events or notifications in content are
478	// present and not a dictionary with values that are integers, reject.
479	let new_events = room_power_levels_event.events(rules)?;
480	let new_notifications = room_power_levels_event.notifications(rules)?;
481
482	// v1-v9, If the users property in content is not an object with keys that are
483	// valid user IDs with values that are integers (or a string that is an
484	// integer), reject. Since v10, if the users property in content is not an
485	// object with keys that are valid user IDs with values that are integers,
486	// reject.
487	let new_users = room_power_levels_event.users(rules)?;
488
489	// Since `org.matrix.hydra.11`, if the `users` property in `content` contains
490	// the `sender` of
491
492	// the `m.room.create` event or any of the user IDs in the create event's
493	// `content.additional_creators`, reject.
494	if rules.explicitly_privilege_room_creators
495		&& new_users.as_ref().is_some_and(|new_users| {
496			room_creators.any(|creator| power_levels::contains_key(new_users, &creator))
497		}) {
498		return Err!(Request(InvalidParam(
499			"creator user IDs are not allowed in the `users` field"
500		)));
501	}
502
503	trace!("validation of power event finished");
504
505	// Since v1, if there is no previous m.room.power_levels event in the room,
506	// allow.
507	let Some(current_room_power_levels_event) = current_room_power_levels_event else {
508		trace!("initial m.room.power_levels event allowed");
509		return Ok(());
510	};
511
512	// Since v1, for the properties users_default, events_default, state_default,
513	// ban, redact, kick, invite check if they were added, changed or removed. For
514	// each found alteration:
515	for field in RoomPowerLevelsIntField::ALL {
516		let current_power_level = current_room_power_levels_event.get_as_int(*field, rules)?;
517		let new_power_level = power_levels::get_value(&new_int_fields, field).copied();
518
519		if current_power_level == new_power_level {
520			continue;
521		}
522
523		// Since v1, if the current value is higher than the sender’s current power
524		// level, reject.
525		let current_power_level_too_big =
526			current_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
527
528		// Since v1, if the new value is higher than the sender’s current power level,
529		// reject.
530		let new_power_level_too_big =
531			new_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
532
533		if current_power_level_too_big || new_power_level_too_big {
534			return Err!(
535				"sender does not have enough power to change the power level of `{field}`"
536			);
537		}
538	}
539
540	// Since v1, for each entry being added to, or changed in, the events property:
541	// - Since v1, if the new value is higher than the sender's current power level,
542	//   reject.
543	let current_events = current_room_power_levels_event.events(rules)?;
544	check_power_level_maps(
545		current_events.as_deref(),
546		new_events.as_deref(),
547		sender_power_level,
548		|_, current_power_level| {
549			// Since v1, for each entry being changed in, or removed from, the events
550			// property:
551			// - Since v1, if the current value is higher than the sender's current power
552			//   level, reject.
553			current_power_level > sender_power_level
554		},
555		|ev_type| {
556			err!(
557				"sender does not have enough power to change the `{ev_type}` event type power \
558				 level"
559			)
560		},
561	)?;
562
563	// Since v6, for each entry being added to, or changed in, the notifications
564	// property:
565	// - Since v6, if the new value is higher than the sender's current power level,
566	//   reject.
567	if rules.limit_notifications_power_levels {
568		let current_notifications = current_room_power_levels_event.notifications(rules)?;
569		check_power_level_maps(
570			current_notifications.as_deref(),
571			new_notifications.as_deref(),
572			sender_power_level,
573			|_, current_power_level| {
574				// Since v6, for each entry being changed in, or removed from, the notifications
575				// property:
576				// - Since v6, if the current value is higher than the sender's current power
577				//   level, reject.
578				current_power_level > sender_power_level
579			},
580			|key| {
581				err!(
582					"sender does not have enough power to change the `{key}` notification power \
583					 level"
584				)
585			},
586		)?;
587	}
588
589	// Since v1, for each entry being added to, or changed in, the users property:
590	// - Since v1, if the new value is greater than the sender’s current power
591	//   level, reject.
592	let current_users = current_room_power_levels_event.users(rules)?;
593	check_power_level_maps(
594		current_users.as_deref(),
595		new_users.as_deref(),
596		sender_power_level,
597		|user_id, current_power_level| {
598			// Since v1, for each entry being changed in, or removed from, the users
599			// property, other than the sender’s own entry:
600			// - Since v1, if the current value is greater than or equal to the sender’s
601			//   current power level, reject.
602			user_id != room_power_levels_event.sender()
603				&& current_power_level >= sender_power_level
604		},
605		|user_id| err!("sender does not have enough power to change `{user_id}`'s  power level"),
606	)?;
607
608	// Otherwise, allow.
609	trace!("m.room.power_levels event allowed");
610	Ok(())
611}
612
613/// Check the power levels changes between the current and the new maps.
614///
615/// # Arguments
616///
617/// * `current`: the map with the current power levels.
618/// * `new`: the map with the new power levels.
619/// * `sender_power_level`: the power level of the sender of the new map.
620/// * `reject_current_power_level_change_fn`: the function to check if a power
621///   level change or removal must be rejected given its current value.
622///
623///   The arguments to the method are the key of the power level and the current
624///   value of the power   level. It must return `true` if the change or removal
625///   is rejected.
626///
627///   Note that another check is done after this one to check if the change is
628///   allowed given the new   value of the power level.
629/// * `error_fn`: the function to generate an error when the change for the
630///   given key is not allowed.
631fn check_power_level_maps<'a, K>(
632	current: Option<&'a [(K, Int)]>,
633	new: Option<&'a [(K, Int)]>,
634	sender_power_level: UserPowerLevel,
635	reject_current_power_level_change_fn: impl FnOnce(&K, Int) -> bool + Copy,
636	error_fn: impl FnOnce(&K) -> Error,
637) -> Result
638where
639	K: Ord,
640{
641	let keys_to_check = current
642		.iter()
643		.flat_map(|m| m.iter().map(|(k, _)| k))
644		.chain(new.iter().flat_map(|m| m.iter().map(|(k, _)| k)));
645
646	for key in keys_to_check {
647		let current_power_level = current.and_then(|m| power_levels::get_value(m, key));
648		let new_power_level = new.and_then(|m| power_levels::get_value(m, key));
649
650		if current_power_level == new_power_level {
651			continue;
652		}
653
654		// For each entry being changed in, or removed from, the property.
655		let current_power_level_change_rejected = current_power_level
656			.is_some_and(|power_level| reject_current_power_level_change_fn(key, *power_level));
657
658		// For each entry being added to, or changed in, the property:
659		// - If the new value is higher than the sender's current power level, reject.
660		let new_power_level_too_big =
661			new_power_level.is_some_and(|&new_power_level| new_power_level > sender_power_level);
662
663		if current_power_level_change_rejected || new_power_level_too_big {
664			return Err(error_fn(key));
665		}
666	}
667
668	Ok(())
669}
670
671/// Check whether the given event passes the `m.room.redaction` authorization
672/// rules.
673fn check_room_redaction<Pdu>(
674	room_redaction_event: &Pdu,
675	current_room_power_levels_event: Option<&RoomPowerLevelsEvent<Pdu>>,
676	rules: &AuthorizationRules,
677	sender_level: UserPowerLevel,
678) -> Result
679where
680	Pdu: Event,
681{
682	let redact_level = current_room_power_levels_event
683		.cloned()
684		.get_as_int_or_default(RoomPowerLevelsIntField::Redact, rules)?;
685
686	// v1-v2, if the sender’s power level is greater than or equal to the redact
687	// level, allow.
688	if sender_level >= redact_level {
689		trace!("`m.room.redaction` event allowed via power levels");
690		return Ok(());
691	}
692
693	// v1-v2, if the domain of the event_id of the event being redacted is the same
694	// as the domain of the event_id of the m.room.redaction, allow.
695	if room_redaction_event.event_id().server_name()
696		== room_redaction_event
697			.redacts()
698			.as_ref()
699			.and_then(|&id| id.server_name())
700	{
701		trace!("`m.room.redaction` event allowed via room version 1 rules");
702		return Ok(());
703	}
704
705	// Otherwise, reject.
706	Err!("`m.room.redaction` event did not pass any of the allow rules")
707}