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