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