1use std::{
2 borrow::Borrow,
3 collections::{BTreeMap, BTreeSet},
4};
5
6use js_int::Int;
7use ruma_common::UserId;
8use ruma_events::room::member::MembershipState;
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 JoinRule, RoomCreateEvent, RoomJoinRulesEvent, RoomMemberEvent, RoomPowerLevelsEvent,
22 RoomThirdPartyInviteEvent,
23 },
24 room_version::RoomVersion,
25 Event, StateEventType, TimelineEventType,
26};
27
28pub fn auth_types_for_event(
51 event_type: &TimelineEventType,
52 sender: &UserId,
53 state_key: Option<&str>,
54 content: &RawJsonValue,
55 room_version: &RoomVersion,
56) -> Result<Vec<(StateEventType, String)>, String> {
57 if event_type == &TimelineEventType::RoomCreate {
59 return Ok(vec![]);
60 }
61
62 let mut auth_types = vec![
68 (StateEventType::RoomPowerLevels, "".to_owned()),
69 (StateEventType::RoomMember, sender.to_string()),
70 (StateEventType::RoomCreate, "".to_owned()),
71 ];
72
73 if event_type == &TimelineEventType::RoomMember {
75 let Some(state_key) = state_key else {
77 return Err("missing `state_key` field for `m.room.member` event".to_owned());
78 };
79 let key = (StateEventType::RoomMember, state_key.to_owned());
80 if !auth_types.contains(&key) {
81 auth_types.push(key);
82 }
83
84 let content = RoomMemberEventContent::new(content);
85 let membership = content.membership()?;
86
87 if matches!(
90 membership,
91 MembershipState::Join | MembershipState::Invite | MembershipState::Knock
92 ) {
93 let key = (StateEventType::RoomJoinRules, "".to_owned());
94 if !auth_types.contains(&key) {
95 auth_types.push(key);
96 }
97 }
98
99 if membership == MembershipState::Invite {
103 let third_party_invite = content.third_party_invite()?;
104
105 if let Some(third_party_invite) = third_party_invite {
106 let token = third_party_invite.token()?.to_owned();
107 let key = (StateEventType::RoomThirdPartyInvite, token);
108 if !auth_types.contains(&key) {
109 auth_types.push(key);
110 }
111 }
112 }
113
114 if membership == MembershipState::Join && room_version.restricted_join_rules {
120 let join_authorised_via_users_server = content.join_authorised_via_users_server()?;
121 if let Some(user_id) = join_authorised_via_users_server {
122 let key = (StateEventType::RoomMember, user_id.to_string());
123 if !auth_types.contains(&key) {
124 auth_types.push(key);
125 }
126 }
127 }
128 }
129
130 Ok(auth_types)
131}
132
133#[instrument(skip_all, fields(event_id = incoming_event.event_id().borrow().as_str()))]
147pub fn auth_check<E: Event>(
148 room_version: &RoomVersion,
149 incoming_event: impl Event,
150 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
151) -> Result<(), String> {
152 debug!("starting auth check");
153
154 if *incoming_event.event_type() == TimelineEventType::RoomCreate {
156 let room_create_event = RoomCreateEvent::new(incoming_event);
157
158 return check_room_create(room_create_event, room_version);
159 }
160
161 let room_create_event = fetch_state.room_create_event()?;
195
196 if !incoming_event.auth_events().any(|id| id.borrow() == room_create_event.event_id().borrow())
198 {
199 return Err("no `m.room.create` event in auth events".to_owned());
200 }
201
202 let federate = room_create_event.federate()?;
205 if !federate
206 && room_create_event.sender().server_name() != incoming_event.sender().server_name()
207 {
208 return Err("\
209 room is not federated and event's sender domain \
210 does not match `m.room.create` event's sender domain"
211 .to_owned());
212 }
213
214 let sender = incoming_event.sender();
215
216 if room_version.special_case_aliases_auth
218 && *incoming_event.event_type() == TimelineEventType::RoomAliases
219 {
220 debug!("starting m.room.aliases check");
221 if incoming_event.state_key() != Some(sender.server_name().as_str()) {
225 return Err("\
226 server name of the `state_key` of `m.room.aliases` event \
227 does not match the server name of the sender"
228 .to_owned());
229 }
230
231 info!("`m.room.aliases` event was allowed");
233 return Ok(());
234 }
235
236 if *incoming_event.event_type() == TimelineEventType::RoomMember {
238 let room_member_event = RoomMemberEvent::new(incoming_event);
239 return check_room_member(room_member_event, room_version, room_create_event, fetch_state);
240 }
241
242 let sender_membership = fetch_state.user_membership(sender)?;
244
245 if sender_membership != MembershipState::Join {
246 return Err("sender's membership is not `join`".to_owned());
247 }
248
249 let creator = room_create_event.creator(room_version)?;
250 let current_room_power_levels_event = fetch_state.room_power_levels_event();
251
252 let sender_power_level =
253 current_room_power_levels_event.user_power_level(sender, &creator, room_version)?;
254
255 if *incoming_event.event_type() == TimelineEventType::RoomThirdPartyInvite {
257 let invite_power_level = current_room_power_levels_event
260 .get_as_int_or_default(RoomPowerLevelsIntField::Invite, room_version)?;
261
262 if sender_power_level < invite_power_level {
263 return Err("sender does not have enough power to send invites in this room".to_owned());
264 }
265
266 info!("`m.room.third_party_invite` event was allowed");
267 return Ok(());
268 }
269
270 let event_type_power_level = current_room_power_levels_event.event_power_level(
273 incoming_event.event_type(),
274 incoming_event.state_key(),
275 room_version,
276 )?;
277 if sender_power_level < event_type_power_level {
278 return Err(format!(
279 "sender does not have enough power to send event of type `{}`",
280 incoming_event.event_type()
281 ));
282 }
283
284 if incoming_event.state_key().is_some_and(|k| k.starts_with('@'))
287 && incoming_event.state_key() != Some(incoming_event.sender().as_str())
288 {
289 return Err(
290 "sender cannot send event with `state_key` matching another user's ID".to_owned()
291 );
292 }
293
294 if *incoming_event.event_type() == TimelineEventType::RoomPowerLevels {
296 let room_power_levels_event = RoomPowerLevelsEvent::new(incoming_event);
297 return check_room_power_levels(
298 room_power_levels_event,
299 current_room_power_levels_event,
300 room_version,
301 sender_power_level,
302 );
303 }
304
305 if room_version.extra_redaction_checks
307 && *incoming_event.event_type() == TimelineEventType::RoomRedaction
308 {
309 return check_room_redaction(
310 incoming_event,
311 current_room_power_levels_event,
312 room_version,
313 sender_power_level,
314 );
315 }
316
317 info!("allowing event passed all checks");
319 Ok(())
320}
321
322fn check_room_create(
324 room_create_event: RoomCreateEvent<impl Event>,
325 room_version: &RoomVersion,
326) -> Result<(), String> {
327 debug!("start `m.room.create` check");
328
329 if room_create_event.prev_events().next().is_some() {
331 return Err("`m.room.create` event cannot have previous events".into());
332 }
333
334 let Some(room_id_server_name) = room_create_event.room_id().server_name() else {
336 return Err(
337 "invalid `room_id` field in `m.room.create` event: could not parse server name".into(),
338 );
339 };
340
341 if room_id_server_name != room_create_event.sender().server_name() {
342 return Err("invalid `room_id` field in `m.room.create` event: server name does not match sender's server name".into());
343 }
344
345 if !room_version.use_room_create_sender && !room_create_event.has_creator()? {
352 return Err("missing `creator` field in `m.room.create` event".into());
353 }
354
355 info!("`m.room.create` event was allowed");
357 Ok(())
358}
359
360fn check_room_power_levels(
362 room_power_levels_event: RoomPowerLevelsEvent<impl Event>,
363 current_room_power_levels_event: Option<RoomPowerLevelsEvent<impl Event>>,
364 room_version: &RoomVersion,
365 sender_power_level: Int,
366) -> Result<(), String> {
367 debug!("starting m.room.power_levels check");
368
369 let new_int_fields = room_power_levels_event.int_fields_map(room_version)?;
372
373 let new_events = room_power_levels_event.events(room_version)?;
376 let new_notifications = room_power_levels_event.notifications(room_version)?;
377
378 let new_users = room_power_levels_event.users(room_version)?;
383
384 debug!("validation of power event finished");
385
386 let Some(current_room_power_levels_event) = current_room_power_levels_event else {
388 info!("initial m.room.power_levels event allowed");
389 return Ok(());
390 };
391
392 for field in RoomPowerLevelsIntField::ALL {
395 let current_power_level =
396 current_room_power_levels_event.get_as_int(*field, room_version)?;
397 let new_power_level = new_int_fields.get(field).copied();
398
399 if current_power_level == new_power_level {
400 continue;
401 }
402
403 let current_power_level_too_big =
406 current_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
407 let new_power_level_too_big =
409 new_power_level.unwrap_or_else(|| field.default_value()) > sender_power_level;
410
411 if current_power_level_too_big || new_power_level_too_big {
412 return Err(format!(
413 "sender does not have enough power to change the power level of `{field}`"
414 ));
415 }
416 }
417
418 let current_events = current_room_power_levels_event.events(room_version)?;
421 check_power_level_maps(
422 current_events.as_ref(),
423 new_events.as_ref(),
424 &sender_power_level,
425 |_, current_power_level| {
426 current_power_level > sender_power_level
430 },
431 |ev_type| {
432 format!(
433 "sender does not have enough power to change the `{ev_type}` event type power level"
434 )
435 },
436 )?;
437
438 if room_version.limit_notifications_power_levels {
441 let current_notifications = current_room_power_levels_event.notifications(room_version)?;
442 check_power_level_maps(
443 current_notifications.as_ref(),
444 new_notifications.as_ref(),
445 &sender_power_level,
446 |_, current_power_level| {
447 current_power_level > sender_power_level
452 },
453 |key| {
454 format!(
455 "sender does not have enough power to change the `{key}` notification power level"
456 )
457 },
458 )?;
459 }
460
461 let current_users = current_room_power_levels_event.users(room_version)?;
464 check_power_level_maps(
465 current_users,
466 new_users,
467 &sender_power_level,
468 |user_id, current_power_level| {
469 user_id != room_power_levels_event.sender() && current_power_level >= sender_power_level
474 },
475 |user_id| format!("sender does not have enough power to change `{user_id}`'s power level"),
476 )?;
477
478 info!("m.room.power_levels event allowed");
480 Ok(())
481}
482
483fn check_power_level_maps<K: Ord>(
501 current: Option<&BTreeMap<K, Int>>,
502 new: Option<&BTreeMap<K, Int>>,
503 sender_power_level: &Int,
504 reject_current_power_level_change_fn: impl FnOnce(&K, Int) -> bool + Copy,
505 error_fn: impl FnOnce(&K) -> String,
506) -> Result<(), String> {
507 let keys_to_check = current
508 .iter()
509 .flat_map(|m| m.keys())
510 .chain(new.iter().flat_map(|m| m.keys()))
511 .collect::<BTreeSet<_>>();
512
513 for key in keys_to_check {
514 let current_power_level = current.as_ref().and_then(|m| m.get(key));
515 let new_power_level = new.as_ref().and_then(|m| m.get(key));
516
517 if current_power_level == new_power_level {
518 continue;
519 }
520
521 let current_power_level_change_rejected = current_power_level
523 .is_some_and(|power_level| reject_current_power_level_change_fn(key, *power_level));
524
525 let new_power_level_too_big = new_power_level > Some(sender_power_level);
528
529 if current_power_level_change_rejected || new_power_level_too_big {
530 return Err(error_fn(key));
531 }
532 }
533
534 Ok(())
535}
536
537fn check_room_redaction(
539 room_redaction_event: impl Event,
540 current_room_power_levels_event: Option<RoomPowerLevelsEvent<impl Event>>,
541 room_version: &RoomVersion,
542 sender_level: Int,
543) -> Result<(), String> {
544 let redact_level = current_room_power_levels_event
545 .get_as_int_or_default(RoomPowerLevelsIntField::Redact, room_version)?;
546
547 if sender_level >= redact_level {
549 info!("`m.room.redaction` event allowed via power levels");
550 return Ok(());
551 }
552
553 if room_redaction_event.event_id().borrow().server_name()
556 == room_redaction_event.redacts().as_ref().and_then(|&id| id.borrow().server_name())
557 {
558 info!("`m.room.redaction` event allowed via room version 1 rules");
559 return Ok(());
560 }
561
562 Err("`m.room.redaction` event did not pass any of the allow rules".to_owned())
564}
565
566trait FetchStateExt<E: Event> {
567 fn room_create_event(&self) -> Result<RoomCreateEvent<E>, String>;
568
569 fn user_membership(&self, user_id: &UserId) -> Result<MembershipState, String>;
570
571 fn room_power_levels_event(&self) -> Option<RoomPowerLevelsEvent<E>>;
572
573 fn join_rule(&self) -> Result<JoinRule, String>;
574
575 fn room_third_party_invite_event(&self, token: &str) -> Option<RoomThirdPartyInviteEvent<E>>;
576}
577
578impl<E, F> FetchStateExt<E> for F
579where
580 F: Fn(&StateEventType, &str) -> Option<E>,
581 E: Event,
582{
583 fn room_create_event(&self) -> Result<RoomCreateEvent<E>, String> {
584 self(&StateEventType::RoomCreate, "")
585 .map(RoomCreateEvent::new)
586 .ok_or_else(|| "no `m.room.create` event in current state".to_owned())
587 }
588
589 fn user_membership(&self, user_id: &UserId) -> Result<MembershipState, String> {
590 self(&StateEventType::RoomMember, user_id.as_str()).map(RoomMemberEvent::new).membership()
591 }
592
593 fn room_power_levels_event(&self) -> Option<RoomPowerLevelsEvent<E>> {
594 self(&StateEventType::RoomPowerLevels, "").map(RoomPowerLevelsEvent::new)
595 }
596
597 fn join_rule(&self) -> Result<JoinRule, String> {
598 self(&StateEventType::RoomJoinRules, "")
599 .map(RoomJoinRulesEvent::new)
600 .ok_or_else(|| "no `m.room.join_rules` event in current state".to_owned())?
601 .join_rule()
602 }
603
604 fn room_third_party_invite_event(&self, token: &str) -> Option<RoomThirdPartyInviteEvent<E>> {
605 self(&StateEventType::RoomThirdPartyInvite, token).map(RoomThirdPartyInviteEvent::new)
606 }
607}