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#[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 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 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 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 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 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 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 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 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#[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 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 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 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 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 trace!("`m.room.aliases` event was allowed");
297 return Ok(());
298 }
299
300 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 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 if *incoming_event.event_type() == TimelineEventType::RoomThirdPartyInvite {
328 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 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 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 *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 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 trace!("allowing event passed all checks");
396 Ok(())
397}
398
399#[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 if room_create_event.prev_events().next().is_some() {
411 return Err!("`m.room.create` event cannot have previous events");
412 }
413
414 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 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 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 trace!("`m.room.create` event was allowed");
458 Ok(())
459}
460
461#[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 let new_int_fields = room_power_levels_event.int_fields_map(rules)?;
481
482 let new_events = room_power_levels_event.events(rules)?;
485 let new_notifications = room_power_levels_event.notifications(rules)?;
486
487 let new_users = room_power_levels_event.users(rules)?;
493
494 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 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 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 let current_power_level_too_big =
531 current_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
532
533 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 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 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 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 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 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 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 trace!("m.room.power_levels event allowed");
615 Ok(())
616}
617
618fn 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 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 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
676fn 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 if sender_level >= redact_level {
694 trace!("`m.room.redaction` event allowed via power levels");
695 return Ok(());
696 }
697
698 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 Err!("`m.room.redaction` event did not pass any of the allow rules")
712}