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(Error::AuthCheck(Box::new(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	// Since v1, if it has any auth events, reject.
415	if room_create_event.auth_events().next().is_some() {
416		return Err!("`m.room.create` event cannot have `auth_events`");
417	}
418
419	if rules.room_create_event_id_as_room_id {
420		let Ok(room_create_event_id) = room_create_event.room_id().as_event_id() else {
421			return Err!(Request(InvalidParam(
422				"Failed to create `event_id` out of `m.room.create` synthetic `room_id`"
423			)));
424		};
425
426		if room_create_event_id != room_create_event.event_id() {
427			return Err!(Request(InvalidParam(
428				"`m.room.create` has mismatching synthetic `room_id` and `event_id`"
429			)));
430		}
431	} else {
432		// v1-v11, if the domain of the room_id does not match the domain of the sender,
433		// reject.
434		let Some(room_id_server_name) = room_create_event.room_id().server_name() else {
435			return Err!("Invalid `ServerName` for `room_id` in `m.room.create` event");
436		};
437
438		if room_id_server_name != room_create_event.sender().server_name() {
439			return Err!(
440				"Mismatched `ServerName` for `room_id` in `m.room.create` with `sender`"
441			);
442		}
443	}
444
445	// Since v1, if `content.room_version` is present and is not a recognized
446	// version, reject.
447	//
448	// This check is assumed to be done before calling auth_check because we have an
449	// AuthorizationRules, which means that we recognized the version.
450
451	// v1-v10, if content has no creator field, reject.
452	if !rules.use_room_create_sender && !room_create_event.has_creator()? {
453		return Err!("missing `creator` field in `m.room.create` event");
454	}
455
456	// Otherwise, allow.
457	trace!("`m.room.create` event was allowed");
458	Ok(())
459}
460
461/// Check whether the given event passes the `m.room.power_levels` authorization
462/// rules.
463#[tracing::instrument(level = "trace", skip_all)]
464fn check_room_power_levels<Creators, Pdu>(
465	room_power_levels_event: &RoomPowerLevelsEvent<Pdu>,
466	current_room_power_levels_event: Option<&RoomPowerLevelsEvent<Pdu>>,
467	rules: &AuthorizationRules,
468	sender_power_level: impl Into<UserPowerLevel>,
469	mut room_creators: Creators,
470) -> Result
471where
472	Creators: Iterator<Item = OwnedUserId> + Clone,
473	Pdu: Event,
474{
475	let sender_power_level = sender_power_level.into();
476
477	// Since v10, if any of the properties users_default, events_default,
478	// state_default, ban, redact, kick, or invite in content are present and not
479	// an integer, reject.
480	let new_int_fields = room_power_levels_event.int_fields_map(rules)?;
481
482	// Since v10, if either of the properties events or notifications in content are
483	// present and not a dictionary with values that are integers, reject.
484	let new_events = room_power_levels_event.events(rules)?;
485	let new_notifications = room_power_levels_event.notifications(rules)?;
486
487	// v1-v9, If the users property in content is not an object with keys that are
488	// valid user IDs with values that are integers (or a string that is an
489	// integer), reject. Since v10, if the users property in content is not an
490	// object with keys that are valid user IDs with values that are integers,
491	// reject.
492	let new_users = room_power_levels_event.users(rules)?;
493
494	// Since `org.matrix.hydra.11`, if the `users` property in `content` contains
495	// the `sender` of
496
497	// the `m.room.create` event or any of the user IDs in the create event's
498	// `content.additional_creators`, reject.
499	if rules.explicitly_privilege_room_creators
500		&& new_users.as_ref().is_some_and(|new_users| {
501			room_creators.any(|creator| power_levels::contains_key(new_users, &creator))
502		}) {
503		return Err!(Request(InvalidParam(
504			"creator user IDs are not allowed in the `users` field"
505		)));
506	}
507
508	trace!("validation of power event finished");
509
510	// Since v1, if there is no previous m.room.power_levels event in the room,
511	// allow.
512	let Some(current_room_power_levels_event) = current_room_power_levels_event else {
513		trace!("initial m.room.power_levels event allowed");
514		return Ok(());
515	};
516
517	// Since v1, for the properties users_default, events_default, state_default,
518	// ban, redact, kick, invite check if they were added, changed or removed. For
519	// each found alteration:
520	for field in RoomPowerLevelsIntField::ALL {
521		let current_power_level = current_room_power_levels_event.get_as_int(*field, rules)?;
522		let new_power_level = power_levels::get_value(&new_int_fields, field).copied();
523
524		if current_power_level == new_power_level {
525			continue;
526		}
527
528		// Since v1, if the current value is higher than the sender’s current power
529		// level, reject.
530		let current_power_level_too_big =
531			current_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
532
533		// Since v1, if the new value is higher than the sender’s current power level,
534		// reject.
535		let new_power_level_too_big =
536			new_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
537
538		if current_power_level_too_big || new_power_level_too_big {
539			return Err!(
540				"sender does not have enough power to change the power level of `{field}`"
541			);
542		}
543	}
544
545	// Since v1, for each entry being added to, or changed in, the events property:
546	// - Since v1, if the new value is higher than the sender's current power level,
547	//   reject.
548	let current_events = current_room_power_levels_event.events(rules)?;
549	check_power_level_maps(
550		current_events.as_deref(),
551		new_events.as_deref(),
552		sender_power_level,
553		|_, current_power_level| {
554			// Since v1, for each entry being changed in, or removed from, the events
555			// property:
556			// - Since v1, if the current value is higher than the sender's current power
557			//   level, reject.
558			current_power_level > sender_power_level
559		},
560		|ev_type| {
561			err!(
562				"sender does not have enough power to change the `{ev_type}` event type power \
563				 level"
564			)
565		},
566	)?;
567
568	// Since v6, for each entry being added to, or changed in, the notifications
569	// property:
570	// - Since v6, if the new value is higher than the sender's current power level,
571	//   reject.
572	if rules.limit_notifications_power_levels {
573		let current_notifications = current_room_power_levels_event.notifications(rules)?;
574		check_power_level_maps(
575			current_notifications.as_deref(),
576			new_notifications.as_deref(),
577			sender_power_level,
578			|_, current_power_level| {
579				// Since v6, for each entry being changed in, or removed from, the notifications
580				// property:
581				// - Since v6, if the current value is higher than the sender's current power
582				//   level, reject.
583				current_power_level > sender_power_level
584			},
585			|key| {
586				err!(
587					"sender does not have enough power to change the `{key}` notification power \
588					 level"
589				)
590			},
591		)?;
592	}
593
594	// Since v1, for each entry being added to, or changed in, the users property:
595	// - Since v1, if the new value is greater than the sender’s current power
596	//   level, reject.
597	let current_users = current_room_power_levels_event.users(rules)?;
598	check_power_level_maps(
599		current_users.as_deref(),
600		new_users.as_deref(),
601		sender_power_level,
602		|user_id, current_power_level| {
603			// Since v1, for each entry being changed in, or removed from, the users
604			// property, other than the sender’s own entry:
605			// - Since v1, if the current value is greater than or equal to the sender’s
606			//   current power level, reject.
607			user_id != room_power_levels_event.sender()
608				&& current_power_level >= sender_power_level
609		},
610		|user_id| err!("sender does not have enough power to change `{user_id}`'s  power level"),
611	)?;
612
613	// Otherwise, allow.
614	trace!("m.room.power_levels event allowed");
615	Ok(())
616}
617
618/// Check the power levels changes between the current and the new maps.
619///
620/// # Arguments
621///
622/// * `current`: the map with the current power levels.
623/// * `new`: the map with the new power levels.
624/// * `sender_power_level`: the power level of the sender of the new map.
625/// * `reject_current_power_level_change_fn`: the function to check if a power
626///   level change or removal must be rejected given its current value.
627///
628///   The arguments to the method are the key of the power level and the current
629///   value of the power   level. It must return `true` if the change or removal
630///   is rejected.
631///
632///   Note that another check is done after this one to check if the change is
633///   allowed given the new   value of the power level.
634/// * `error_fn`: the function to generate an error when the change for the
635///   given key is not allowed.
636fn check_power_level_maps<'a, K>(
637	current: Option<&'a [(K, Int)]>,
638	new: Option<&'a [(K, Int)]>,
639	sender_power_level: UserPowerLevel,
640	reject_current_power_level_change_fn: impl FnOnce(&K, Int) -> bool + Copy,
641	error_fn: impl FnOnce(&K) -> Error,
642) -> Result
643where
644	K: Ord,
645{
646	let keys_to_check = current
647		.iter()
648		.flat_map(|m| m.iter().map(|(k, _)| k))
649		.chain(new.iter().flat_map(|m| m.iter().map(|(k, _)| k)));
650
651	for key in keys_to_check {
652		let current_power_level = current.and_then(|m| power_levels::get_value(m, key));
653		let new_power_level = new.and_then(|m| power_levels::get_value(m, key));
654
655		if current_power_level == new_power_level {
656			continue;
657		}
658
659		// For each entry being changed in, or removed from, the property.
660		let current_power_level_change_rejected = current_power_level
661			.is_some_and(|power_level| reject_current_power_level_change_fn(key, *power_level));
662
663		// For each entry being added to, or changed in, the property:
664		// - If the new value is higher than the sender's current power level, reject.
665		let new_power_level_too_big =
666			new_power_level.is_some_and(|&new_power_level| new_power_level > sender_power_level);
667
668		if current_power_level_change_rejected || new_power_level_too_big {
669			return Err(error_fn(key));
670		}
671	}
672
673	Ok(())
674}
675
676/// Check whether the given event passes the `m.room.redaction` authorization
677/// rules.
678fn check_room_redaction<Pdu>(
679	room_redaction_event: &Pdu,
680	current_room_power_levels_event: Option<&RoomPowerLevelsEvent<Pdu>>,
681	rules: &AuthorizationRules,
682	sender_level: UserPowerLevel,
683) -> Result
684where
685	Pdu: Event,
686{
687	let redact_level = current_room_power_levels_event
688		.cloned()
689		.get_as_int_or_default(RoomPowerLevelsIntField::Redact, rules)?;
690
691	// v1-v2, if the sender’s power level is greater than or equal to the redact
692	// level, allow.
693	if sender_level >= redact_level {
694		trace!("`m.room.redaction` event allowed via power levels");
695		return Ok(());
696	}
697
698	// v1-v2, if the domain of the event_id of the event being redacted is the same
699	// as the domain of the event_id of the m.room.redaction, allow.
700	if room_redaction_event.event_id().server_name()
701		== room_redaction_event
702			.redacts()
703			.as_ref()
704			.and_then(|&id| id.server_name())
705	{
706		trace!("`m.room.redaction` event allowed via room version 1 rules");
707		return Ok(());
708	}
709
710	// Otherwise, reject.
711	Err!("`m.room.redaction` event did not pass any of the allow rules")
712}