From 3a8ed99246ccc83feed3150bfdf27de5446b2623 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Mon, 1 Jul 2024 17:16:39 +0100 Subject: [PATCH] fixup! feat(federation): support /make_join and /send_join for restricted rooms move out checking restricted room rules into separate function --- src/api/server_server.rs | 198 ++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 94 deletions(-) diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 2fe90e0e..e4f5adae 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -49,7 +49,7 @@ use ruma::{ to_device::DeviceIdOrAllDevices, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, RoomId, - RoomVersionId, ServerName, + RoomVersionId, ServerName, UserId, }; use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; use std::{ @@ -1517,100 +1517,46 @@ pub async fn create_join_event_template_route( ); let state_lock = mutex_state.lock().await; - let join_rules_event = services().rooms.state_accessor.room_state_get( - &body.room_id, - &StateEventType::RoomJoinRules, - "", - )?; + let room_version_id = services().rooms.state.get_room_version(&body.room_id)?; - let join_rules_event_content: Option = join_rules_event - .as_ref() - .map(|join_rules_event| { - serde_json::from_str(join_rules_event.content.get()).map_err(|e| { - warn!("Invalid join rules event: {}", e); - Error::bad_database("Invalid join rules event in db.") - }) - }) - .transpose()?; - - let join_authorized_via_users_server = || { - let join_rules_event_content = join_rules_event_content?; - - if !services() + let join_authorized_via_users_server = if (services() + .rooms + .state_cache + .is_left(&body.user_id, &body.room_id) + .unwrap_or(true) + || services() .rooms .state_cache - .is_left(&body.user_id, &body.room_id) - .unwrap_or(true) - && !services() - .rooms - .state_cache - .is_knocked(&body.user_id, &body.room_id) - .unwrap_or(false) - { - // If the user has any state other than leave or knock, either: - // - the auth_check will deny them (ban) - // - they are able to join via other methods (invite) - // - they are already in the room (join) - return None; - }; - - let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) = - join_rules_event_content.join_rule - else { - // Room is not restricted - return None; - }; - - if r.allow - .iter() - .filter_map(|rule| { - if let AllowRule::RoomMembership(membership) = rule { - Some(membership) - } else { - None - } - }) - .any(|m| { + .is_knocked(&body.user_id, &body.room_id) + .unwrap_or(false)) + && user_can_perform_restricted_join(&body.user_id, &body.room_id, &room_version_id)? + { + let auth_user = services() + .rooms + .state_cache + .room_members(&body.room_id) + .filter_map(Result::ok) + .filter(|user| user.server_name() == services().globals.server_name()) + .find(|user| { services() .rooms - .state_cache - .is_joined(&body.user_id, &m.room_id) + .state_accessor + .user_can_invite(&body.room_id, user, &body.user_id, &state_lock) .unwrap_or(false) - }) - { - let auth_user = services() - .rooms - .state_cache - .room_members(&body.room_id) - .filter_map(Result::ok) - .filter(|user| user.server_name() == services().globals.server_name()) - .find(|user| { - services() - .rooms - .state_accessor - .user_can_invite(&body.room_id, user, &body.user_id, &state_lock) - .unwrap_or(false) - }); + }); - if auth_user.is_some() { - Ok(auth_user).transpose() - } else { - Some(Err(Error::BadRequest( - ErrorKind::UnableToGrantJoin, - "No user on this server is able to assist in joining.", - ))) - } + if auth_user.is_some() { + auth_user } else { - Some(Err(Error::BadRequest( - ErrorKind::UnableToAuthorizeJoin, - "User is not known to be in any required room.", - ))) + return Err(Error::BadRequest( + ErrorKind::UnableToGrantJoin, + "No user on this server is able to assist in joining.", + )); } + } else { + None }; - let join_authorized_via_users_server = join_authorized_via_users_server().transpose()?; - - let room_version_id = services().rooms.state.get_room_version(&body.room_id)?; if !body.ver.contains(&room_version_id) { return Err(Error::BadRequest( ErrorKind::IncompatibleRoomVersion { @@ -1763,16 +1709,7 @@ async fn create_join_event( .join_authorized_via_users_server .map(|user| user.server_name() == services().globals.server_name()) .unwrap_or_default() - && !matches!( - room_version_id, - RoomVersionId::V1 - | RoomVersionId::V2 - | RoomVersionId::V3 - | RoomVersionId::V4 - | RoomVersionId::V5 - | RoomVersionId::V6 - | RoomVersionId::V7 - ) + && user_can_perform_restricted_join(&sender, room_id, &room_version_id).unwrap_or_default() { ruma::signatures::hash_and_sign_event( services().globals.server_name().as_str(), @@ -1901,6 +1838,79 @@ pub async fn create_join_event_v2_route( Ok(create_join_event::v2::Response { room_state }) } +/// Checks whether the given user can join the given room via a restricted join. +/// This doesn't check the current user's membership. This should be done externally, +/// either by using the state cache or attempting to authorize the event. +fn user_can_perform_restricted_join( + user_id: &UserId, + room_id: &RoomId, + room_version_id: &RoomVersionId, +) -> Result { + let join_rules_event = services().rooms.state_accessor.room_state_get( + room_id, + &StateEventType::RoomJoinRules, + "", + )?; + + let Some(join_rules_event_content) = join_rules_event + .as_ref() + .map(|join_rules_event| { + serde_json::from_str::(join_rules_event.content.get()) + .map_err(|e| { + warn!("Invalid join rules event: {}", e); + Error::bad_database("Invalid join rules event in db.") + }) + }) + .transpose()? + else { + return Ok(false); + }; + + if matches!( + room_version_id, + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + ) { + return Ok(false); + } + + let (JoinRule::Restricted(r) | JoinRule::KnockRestricted(r)) = + join_rules_event_content.join_rule + else { + return Ok(false); + }; + + if r.allow + .iter() + .filter_map(|rule| { + if let AllowRule::RoomMembership(membership) = rule { + Some(membership) + } else { + None + } + }) + .any(|m| { + services() + .rooms + .state_cache + .is_joined(user_id, &m.room_id) + .unwrap_or(false) + }) + { + Ok(true) + } else { + Err(Error::BadRequest( + ErrorKind::UnableToAuthorizeJoin, + "User is not known to be in any required room.", + )) + } +} + /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}` /// /// Invites a remote user to a room.