1use std::{
2 borrow::Borrow,
3 collections::{BTreeMap, BTreeSet, HashSet},
4};
5
6use js_int::Int;
7use ruma_common::{room::JoinRuleKind, room_version_rules::AuthorizationRules, EventId, UserId};
8use ruma_events::{room::member::MembershipState, StateEventType, TimelineEventType};
9use serde_json::value::RawValue as RawJsonValue;
10use tracing::{debug, info, instrument, warn};
11
12mod room_member;
13#[cfg(test)]
14mod tests;
15
16use self::room_member::check_room_member;
17use crate::{
18 events::{
19 member::{RoomMemberEventContent, RoomMemberEventOptionExt},
20 power_levels::{RoomPowerLevelsEventOptionExt, RoomPowerLevelsIntField},
21 RoomCreateEvent, RoomJoinRulesEvent, RoomMemberEvent, RoomPowerLevelsEvent,
22 RoomThirdPartyInviteEvent,
23 },
24 Event,
25};
26
27pub fn auth_types_for_event(
38 event_type: &TimelineEventType,
39 sender: &UserId,
40 state_key: Option<&str>,
41 content: &RawJsonValue,
42 rules: &AuthorizationRules,
43) -> Result<Vec<(StateEventType, String)>, String> {
44 if event_type == &TimelineEventType::RoomCreate {
46 return Ok(vec![]);
47 }
48
49 let mut auth_types = vec![
55 (StateEventType::RoomPowerLevels, "".to_owned()),
56 (StateEventType::RoomMember, sender.to_string()),
57 (StateEventType::RoomCreate, "".to_owned()),
58 ];
59
60 if event_type == &TimelineEventType::RoomMember {
62 let Some(state_key) = state_key else {
64 return Err("missing `state_key` field for `m.room.member` event".to_owned());
65 };
66 let key = (StateEventType::RoomMember, state_key.to_owned());
67 if !auth_types.contains(&key) {
68 auth_types.push(key);
69 }
70
71 let content = RoomMemberEventContent::new(content);
72 let membership = content.membership()?;
73
74 if matches!(
77 membership,
78 MembershipState::Join | MembershipState::Invite | MembershipState::Knock
79 ) {
80 let key = (StateEventType::RoomJoinRules, "".to_owned());
81 if !auth_types.contains(&key) {
82 auth_types.push(key);
83 }
84 }
85
86 if membership == MembershipState::Invite {
90 let third_party_invite = content.third_party_invite()?;
91
92 if let Some(third_party_invite) = third_party_invite {
93 let token = third_party_invite.token()?.to_owned();
94 let key = (StateEventType::RoomThirdPartyInvite, token);
95 if !auth_types.contains(&key) {
96 auth_types.push(key);
97 }
98 }
99 }
100
101 if membership == MembershipState::Join && rules.restricted_join_rule {
107 let join_authorised_via_users_server = content.join_authorised_via_users_server()?;
108 if let Some(user_id) = join_authorised_via_users_server {
109 let key = (StateEventType::RoomMember, user_id.to_string());
110 if !auth_types.contains(&key) {
111 auth_types.push(key);
112 }
113 }
114 }
115 }
116
117 Ok(auth_types)
118}
119
120#[instrument(skip_all, fields(event_id = incoming_event.event_id().borrow().as_str()))]
135pub fn check_state_independent_auth_rules<E: Event>(
136 rules: &AuthorizationRules,
137 incoming_event: impl Event,
138 fetch_event: impl Fn(&EventId) -> Option<E>,
139) -> Result<(), String> {
140 debug!("starting state-independent auth check");
141
142 if *incoming_event.event_type() == TimelineEventType::RoomCreate {
144 let room_create_event = RoomCreateEvent::new(incoming_event);
145
146 return check_room_create(room_create_event, rules);
147 }
148
149 let expected_auth_types = auth_types_for_event(
150 incoming_event.event_type(),
151 incoming_event.sender(),
152 incoming_event.state_key(),
153 incoming_event.content(),
154 rules,
155 )?
156 .into_iter()
157 .map(|(event_type, state_key)| (TimelineEventType::from(event_type), state_key))
158 .collect::<HashSet<_>>();
159
160 let room_id = incoming_event.room_id();
161 let mut seen_auth_types: HashSet<(TimelineEventType, String)> =
162 HashSet::with_capacity(expected_auth_types.len());
163
164 for auth_event_id in incoming_event.auth_events() {
166 let event_id = auth_event_id.borrow();
167
168 let Some(auth_event) = fetch_event(event_id) else {
169 return Err(format!("failed to find auth event {event_id}"));
170 };
171
172 if auth_event.room_id() != room_id {
174 return Err(format!("auth event {event_id} not in the same room"));
175 }
176
177 let event_type = auth_event.event_type();
178 let state_key = auth_event
179 .state_key()
180 .ok_or_else(|| format!("auth event {event_id} has no `state_key`"))?;
181 let key = (event_type.clone(), state_key.to_owned());
182
183 if seen_auth_types.contains(&key) {
185 return Err(format!(
186 "duplicate auth event {event_id} for ({event_type}, {state_key}) pair"
187 ));
188 }
189
190 if !expected_auth_types.contains(&key) {
193 return Err(format!(
194 "unexpected auth event {event_id} with ({event_type}, {state_key}) pair"
195 ));
196 }
197
198 if auth_event.rejected() {
201 return Err(format!("rejected auth event {event_id}"));
202 }
203
204 seen_auth_types.insert(key);
205 }
206
207 if !seen_auth_types.iter().any(|(event_type, _)| *event_type == TimelineEventType::RoomCreate) {
209 return Err("no `m.room.create` event in auth events".to_owned());
210 }
211
212 Ok(())
213}
214
215#[instrument(skip_all, fields(event_id = incoming_event.event_id().borrow().as_str()))]
237pub fn check_state_dependent_auth_rules<E: Event>(
238 rules: &AuthorizationRules,
239 incoming_event: impl Event,
240 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
241) -> Result<(), String> {
242 debug!("starting state-dependent auth check");
243
244 if *incoming_event.event_type() == TimelineEventType::RoomCreate {
246 debug!("allowing `m.room.create` event");
247 return Ok(());
248 }
249
250 let room_create_event = fetch_state.room_create_event()?;
251
252 let federate = room_create_event.federate()?;
255 if !federate
256 && room_create_event.sender().server_name() != incoming_event.sender().server_name()
257 {
258 return Err("\
259 room is not federated and event's sender domain \
260 does not match `m.room.create` event's sender domain"
261 .to_owned());
262 }
263
264 let sender = incoming_event.sender();
265
266 if rules.special_case_room_aliases
268 && *incoming_event.event_type() == TimelineEventType::RoomAliases
269 {
270 debug!("starting m.room.aliases check");
271 if incoming_event.state_key() != Some(sender.server_name().as_str()) {
275 return Err("\
276 server name of the `state_key` of `m.room.aliases` event \
277 does not match the server name of the sender"
278 .to_owned());
279 }
280
281 info!("`m.room.aliases` event was allowed");
283 return Ok(());
284 }
285
286 if *incoming_event.event_type() == TimelineEventType::RoomMember {
288 let room_member_event = RoomMemberEvent::new(incoming_event);
289 return check_room_member(room_member_event, rules, room_create_event, fetch_state);
290 }
291
292 let sender_membership = fetch_state.user_membership(sender)?;
294
295 if sender_membership != MembershipState::Join {
296 return Err("sender's membership is not `join`".to_owned());
297 }
298
299 let creator = room_create_event.creator(rules)?;
300 let current_room_power_levels_event = fetch_state.room_power_levels_event();
301
302 let sender_power_level =
303 current_room_power_levels_event.user_power_level(sender, &creator, rules)?;
304
305 if *incoming_event.event_type() == TimelineEventType::RoomThirdPartyInvite {
307 let invite_power_level = current_room_power_levels_event
310 .get_as_int_or_default(RoomPowerLevelsIntField::Invite, rules)?;
311
312 if sender_power_level < invite_power_level {
313 return Err("sender does not have enough power to send invites in this room".to_owned());
314 }
315
316 info!("`m.room.third_party_invite` event was allowed");
317 return Ok(());
318 }
319
320 let event_type_power_level = current_room_power_levels_event.event_power_level(
323 incoming_event.event_type(),
324 incoming_event.state_key(),
325 rules,
326 )?;
327 if sender_power_level < event_type_power_level {
328 return Err(format!(
329 "sender does not have enough power to send event of type `{}`",
330 incoming_event.event_type()
331 ));
332 }
333
334 if incoming_event.state_key().is_some_and(|k| k.starts_with('@'))
337 && incoming_event.state_key() != Some(incoming_event.sender().as_str())
338 {
339 return Err(
340 "sender cannot send event with `state_key` matching another user's ID".to_owned()
341 );
342 }
343
344 if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
346 let room_power_levels_event = RoomPowerLevelsEvent::new(incoming_event);
347 return check_room_power_levels(
348 room_power_levels_event,
349 current_room_power_levels_event,
350 rules,
351 sender_power_level,
352 );
353 }
354
355 if rules.special_case_room_redaction
357 && *incoming_event.event_type() == TimelineEventType::RoomRedaction
358 {
359 return check_room_redaction(
360 incoming_event,
361 current_room_power_levels_event,
362 rules,
363 sender_power_level,
364 );
365 }
366
367 info!("allowing event passed all checks");
369 Ok(())
370}
371
372fn check_room_create(
374 room_create_event: RoomCreateEvent<impl Event>,
375 rules: &AuthorizationRules,
376) -> Result<(), String> {
377 debug!("start `m.room.create` check");
378
379 if room_create_event.prev_events().next().is_some() {
381 return Err("`m.room.create` event cannot have previous events".into());
382 }
383
384 let Some(room_id_server_name) = room_create_event.room_id().server_name() else {
386 return Err(
387 "invalid `room_id` field in `m.room.create` event: could not parse server name".into(),
388 );
389 };
390
391 if room_id_server_name != room_create_event.sender().server_name() {
392 return Err("invalid `room_id` field in `m.room.create` event: server name does not match sender's server name".into());
393 }
394
395 if !rules.use_room_create_sender && !room_create_event.has_creator()? {
402 return Err("missing `creator` field in `m.room.create` event".into());
403 }
404
405 info!("`m.room.create` event was allowed");
407 Ok(())
408}
409
410fn check_room_power_levels(
412 room_power_levels_event: RoomPowerLevelsEvent<impl Event>,
413 current_room_power_levels_event: Option<RoomPowerLevelsEvent<impl Event>>,
414 rules: &AuthorizationRules,
415 sender_power_level: Int,
416) -> Result<(), String> {
417 debug!("starting m.room.power_levels check");
418
419 let new_int_fields = room_power_levels_event.int_fields_map(rules)?;
422
423 let new_events = room_power_levels_event.events(rules)?;
426 let new_notifications = room_power_levels_event.notifications(rules)?;
427
428 let new_users = room_power_levels_event.users(rules)?;
433
434 debug!("validation of power event finished");
435
436 let Some(current_room_power_levels_event) = current_room_power_levels_event else {
438 info!("initial m.room.power_levels event allowed");
439 return Ok(());
440 };
441
442 for field in RoomPowerLevelsIntField::ALL {
445 let current_power_level = current_room_power_levels_event.get_as_int(*field, rules)?;
446 let new_power_level = new_int_fields.get(field).copied();
447
448 if current_power_level == new_power_level {
449 continue;
450 }
451
452 let current_power_level_too_big =
455 current_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
456 let new_power_level_too_big =
458 new_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
459
460 if current_power_level_too_big || new_power_level_too_big {
461 return Err(format!(
462 "sender does not have enough power to change the power level of `{field}`"
463 ));
464 }
465 }
466
467 let current_events = current_room_power_levels_event.events(rules)?;
470 check_power_level_maps(
471 current_events.as_ref(),
472 new_events.as_ref(),
473 &sender_power_level,
474 |_, current_power_level| {
475 current_power_level > sender_power_level
479 },
480 |ev_type| {
481 format!(
482 "sender does not have enough power to change the `{ev_type}` event type power level"
483 )
484 },
485 )?;
486
487 if rules.limit_notifications_power_levels {
490 let current_notifications = current_room_power_levels_event.notifications(rules)?;
491 check_power_level_maps(
492 current_notifications.as_ref(),
493 new_notifications.as_ref(),
494 &sender_power_level,
495 |_, current_power_level| {
496 current_power_level > sender_power_level
501 },
502 |key| {
503 format!(
504 "sender does not have enough power to change the `{key}` notification power level"
505 )
506 },
507 )?;
508 }
509
510 let current_users = current_room_power_levels_event.users(rules)?;
513 check_power_level_maps(
514 current_users,
515 new_users,
516 &sender_power_level,
517 |user_id, current_power_level| {
518 user_id != room_power_levels_event.sender() && current_power_level >= sender_power_level
523 },
524 |user_id| format!("sender does not have enough power to change `{user_id}`'s power level"),
525 )?;
526
527 info!("m.room.power_levels event allowed");
529 Ok(())
530}
531
532fn check_power_level_maps<K: Ord>(
550 current: Option<&BTreeMap<K, Int>>,
551 new: Option<&BTreeMap<K, Int>>,
552 sender_power_level: &Int,
553 reject_current_power_level_change_fn: impl FnOnce(&K, Int) -> bool + Copy,
554 error_fn: impl FnOnce(&K) -> String,
555) -> Result<(), String> {
556 let keys_to_check = current
557 .iter()
558 .flat_map(|m| m.keys())
559 .chain(new.iter().flat_map(|m| m.keys()))
560 .collect::<BTreeSet<_>>();
561
562 for key in keys_to_check {
563 let current_power_level = current.as_ref().and_then(|m| m.get(key));
564 let new_power_level = new.as_ref().and_then(|m| m.get(key));
565
566 if current_power_level == new_power_level {
567 continue;
568 }
569
570 let current_power_level_change_rejected = current_power_level
572 .is_some_and(|power_level| reject_current_power_level_change_fn(key, *power_level));
573
574 let new_power_level_too_big = new_power_level > Some(sender_power_level);
577
578 if current_power_level_change_rejected || new_power_level_too_big {
579 return Err(error_fn(key));
580 }
581 }
582
583 Ok(())
584}
585
586fn check_room_redaction(
588 room_redaction_event: impl Event,
589 current_room_power_levels_event: Option<RoomPowerLevelsEvent<impl Event>>,
590 rules: &AuthorizationRules,
591 sender_level: Int,
592) -> Result<(), String> {
593 let redact_level = current_room_power_levels_event
594 .get_as_int_or_default(RoomPowerLevelsIntField::Redact, rules)?;
595
596 if sender_level >= redact_level {
598 info!("`m.room.redaction` event allowed via power levels");
599 return Ok(());
600 }
601
602 if room_redaction_event.event_id().borrow().server_name()
605 == room_redaction_event.redacts().as_ref().and_then(|&id| id.borrow().server_name())
606 {
607 info!("`m.room.redaction` event allowed via room version 1 rules");
608 return Ok(());
609 }
610
611 Err("`m.room.redaction` event did not pass any of the allow rules".to_owned())
613}
614
615trait FetchStateExt<E: Event> {
616 fn room_create_event(&self) -> Result<RoomCreateEvent<E>, String>;
617
618 fn user_membership(&self, user_id: &UserId) -> Result<MembershipState, String>;
619
620 fn room_power_levels_event(&self) -> Option<RoomPowerLevelsEvent<E>>;
621
622 fn join_rule(&self) -> Result<JoinRuleKind, String>;
623
624 fn room_third_party_invite_event(&self, token: &str) -> Option<RoomThirdPartyInviteEvent<E>>;
625}
626
627impl<E, F> FetchStateExt<E> for F
628where
629 F: Fn(&StateEventType, &str) -> Option<E>,
630 E: Event,
631{
632 fn room_create_event(&self) -> Result<RoomCreateEvent<E>, String> {
633 self(&StateEventType::RoomCreate, "")
634 .map(RoomCreateEvent::new)
635 .ok_or_else(|| "no `m.room.create` event in current state".to_owned())
636 }
637
638 fn user_membership(&self, user_id: &UserId) -> Result<MembershipState, String> {
639 self(&StateEventType::RoomMember, user_id.as_str()).map(RoomMemberEvent::new).membership()
640 }
641
642 fn room_power_levels_event(&self) -> Option<RoomPowerLevelsEvent<E>> {
643 self(&StateEventType::RoomPowerLevels, "").map(RoomPowerLevelsEvent::new)
644 }
645
646 fn join_rule(&self) -> Result<JoinRuleKind, String> {
647 self(&StateEventType::RoomJoinRules, "")
648 .map(RoomJoinRulesEvent::new)
649 .ok_or_else(|| "no `m.room.join_rules` event in current state".to_owned())?
650 .join_rule()
651 }
652
653 fn room_third_party_invite_event(&self, token: &str) -> Option<RoomThirdPartyInviteEvent<E>> {
654 self(&StateEventType::RoomThirdPartyInvite, token).map(RoomThirdPartyInviteEvent::new)
655 }
656}