From ba4fa816201b9564f56eae3575f696c275600bf4 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Tue, 9 Apr 2024 14:36:50 +0100 Subject: [PATCH] feat(federation): support /make_join and /send_join for restricted rooms --- src/api/server_server.rs | 154 +++++++++++++++++++++++++++------------ src/database/mod.rs | 2 +- 2 files changed, 109 insertions(+), 47 deletions(-) diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 6fd131c5..4582188b 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -39,7 +39,7 @@ use ruma::{ events::{ receipt::{ReceiptEvent, ReceiptEventContent, ReceiptType}, room::{ - join_rules::{JoinRule, RoomJoinRulesEventContent}, + join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent}, member::{MembershipState, RoomMemberEventContent}, }, StateEventType, TimelineEventType, @@ -1517,7 +1517,6 @@ pub async fn create_join_event_template_route( ); let state_lock = mutex_state.lock().await; - // TODO: Conduit does not implement restricted join rules yet, we always reject let join_rules_event = services().rooms.state_accessor.room_state_get( &body.room_id, &StateEventType::RoomJoinRules, @@ -1534,17 +1533,89 @@ pub async fn create_join_event_template_route( }) .transpose()?; - if let Some(join_rules_event_content) = join_rules_event_content { - if matches!( - join_rules_event_content.join_rule, - JoinRule::Restricted { .. } | JoinRule::KnockRestricted { .. } - ) { - return Err(Error::BadRequest( - ErrorKind::UnableToAuthorizeJoin, - "Conduit does not support restricted rooms yet.", - )); - } - } + let join_authorized_via_users_server = + if let Some(join_rules_event_content) = join_rules_event_content { + if 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 let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = + join_rules_event_content.join_rule + { + 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(&body.user_id, &m.room_id) + .unwrap_or(false) + }) + { + let members: Vec<_> = services() + .rooms + .state_cache + .room_members(&body.room_id) + .filter_map(Result::ok) + .filter(|user| user.server_name() == services().globals.server_name()) + .collect(); + + let mut auth_user = None; + + for user in members { + if services() + .rooms + .state_accessor + .user_can_invite(&body.room_id, &user, &body.user_id, &state_lock) + .await + .unwrap_or(false) + { + auth_user = Some(user); + break; + } + } + if auth_user.is_some() { + auth_user + } else { + return Err(Error::BadRequest( + ErrorKind::UnableToGrantJoin, + "No user on this server is able to assist in joining.", + )); + } + } else { + return Err(Error::BadRequest( + ErrorKind::UnableToAuthorizeJoin, + "User is not known to be in any required room.", + )); + } + } else { + // Room is not restricted + None + } + } else { + // 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) + None + } + } else { + None + }; let room_version_id = services().rooms.state.get_room_version(&body.room_id)?; if !body.ver.contains(&room_version_id) { @@ -1564,7 +1635,7 @@ pub async fn create_join_event_template_route( membership: MembershipState::Join, third_party_invite: None, reason: None, - join_authorized_via_users_server: None, + join_authorized_via_users_server, }) .expect("member event is valid value"); @@ -1609,35 +1680,6 @@ async fn create_join_event( .event_handler .acl_check(sender_servername, room_id)?; - // TODO: Conduit does not implement restricted join rules yet, we always reject - let join_rules_event = services().rooms.state_accessor.room_state_get( - room_id, - &StateEventType::RoomJoinRules, - "", - )?; - - 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()?; - - if let Some(join_rules_event_content) = join_rules_event_content { - if matches!( - join_rules_event_content.join_rule, - JoinRule::Restricted { .. } | JoinRule::KnockRestricted { .. } - ) { - return Err(Error::BadRequest( - ErrorKind::UnableToAuthorizeJoin, - "Conduit does not support restricted rooms yet.", - )); - } - } - // We need to return the state prior to joining, let's keep a reference to that here let shortstatehash = services() .rooms @@ -1653,7 +1695,8 @@ async fn create_join_event( // We do not add the event_id field to the pdu here because of signature and hashes checks let room_version_id = services().rooms.state.get_room_version(room_id)?; - let (event_id, value) = match gen_event_id_canonical_json(pdu, &room_version_id) { + + let (event_id, mut value) = match gen_event_id_canonical_json(pdu, &room_version_id) { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json @@ -1664,6 +1707,14 @@ async fn create_join_event( } }; + ruma::signatures::hash_and_sign_event( + services().globals.server_name().as_str(), + services().globals.keypair(), + &mut value, + &room_version_id, + ) + .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Failed to sign event."))?; + let origin: OwnedServerName = serde_json::from_value( serde_json::to_value(value.get("origin").ok_or(Error::BadRequest( ErrorKind::InvalidParam, @@ -1686,7 +1737,14 @@ async fn create_join_event( let pdu_id: Vec = services() .rooms .event_handler - .handle_incoming_pdu(&origin, &event_id, room_id, value, true, &pub_key_map) + .handle_incoming_pdu( + &origin, + &event_id, + room_id, + value.clone(), + true, + &pub_key_map, + ) .await? .ok_or(Error::BadRequest( ErrorKind::InvalidParam, @@ -1724,7 +1782,11 @@ async fn create_join_event( .filter_map(|(_, id)| services().rooms.timeline.get_pdu_json(id).ok().flatten()) .map(PduEvent::convert_to_outgoing_federation_event) .collect(), - event: None, // TODO: handle restricted joins + // Event field is required if the room version supports restricted join rules. + event: Some( + to_raw_value(&CanonicalJsonValue::Object(value.clone())) + .expect("To raw json should not fail since only change was adding signature"), + ), }) } diff --git a/src/database/mod.rs b/src/database/mod.rs index 16b2f556..e6b57c0b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -431,7 +431,7 @@ impl KeyValueDatabase { } // If the database has any data, perform data migrations before starting - let latest_database_version = 13; + let latest_database_version = 14; if services().users.count()? > 0 { // MIGRATIONS