1use std::borrow::Borrow;
23use ruma_common::{
4 room_version_rules::AuthorizationRules,
5 serde::{base64::Standard, Base64},
6 AnyKeyName, SigningKeyId, UserId,
7};
8use ruma_events::{room::member::MembershipState, StateEventType};
9use ruma_signatures::verify_canonical_json_bytes;
10use tracing::debug;
1112#[cfg(test)]
13mod tests;
1415use super::FetchStateExt;
16use crate::{
17 events::{
18 member::ThirdPartyInvite, power_levels::RoomPowerLevelsEventOptionExt, JoinRule,
19 RoomCreateEvent, RoomMemberEvent, RoomPowerLevelsIntField,
20 },
21 Event,
22};
2324/// Check whether the given event passes the `m.room.roomber` authorization rules.
25///
26/// This assumes that `ruma_signatures::verify_event()` was called previously, as some authorization
27/// rules depend on the signatures being valid on the event.
28pub(super) fn check_room_member<E: Event>(
29 room_member_event: RoomMemberEvent<impl Event>,
30 rules: &AuthorizationRules,
31 room_create_event: RoomCreateEvent<E>,
32 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
33) -> Result<(), String> {
34debug!("starting m.room.member check");
3536// Since v1, if there is no state_key property, or no membership property in content,
37 // reject.
38let Some(state_key) = room_member_event.state_key() else {
39return Err("missing `state_key` field in `m.room.member` event".to_owned());
40 };
41let target_user = <&UserId>::try_from(state_key)
42 .map_err(|e| format!("invalid `state_key` field in `m.room.member` event: {e}"))?;
4344let target_membership = room_member_event.membership()?;
4546// These checks are done `in ruma_signatures::verify_event()`:
47 //
48 // Since v8, if content has a join_authorised_via_users_server property:
49 //
50 // - Since v8, if the event is not validly signed by the homeserver of the user ID denoted by
51 // the key, reject.
5253match target_membership {
54// Since v1, if membership is join:
55MembershipState::Join => check_room_member_join(
56&room_member_event,
57 target_user,
58 rules,
59 room_create_event,
60 fetch_state,
61 ),
62// Since v1, if membership is invite:
63MembershipState::Invite => check_room_member_invite(
64&room_member_event,
65 target_user,
66 rules,
67 room_create_event,
68 fetch_state,
69 ),
70// Since v1, if membership is leave:
71MembershipState::Leave => check_room_member_leave(
72&room_member_event,
73 target_user,
74 rules,
75 room_create_event,
76 fetch_state,
77 ),
78// Since v1, if membership is ban:
79MembershipState::Ban => check_room_member_ban(
80&room_member_event,
81 target_user,
82 rules,
83 room_create_event,
84 fetch_state,
85 ),
86// Since v7, if membership is knock:
87MembershipState::Knock if rules.knocking => {
88 check_room_member_knock(&room_member_event, target_user, rules, fetch_state)
89 }
90// Since v1, otherwise, the membership is unknown. Reject.
91_ => Err("unknown membership".to_owned()),
92 }
93}
9495/// Check whether the given event passes the `m.room.member` authorization rules with a membership
96/// of `join`.
97fn check_room_member_join<E: Event>(
98 room_member_event: &RoomMemberEvent<impl Event>,
99 target_user: &UserId,
100 rules: &AuthorizationRules,
101 room_create_event: RoomCreateEvent<E>,
102 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
103) -> Result<(), String> {
104let creator = room_create_event.creator(rules)?;
105106let mut prev_events = room_member_event.prev_events();
107let prev_event_is_room_create_event = prev_events
108 .next()
109 .is_some_and(|event_id| event_id.borrow() == room_create_event.event_id().borrow());
110let prev_event_is_only_room_create_event =
111 prev_event_is_room_create_event && prev_events.next().is_none();
112113// v1-v10, if the only previous event is an m.room.create and the state_key is the
114 // creator, allow.
115 // Since v11, if the only previous event is an m.room.create and the state_key is the
116 // sender of the m.room.create, allow.
117if prev_event_is_only_room_create_event && *target_user == *creator {
118return Ok(());
119 }
120121// Since v1, if the sender does not match state_key, reject.
122if room_member_event.sender() != target_user {
123return Err("sender of join event must match target user".to_owned());
124 }
125126let current_membership = fetch_state.user_membership(target_user)?;
127128// Since v1, if the sender is banned, reject.
129if current_membership == MembershipState::Ban {
130return Err("banned user cannot join room".to_owned());
131 }
132133let join_rule = fetch_state.join_rule()?;
134135// v1-v6, if the join_rule is invite then allow if membership state is invite or
136 // join.
137 // Since v7, if the join_rule is invite or knock then allow if membership state is
138 // invite or join.
139if (join_rule == JoinRule::Invite || rules.knocking && join_rule == JoinRule::Knock)
140 && matches!(current_membership, MembershipState::Invite | MembershipState::Join)
141 {
142return Ok(());
143 }
144145// v8-v9, if the join_rule is restricted:
146 // Since v10, if the join_rule is restricted or knock_restricted:
147if rules.restricted_join_rule && matches!(join_rule, JoinRule::Restricted)
148 || rules.knock_restricted_join_rule && matches!(join_rule, JoinRule::KnockRestricted)
149 {
150// Since v8, if membership state is join or invite, allow.
151if matches!(current_membership, MembershipState::Join | MembershipState::Invite) {
152return Ok(());
153 }
154155// Since v8, if the join_authorised_via_users_server key in content is not a
156 // user with sufficient permission to invite other users, reject.
157 //
158 // Otherwise, allow.
159let Some(authorized_via_user) = room_member_event.join_authorised_via_users_server()?
160else {
161// The field is absent, we cannot authorize.
162return Err(
163"cannot join restricted room without `join_authorised_via_users_server` field \
164 if not invited"
165.to_owned(),
166 );
167 };
168169// The member needs to be in the room to have any kind of permission.
170let authorized_via_user_membership = fetch_state.user_membership(&authorized_via_user)?;
171if authorized_via_user_membership != MembershipState::Join {
172return Err("`join_authorised_via_users_server` is not joined".to_owned());
173 }
174175let room_power_levels_event = fetch_state.room_power_levels_event();
176177let authorized_via_user_power_level =
178 room_power_levels_event.user_power_level(&authorized_via_user, &creator, rules)?;
179let invite_power_level = room_power_levels_event
180 .get_as_int_or_default(RoomPowerLevelsIntField::Invite, rules)?;
181182return if authorized_via_user_power_level >= invite_power_level {
183Ok(())
184 } else {
185Err("`join_authorised_via_users_server` does not have enough power".to_owned())
186 };
187 }
188189// Since v1, if the join_rule is public, allow.
190 // Otherwise, reject.
191if join_rule == JoinRule::Public {
192Ok(())
193 } else {
194Err("cannot join a room that is not `public`".to_owned())
195 }
196}
197198/// Check whether the given event passes the `m.room.member` authorization rules with a membership
199/// of `invite`.
200fn check_room_member_invite<E: Event>(
201 room_member_event: &RoomMemberEvent<impl Event>,
202 target_user: &UserId,
203 rules: &AuthorizationRules,
204 room_create_event: RoomCreateEvent<E>,
205 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
206) -> Result<(), String> {
207let third_party_invite = room_member_event.third_party_invite()?;
208209// Since v1, if content has a third_party_invite property:
210if let Some(third_party_invite) = third_party_invite {
211return check_third_party_invite(
212 room_member_event,
213 third_party_invite,
214 target_user,
215 fetch_state,
216 );
217 }
218219let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
220221// Since v1, if the sender’s current membership state is not join, reject.
222if sender_membership != MembershipState::Join {
223return Err("cannot invite user if sender is not joined".to_owned());
224 }
225226let current_target_user_membership = fetch_state.user_membership(target_user)?;
227228// Since v1, if target user’s current membership state is join or ban, reject.
229if matches!(current_target_user_membership, MembershipState::Join | MembershipState::Ban) {
230return Err("cannot invite user that is joined or banned".to_owned());
231 }
232233let creator = room_create_event.creator(rules)?;
234let room_power_levels_event = fetch_state.room_power_levels_event();
235236let sender_power_level =
237 room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
238let invite_power_level =
239 room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Invite, rules)?;
240241// Since v1, if the sender’s power level is greater than or equal to the invite
242 // level, allow.
243 //
244 // Otherwise, reject.
245if sender_power_level >= invite_power_level {
246Ok(())
247 } else {
248Err("sender does not have enough power to invite".to_owned())
249 }
250}
251252/// Check whether the `third_party_invite` from the `m.room.member` event passes the authorization
253/// rules.
254fn check_third_party_invite<E: Event>(
255 room_member_event: &RoomMemberEvent<impl Event>,
256 third_party_invite: ThirdPartyInvite,
257 target_user: &UserId,
258 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
259) -> Result<(), String> {
260let current_target_user_membership = fetch_state.user_membership(target_user)?;
261262// Since v1, if target user is banned, reject.
263if current_target_user_membership == MembershipState::Ban {
264return Err("cannot invite user that is banned".to_owned());
265 }
266267// Since v1, if content.third_party_invite does not have a signed property, reject.
268 // Since v1, if signed does not have mxid and token properties, reject.
269let third_party_invite_token = third_party_invite.token()?;
270let third_party_invite_mxid = third_party_invite.mxid()?;
271272// Since v1, if mxid does not match state_key, reject.
273if target_user != third_party_invite_mxid {
274return Err("third-party invite mxid does not match target user".to_owned());
275 }
276277// Since v1, if there is no m.room.third_party_invite event in the current room state with
278 // state_key matching token, reject.
279let Some(room_third_party_invite_event) =
280 fetch_state.room_third_party_invite_event(third_party_invite_token)
281else {
282return Err("no `m.room.third_party_invite` in room state matches the token".to_owned());
283 };
284285// Since v1, if sender does not match sender of the m.room.third_party_invite, reject.
286if room_member_event.sender() != room_third_party_invite_event.sender() {
287return Err(
288"sender of `m.room.third_party_invite` does not match sender of `m.room.member`"
289.to_owned(),
290 );
291 }
292293let public_keys = room_third_party_invite_event.public_keys()?;
294let signatures = third_party_invite.signatures()?;
295let signed_canonical_json = third_party_invite.signed_canonical_json()?;
296297// Since v1, if any signature in signed matches any public key in the m.room.third_party_invite
298 // event, allow.
299for entity_signatures_value in signatures.values() {
300let Some(entity_signatures) = entity_signatures_value.as_object() else {
301return Err(format!(
302"unexpected format of `signatures` field in `third_party_invite.signed` \
303 of `m.room.member` event: expected a map of string to object, got {entity_signatures_value:?}"
304));
305 };
306307// We will ignore any error from now on, we just want to find a signature that can be
308 // verified from a public key.
309310for (key_id, signature_value) in entity_signatures {
311let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
312continue;
313 };
314let algorithm = parsed_key_id.algorithm();
315316let Some(signature_str) = signature_value.as_str() else {
317continue;
318 };
319320let Ok(signature) = Base64::<Standard>::parse(signature_str) else {
321continue;
322 };
323324for encoded_public_key in &public_keys {
325let Ok(public_key) = encoded_public_key.decode() else {
326continue;
327 };
328329if verify_canonical_json_bytes(
330&algorithm,
331&public_key,
332 signature.as_bytes(),
333 signed_canonical_json.as_bytes(),
334 )
335 .is_ok()
336 {
337return Ok(());
338 }
339 }
340 }
341 }
342343// Otherwise, reject.
344Err("\
345 no signature on third-party invite matches a public key \
346 in `m.room.third_party_invite` event"
347.to_owned())
348}
349350/// Check whether the given event passes the `m.room.member` authorization rules with a membership
351/// of `leave`.
352fn check_room_member_leave<E: Event>(
353 room_member_event: &RoomMemberEvent<impl Event>,
354 target_user: &UserId,
355 rules: &AuthorizationRules,
356 room_create_event: RoomCreateEvent<E>,
357 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
358) -> Result<(), String> {
359let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
360361// v1-v6, if the sender matches state_key, allow if and only if that user’s current
362 // membership state is invite or join.
363 // Since v7, if the sender matches state_key, allow if and only if that user’s current
364 // membership state is invite, join, or knock.
365if room_member_event.sender() == target_user {
366let membership_is_invite_or_join =
367matches!(sender_membership, MembershipState::Join | MembershipState::Invite);
368let membership_is_knock = rules.knocking && sender_membership == MembershipState::Knock;
369370return if membership_is_invite_or_join || membership_is_knock {
371Ok(())
372 } else {
373Err("cannot leave if not joined, invited or knocked".to_owned())
374 };
375 }
376377// Since v1, if the sender’s current membership state is not join, reject.
378if sender_membership != MembershipState::Join {
379return Err("cannot kick if sender is not joined".to_owned());
380 }
381382let creator = room_create_event.creator(rules)?;
383let room_power_levels_event = fetch_state.room_power_levels_event();
384385let current_target_user_membership = fetch_state.user_membership(target_user)?;
386let sender_power_level =
387 room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
388let ban_power_level =
389 room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Ban, rules)?;
390391// Since v1, if the target user’s current membership state is ban, and the sender’s
392 // power level is less than the ban level, reject.
393if current_target_user_membership == MembershipState::Ban
394 && sender_power_level < ban_power_level
395 {
396return Err("sender does not have enough power to unban".to_owned());
397 }
398399let kick_power_level =
400 room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Kick, rules)?;
401let target_user_power_level =
402 room_power_levels_event.user_power_level(target_user, &creator, rules)?;
403404// Since v1, if the sender’s power level is greater than or equal to the kick level,
405 // and the target user’s power level is less than the sender’s power level, allow.
406 //
407 // Otherwise, reject.
408if sender_power_level >= kick_power_level && target_user_power_level < sender_power_level {
409Ok(())
410 } else {
411Err("sender does not have enough power to kick target user".to_owned())
412 }
413}
414415/// Check whether the given event passes the `m.room.member` authorization rules with a membership
416/// of `ban`.
417fn check_room_member_ban<E: Event>(
418 room_member_event: &RoomMemberEvent<impl Event>,
419 target_user: &UserId,
420 rules: &AuthorizationRules,
421 room_create_event: RoomCreateEvent<E>,
422 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
423) -> Result<(), String> {
424let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
425426// Since v1, if the sender’s current membership state is not join, reject.
427if sender_membership != MembershipState::Join {
428return Err("cannot ban if sender is not joined".to_owned());
429 }
430431let creator = room_create_event.creator(rules)?;
432let room_power_levels_event = fetch_state.room_power_levels_event();
433434let sender_power_level =
435 room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
436let ban_power_level =
437 room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Ban, rules)?;
438let target_user_power_level =
439 room_power_levels_event.user_power_level(target_user, &creator, rules)?;
440441// If the sender’s power level is greater than or equal to the ban level, and the
442 // target user’s power level is less than the sender’s power level, allow.
443 //
444 // Otherwise, reject.
445if sender_power_level >= ban_power_level && target_user_power_level < sender_power_level {
446Ok(())
447 } else {
448Err("sender does not have enough power to ban target user".to_owned())
449 }
450}
451452/// Check whether the given event passes the `m.room.member` authorization rules with a membership
453/// of `knock`.
454fn check_room_member_knock<E: Event>(
455 room_member_event: &RoomMemberEvent<impl Event>,
456 target_user: &UserId,
457 rules: &AuthorizationRules,
458 fetch_state: impl Fn(&StateEventType, &str) -> Option<E>,
459) -> Result<(), String> {
460let join_rule = fetch_state.join_rule()?;
461462// v7-v9, if the join_rule is anything other than knock, reject.
463 // Since v10, if the join_rule is anything other than knock or knock_restricted,
464 // reject.
465if join_rule != JoinRule::Knock
466 && (rules.knock_restricted_join_rule && !matches!(join_rule, JoinRule::KnockRestricted))
467 {
468return Err(
469"join rule is not set to knock or knock_restricted, knocking is not allowed".to_owned()
470 );
471 }
472473// Since v7, if sender does not match state_key, reject.
474if room_member_event.sender() != target_user {
475return Err("cannot make another user knock, sender does not match target user".to_owned());
476 }
477478let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
479480// Since v7, if the sender’s current membership is not ban, invite, or join, allow.
481 // Otherwise, reject.
482if !matches!(
483 sender_membership,
484 MembershipState::Ban | MembershipState::Invite | MembershipState::Join
485 ) {
486Ok(())
487 } else {
488Err("cannot knock if user is banned, invited or joined".to_owned())
489 }
490}