ruma_state_res/event_auth/
room_member.rs

1use std::borrow::Borrow;
2
3use 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;
11
12#[cfg(test)]
13mod tests;
14
15use super::FetchStateExt;
16use crate::{
17    events::{
18        member::ThirdPartyInvite, power_levels::RoomPowerLevelsEventOptionExt, JoinRule,
19        RoomCreateEvent, RoomMemberEvent, RoomPowerLevelsIntField,
20    },
21    Event,
22};
23
24/// 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> {
34    debug!("starting m.room.member check");
35
36    // Since v1, if there is no state_key property, or no membership property in content,
37    // reject.
38    let Some(state_key) = room_member_event.state_key() else {
39        return Err("missing `state_key` field in `m.room.member` event".to_owned());
40    };
41    let target_user = <&UserId>::try_from(state_key)
42        .map_err(|e| format!("invalid `state_key` field in `m.room.member` event: {e}"))?;
43
44    let target_membership = room_member_event.membership()?;
45
46    // 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.
52
53    match target_membership {
54        // Since v1, if membership is join:
55        MembershipState::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:
63        MembershipState::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:
71        MembershipState::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:
79        MembershipState::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:
87        MembershipState::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}
94
95/// 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> {
104    let creator = room_create_event.creator(rules)?;
105
106    let mut prev_events = room_member_event.prev_events();
107    let 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());
110    let prev_event_is_only_room_create_event =
111        prev_event_is_room_create_event && prev_events.next().is_none();
112
113    // 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.
117    if prev_event_is_only_room_create_event && *target_user == *creator {
118        return Ok(());
119    }
120
121    // Since v1, if the sender does not match state_key, reject.
122    if room_member_event.sender() != target_user {
123        return Err("sender of join event must match target user".to_owned());
124    }
125
126    let current_membership = fetch_state.user_membership(target_user)?;
127
128    // Since v1, if the sender is banned, reject.
129    if current_membership == MembershipState::Ban {
130        return Err("banned user cannot join room".to_owned());
131    }
132
133    let join_rule = fetch_state.join_rule()?;
134
135    // 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.
139    if (join_rule == JoinRule::Invite || rules.knocking && join_rule == JoinRule::Knock)
140        && matches!(current_membership, MembershipState::Invite | MembershipState::Join)
141    {
142        return Ok(());
143    }
144
145    // v8-v9, if the join_rule is restricted:
146    // Since v10, if the join_rule is restricted or knock_restricted:
147    if 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.
151        if matches!(current_membership, MembershipState::Join | MembershipState::Invite) {
152            return Ok(());
153        }
154
155        // 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.
159        let Some(authorized_via_user) = room_member_event.join_authorised_via_users_server()?
160        else {
161            // The field is absent, we cannot authorize.
162            return Err(
163                "cannot join restricted room without `join_authorised_via_users_server` field \
164                 if not invited"
165                    .to_owned(),
166            );
167        };
168
169        // The member needs to be in the room to have any kind of permission.
170        let authorized_via_user_membership = fetch_state.user_membership(&authorized_via_user)?;
171        if authorized_via_user_membership != MembershipState::Join {
172            return Err("`join_authorised_via_users_server` is not joined".to_owned());
173        }
174
175        let room_power_levels_event = fetch_state.room_power_levels_event();
176
177        let authorized_via_user_power_level =
178            room_power_levels_event.user_power_level(&authorized_via_user, &creator, rules)?;
179        let invite_power_level = room_power_levels_event
180            .get_as_int_or_default(RoomPowerLevelsIntField::Invite, rules)?;
181
182        return if authorized_via_user_power_level >= invite_power_level {
183            Ok(())
184        } else {
185            Err("`join_authorised_via_users_server` does not have enough power".to_owned())
186        };
187    }
188
189    // Since v1, if the join_rule is public, allow.
190    // Otherwise, reject.
191    if join_rule == JoinRule::Public {
192        Ok(())
193    } else {
194        Err("cannot join a room that is not `public`".to_owned())
195    }
196}
197
198/// 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> {
207    let third_party_invite = room_member_event.third_party_invite()?;
208
209    // Since v1, if content has a third_party_invite property:
210    if let Some(third_party_invite) = third_party_invite {
211        return check_third_party_invite(
212            room_member_event,
213            third_party_invite,
214            target_user,
215            fetch_state,
216        );
217    }
218
219    let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
220
221    // Since v1, if the sender’s current membership state is not join, reject.
222    if sender_membership != MembershipState::Join {
223        return Err("cannot invite user if sender is not joined".to_owned());
224    }
225
226    let current_target_user_membership = fetch_state.user_membership(target_user)?;
227
228    // Since v1, if target user’s current membership state is join or ban, reject.
229    if matches!(current_target_user_membership, MembershipState::Join | MembershipState::Ban) {
230        return Err("cannot invite user that is joined or banned".to_owned());
231    }
232
233    let creator = room_create_event.creator(rules)?;
234    let room_power_levels_event = fetch_state.room_power_levels_event();
235
236    let sender_power_level =
237        room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
238    let invite_power_level =
239        room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Invite, rules)?;
240
241    // Since v1, if the sender’s power level is greater than or equal to the invite
242    // level, allow.
243    //
244    // Otherwise, reject.
245    if sender_power_level >= invite_power_level {
246        Ok(())
247    } else {
248        Err("sender does not have enough power to invite".to_owned())
249    }
250}
251
252/// 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> {
260    let current_target_user_membership = fetch_state.user_membership(target_user)?;
261
262    // Since v1, if target user is banned, reject.
263    if current_target_user_membership == MembershipState::Ban {
264        return Err("cannot invite user that is banned".to_owned());
265    }
266
267    // 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.
269    let third_party_invite_token = third_party_invite.token()?;
270    let third_party_invite_mxid = third_party_invite.mxid()?;
271
272    // Since v1, if mxid does not match state_key, reject.
273    if target_user != third_party_invite_mxid {
274        return Err("third-party invite mxid does not match target user".to_owned());
275    }
276
277    // Since v1, if there is no m.room.third_party_invite event in the current room state with
278    // state_key matching token, reject.
279    let Some(room_third_party_invite_event) =
280        fetch_state.room_third_party_invite_event(third_party_invite_token)
281    else {
282        return Err("no `m.room.third_party_invite` in room state matches the token".to_owned());
283    };
284
285    // Since v1, if sender does not match sender of the m.room.third_party_invite, reject.
286    if room_member_event.sender() != room_third_party_invite_event.sender() {
287        return Err(
288            "sender of `m.room.third_party_invite` does not match sender of `m.room.member`"
289                .to_owned(),
290        );
291    }
292
293    let public_keys = room_third_party_invite_event.public_keys()?;
294    let signatures = third_party_invite.signatures()?;
295    let signed_canonical_json = third_party_invite.signed_canonical_json()?;
296
297    // Since v1, if any signature in signed matches any public key in the m.room.third_party_invite
298    // event, allow.
299    for entity_signatures_value in signatures.values() {
300        let Some(entity_signatures) = entity_signatures_value.as_object() else {
301            return 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        };
306
307        // We will ignore any error from now on, we just want to find a signature that can be
308        // verified from a public key.
309
310        for (key_id, signature_value) in entity_signatures {
311            let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
312                continue;
313            };
314            let algorithm = parsed_key_id.algorithm();
315
316            let Some(signature_str) = signature_value.as_str() else {
317                continue;
318            };
319
320            let Ok(signature) = Base64::<Standard>::parse(signature_str) else {
321                continue;
322            };
323
324            for encoded_public_key in &public_keys {
325                let Ok(public_key) = encoded_public_key.decode() else {
326                    continue;
327                };
328
329                if verify_canonical_json_bytes(
330                    &algorithm,
331                    &public_key,
332                    signature.as_bytes(),
333                    signed_canonical_json.as_bytes(),
334                )
335                .is_ok()
336                {
337                    return Ok(());
338                }
339            }
340        }
341    }
342
343    // Otherwise, reject.
344    Err("\
345        no signature on third-party invite matches a public key \
346        in `m.room.third_party_invite` event"
347        .to_owned())
348}
349
350/// 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> {
359    let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
360
361    // 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.
365    if room_member_event.sender() == target_user {
366        let membership_is_invite_or_join =
367            matches!(sender_membership, MembershipState::Join | MembershipState::Invite);
368        let membership_is_knock = rules.knocking && sender_membership == MembershipState::Knock;
369
370        return if membership_is_invite_or_join || membership_is_knock {
371            Ok(())
372        } else {
373            Err("cannot leave if not joined, invited or knocked".to_owned())
374        };
375    }
376
377    // Since v1, if the sender’s current membership state is not join, reject.
378    if sender_membership != MembershipState::Join {
379        return Err("cannot kick if sender is not joined".to_owned());
380    }
381
382    let creator = room_create_event.creator(rules)?;
383    let room_power_levels_event = fetch_state.room_power_levels_event();
384
385    let current_target_user_membership = fetch_state.user_membership(target_user)?;
386    let sender_power_level =
387        room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
388    let ban_power_level =
389        room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Ban, rules)?;
390
391    // 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.
393    if current_target_user_membership == MembershipState::Ban
394        && sender_power_level < ban_power_level
395    {
396        return Err("sender does not have enough power to unban".to_owned());
397    }
398
399    let kick_power_level =
400        room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Kick, rules)?;
401    let target_user_power_level =
402        room_power_levels_event.user_power_level(target_user, &creator, rules)?;
403
404    // 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.
408    if sender_power_level >= kick_power_level && target_user_power_level < sender_power_level {
409        Ok(())
410    } else {
411        Err("sender does not have enough power to kick target user".to_owned())
412    }
413}
414
415/// 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> {
424    let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
425
426    // Since v1, if the sender’s current membership state is not join, reject.
427    if sender_membership != MembershipState::Join {
428        return Err("cannot ban if sender is not joined".to_owned());
429    }
430
431    let creator = room_create_event.creator(rules)?;
432    let room_power_levels_event = fetch_state.room_power_levels_event();
433
434    let sender_power_level =
435        room_power_levels_event.user_power_level(room_member_event.sender(), &creator, rules)?;
436    let ban_power_level =
437        room_power_levels_event.get_as_int_or_default(RoomPowerLevelsIntField::Ban, rules)?;
438    let target_user_power_level =
439        room_power_levels_event.user_power_level(target_user, &creator, rules)?;
440
441    // 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.
445    if sender_power_level >= ban_power_level && target_user_power_level < sender_power_level {
446        Ok(())
447    } else {
448        Err("sender does not have enough power to ban target user".to_owned())
449    }
450}
451
452/// 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> {
460    let join_rule = fetch_state.join_rule()?;
461
462    // 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.
465    if join_rule != JoinRule::Knock
466        && (rules.knock_restricted_join_rule && !matches!(join_rule, JoinRule::KnockRestricted))
467    {
468        return Err(
469            "join rule is not set to knock or knock_restricted, knocking is not allowed".to_owned()
470        );
471    }
472
473    // Since v7, if sender does not match state_key, reject.
474    if room_member_event.sender() != target_user {
475        return Err("cannot make another user knock, sender does not match target user".to_owned());
476    }
477
478    let sender_membership = fetch_state.user_membership(room_member_event.sender())?;
479
480    // Since v7, if the sender’s current membership is not ban, invite, or join, allow.
481    // Otherwise, reject.
482    if !matches!(
483        sender_membership,
484        MembershipState::Ban | MembershipState::Invite | MembershipState::Join
485    ) {
486        Ok(())
487    } else {
488        Err("cannot knock if user is banned, invited or joined".to_owned())
489    }
490}