Skip to main content

tuwunel_matrix/room/state/resolution/
event_auth.rs

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