diff --git a/src/api/client/membership.rs b/src/api/client/membership.rs index bde8dee8..10e69f58 100644 --- a/src/api/client/membership.rs +++ b/src/api/client/membership.rs @@ -1240,8 +1240,8 @@ async fn make_join_request( ) -> Result<(federation::membership::prepare_join_event::v1::Response, OwnedServerName)> { let mut make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining.")); - let mut make_join_counter: u16 = 0; - let mut incompatible_room_version_count: u8 = 0; + let mut make_join_counter: usize = 0; + let mut incompatible_room_version_count: usize = 0; for remote_server in servers { if services.globals.server_is_ours(remote_server) { @@ -1264,28 +1264,25 @@ async fn make_join_request( make_join_counter = make_join_counter.saturating_add(1); if let Err(ref e) = make_join_response { - trace!("make_join ErrorKind string: {:?}", e.kind().to_string()); - - // converting to a string is necessary (i think) because ruma is forcing us to - // fill in the struct for M_INCOMPATIBLE_ROOM_VERSION - if e.kind().to_string().contains("M_INCOMPATIBLE_ROOM_VERSION") - || e.kind().to_string().contains("M_UNSUPPORTED_ROOM_VERSION") - { + if matches!( + e.kind(), + ErrorKind::IncompatibleRoomVersion { .. } | ErrorKind::UnsupportedRoomVersion + ) { incompatible_room_version_count = incompatible_room_version_count.saturating_add(1); } if incompatible_room_version_count > 15 { info!( "15 servers have responded with M_INCOMPATIBLE_ROOM_VERSION or M_UNSUPPORTED_ROOM_VERSION, \ - assuming that Conduwuit does not support the room {room_id}: {e}" + assuming that conduwuit does not support the room {room_id}: {e}" ); make_join_response_and_server = Err!(BadServerResponse("Room version is not supported by Conduwuit")); return make_join_response_and_server; } - if make_join_counter > 50 { + if make_join_counter > 40 { warn!( - "50 servers failed to provide valid make_join response, assuming no server can assist in joining." + "40 servers failed to provide valid make_join response, assuming no server can assist in joining." ); make_join_response_and_server = Err!(BadServerResponse("No server available to assist in joining.")); return make_join_response_and_server; diff --git a/src/api/router/auth.rs b/src/api/router/auth.rs index 2552dded..68abf5e2 100644 --- a/src/api/router/auth.rs +++ b/src/api/router/auth.rs @@ -13,6 +13,7 @@ use ruma::{ profile::{get_avatar_url, get_display_name, get_profile, get_profile_key, get_timezone_key}, voip::get_turn_server_info, }, + federation::openid::get_openid_userinfo, AuthScheme, IncomingRequest, Metadata, }, server_util::authorization::XMatrix, @@ -102,26 +103,6 @@ pub(super) async fn auth( } match (metadata.authentication, token) { - (_, Token::Invalid) => { - // OpenID endpoint uses a query param with the same name, drop this once query - // params for user auth are removed from the spec. This is required to make - // integration manager work. - if request.query.access_token.is_some() && request.parts.uri.path().contains("/openid/") { - Ok(Auth { - origin: None, - sender_user: None, - sender_device: None, - appservice_info: None, - }) - } else { - Err(Error::BadRequest( - ErrorKind::UnknownToken { - soft_logout: false, - }, - "Unknown access token.", - )) - } - }, (AuthScheme::AccessToken, Token::Appservice(info)) => Ok(auth_appservice(services, request, info).await?), (AuthScheme::None | AuthScheme::AccessTokenOptional | AuthScheme::AppserviceToken, Token::Appservice(info)) => { Ok(Auth { @@ -132,7 +113,6 @@ pub(super) async fn auth( }) }, (AuthScheme::AccessToken, Token::None) => match metadata { - // TODO: can we check this better? &get_turn_server_info::v3::Request::METADATA => { if services.globals.config.turn_allow_guests { Ok(Auth { @@ -171,6 +151,32 @@ pub(super) async fn auth( ErrorKind::Unauthorized, "Only appservice access tokens should be used on this endpoint.", )), + (AuthScheme::None, Token::Invalid) => { + // OpenID federation endpoint uses a query param with the same name, drop this + // once query params for user auth are removed from the spec. This is + // required to make integration manager work. + if request.query.access_token.is_some() && metadata == &get_openid_userinfo::v1::Request::METADATA { + Ok(Auth { + origin: None, + sender_user: None, + sender_device: None, + appservice_info: None, + }) + } else { + Err(Error::BadRequest( + ErrorKind::UnknownToken { + soft_logout: false, + }, + "Unknown access token.", + )) + } + }, + (_, Token::Invalid) => Err(Error::BadRequest( + ErrorKind::UnknownToken { + soft_logout: false, + }, + "Unknown access token.", + )), } } diff --git a/src/api/server/invite.rs b/src/api/server/invite.rs index b30a1b58..edf80cd6 100644 --- a/src/api/server/invite.rs +++ b/src/api/server/invite.rs @@ -1,5 +1,6 @@ use axum::extract::State; use axum_client_ip::InsecureClientIp; +use base64::{engine::general_purpose, Engine as _}; use conduit::{err, utils, warn, Err, Error, PduEvent, Result}; use ruma::{ api::{client::error::ErrorKind, federation::membership::create_invite}, @@ -125,8 +126,10 @@ pub(crate) async fn create_invite_route( invite_state.push(pdu.to_stripped_state_event()); - // If we are active in the room, the remote server will notify us about the join - // via /send + // If we are active in the room, the remote server will notify us about the + // join/invite through /send. If we are not in the room, we need to manually + // record the invited state for client /sync through update_membership(), and + // send the invite PDU to the relevant appservices. if !services .rooms .state_cache @@ -148,6 +151,25 @@ pub(crate) async fn create_invite_route( .await?; } + for appservice in services.appservice.read().await.values() { + if appservice.is_user_match(&invited_user) { + services + .sending + .send_appservice_request( + appservice.registration.clone(), + ruma::api::appservice::event::push_events::v1::Request { + events: vec![pdu.to_room_event()], + txn_id: general_purpose::URL_SAFE_NO_PAD + .encode(utils::calculate_hash(&[pdu.event_id.as_bytes()])) + .into(), + ephemeral: Vec::new(), + to_device: Vec::new(), + }, + ) + .await?; + } + } + Ok(create_invite::v2::Response { event: services .sending diff --git a/src/api/server/make_join.rs b/src/api/server/make_join.rs index af570064..d5ea675e 100644 --- a/src/api/server/make_join.rs +++ b/src/api/server/make_join.rs @@ -80,6 +80,14 @@ pub(crate) async fn create_join_event_template_route( } let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; + if !body.ver.contains(&room_version_id) { + return Err(Error::BadRequest( + ErrorKind::IncompatibleRoomVersion { + room_version: room_version_id, + }, + "Room version not supported.", + )); + } let state_lock = services.rooms.state.mutex.lock(&body.room_id).await; @@ -118,16 +126,6 @@ pub(crate) async fn create_join_event_template_route( None }; - let room_version_id = services.rooms.state.get_room_version(&body.room_id).await?; - if !body.ver.contains(&room_version_id) { - return Err(Error::BadRequest( - ErrorKind::IncompatibleRoomVersion { - room_version: room_version_id, - }, - "Room version not supported.", - )); - } - let (_pdu, mut pdu_json) = services .rooms .timeline diff --git a/src/api/server/send_leave.rs b/src/api/server/send_leave.rs index 448e5de3..e4f41833 100644 --- a/src/api/server/send_leave.rs +++ b/src/api/server/send_leave.rs @@ -157,7 +157,5 @@ async fn create_leave_event( .room_servers(room_id) .ready_filter(|server| !services.globals.server_is_ours(server)); - services.sending.send_pdu_servers(servers, &pdu_id).await?; - - Ok(()) + services.sending.send_pdu_servers(servers, &pdu_id).await } diff --git a/src/service/rooms/spaces/mod.rs b/src/service/rooms/spaces/mod.rs index 37272dca..0ef7ddf5 100644 --- a/src/service/rooms/spaces/mod.rs +++ b/src/service/rooms/spaces/mod.rs @@ -8,7 +8,7 @@ use std::{ }; use conduit::{ - checked, debug, debug_info, err, + checked, debug_info, err, utils::{math::usize_from_f64, IterStream}, Error, Result, }; @@ -234,27 +234,25 @@ impl Service { }); } - Ok( - if let Some(children_pdus) = self.get_stripped_space_child_events(current_room).await? { - let summary = self - .get_room_summary(current_room, children_pdus, &identifier) - .await; - if let Ok(summary) = summary { - self.roomid_spacehierarchy_cache.lock().await.insert( - current_room.clone(), - Some(CachedSpaceHierarchySummary { - summary: summary.clone(), - }), - ); + if let Some(children_pdus) = self.get_stripped_space_child_events(current_room).await? { + let summary = self + .get_room_summary(current_room, children_pdus, &identifier) + .await; + if let Ok(summary) = summary { + self.roomid_spacehierarchy_cache.lock().await.insert( + current_room.clone(), + Some(CachedSpaceHierarchySummary { + summary: summary.clone(), + }), + ); - Some(SummaryAccessibility::Accessible(Box::new(summary))) - } else { - None - } + Ok(Some(SummaryAccessibility::Accessible(Box::new(summary)))) } else { - None - }, - ) + Ok(None) + } + } else { + Ok(None) + } } /// Gets the summary of a space using solely federation @@ -393,7 +391,7 @@ impl Service { .is_accessible_child(current_room, &join_rule.clone().into(), identifier, &allowed_room_ids) .await { - debug!("User is not allowed to see room {room_id}"); + debug_info!("User is not allowed to see room {room_id}"); // This error will be caught later return Err(Error::BadRequest(ErrorKind::forbidden(), "User is not allowed to see the room")); } @@ -615,16 +613,13 @@ impl Service { &self, current_room: &OwnedRoomId, join_rule: &SpaceRoomJoinRule, identifier: &Identifier<'_>, allowed_room_ids: &Vec, ) -> bool { - // Note: unwrap_or_default for bool means false match identifier { Identifier::ServerName(server_name) => { - let room_id: &RoomId = current_room; - // Checks if ACLs allow for the server to participate if self .services .event_handler - .acl_check(server_name, room_id) + .acl_check(server_name, current_room) .await .is_err() { @@ -645,8 +640,9 @@ impl Service { return true; } }, - } // Takes care of join rules - match join_rule { + } + match &join_rule { + SpaceRoomJoinRule::Public | SpaceRoomJoinRule::Knock | SpaceRoomJoinRule::KnockRestricted => true, SpaceRoomJoinRule::Restricted => { for room in allowed_room_ids { match identifier { @@ -664,7 +660,6 @@ impl Service { } false }, - SpaceRoomJoinRule::Public | SpaceRoomJoinRule::Knock | SpaceRoomJoinRule::KnockRestricted => true, // Invite only, Private, or Custom join rule _ => false, } diff --git a/src/service/rooms/state/mod.rs b/src/service/rooms/state/mod.rs index 71a3900c..7d8200f0 100644 --- a/src/service/rooms/state/mod.rs +++ b/src/service/rooms/state/mod.rs @@ -295,20 +295,22 @@ impl Service { } #[tracing::instrument(skip_all, level = "debug")] - pub async fn summary_stripped(&self, invite: &PduEvent) -> Vec> { + pub async fn summary_stripped(&self, event: &PduEvent) -> Vec> { let cells = [ (&StateEventType::RoomCreate, ""), (&StateEventType::RoomJoinRules, ""), (&StateEventType::RoomCanonicalAlias, ""), (&StateEventType::RoomName, ""), (&StateEventType::RoomAvatar, ""), - (&StateEventType::RoomMember, invite.sender.as_str()), // Add recommended events + (&StateEventType::RoomMember, event.sender.as_str()), // Add recommended events + (&StateEventType::RoomEncryption, ""), + (&StateEventType::RoomTopic, ""), ]; let fetches = cells.iter().map(|(event_type, state_key)| { self.services .state_accessor - .room_state_get(&invite.room_id, event_type, state_key) + .room_state_get(&event.room_id, event_type, state_key) }); join_all(fetches) @@ -316,7 +318,7 @@ impl Service { .into_iter() .filter_map(Result::ok) .map(|e| e.to_stripped_state_event()) - .chain(once(invite.to_stripped_state_event())) + .chain(once(event.to_stripped_state_event())) .collect() } diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs index d51da8af..4958c4ea 100644 --- a/src/service/rooms/state_accessor/mod.rs +++ b/src/service/rooms/state_accessor/mod.rs @@ -10,7 +10,7 @@ use conduit::{ err, error, pdu::PduBuilder, utils::{math::usize_from_f64, ReadyExt}, - Error, PduEvent, Result, + Err, Error, Event, PduEvent, Result, }; use futures::StreamExt; use lru_cache::LruCache; @@ -29,7 +29,7 @@ use ruma::{ power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, topic::RoomTopicEventContent, }, - StateEventType, + StateEventType, TimelineEventType, }, room::RoomType, space::SpaceRoomJoinRule, @@ -408,34 +408,41 @@ impl Service { pub async fn user_can_redact( &self, redacts: &EventId, sender: &UserId, room_id: &RoomId, federation: bool, ) -> Result { - if let Ok(event) = self + let redacting_event = self.services.timeline.get_pdu(redacts).await; + + if redacting_event + .as_ref() + .is_ok_and(|event| event.event_type() == &TimelineEventType::RoomCreate) + { + return Err!(Request(Forbidden("Redacting m.room.create is not safe, forbidding."))); + } + + if let Ok(pl_event_content) = self .room_state_get_content::(room_id, &StateEventType::RoomPowerLevels, "") .await { - let event: RoomPowerLevels = event.into(); - Ok(event.user_can_redact_event_of_other(sender) - || event.user_can_redact_own_event(sender) - && if let Ok(pdu) = self.services.timeline.get_pdu(redacts).await { + let pl_event: RoomPowerLevels = pl_event_content.into(); + Ok(pl_event.user_can_redact_event_of_other(sender) + || pl_event.user_can_redact_own_event(sender) + && if let Ok(redacting_event) = redacting_event { if federation { - pdu.sender.server_name() == sender.server_name() + redacting_event.sender.server_name() == sender.server_name() } else { - pdu.sender == sender + redacting_event.sender == sender } } else { false }) } else { // Falling back on m.room.create to judge power level - if let Ok(pdu) = self + if let Ok(room_create) = self .room_state_get(room_id, &StateEventType::RoomCreate, "") .await { - Ok(pdu.sender == sender - || if let Ok(pdu) = self.services.timeline.get_pdu(redacts).await { - pdu.sender == sender - } else { - false - }) + Ok(room_create.sender == sender + || redacting_event + .as_ref() + .is_ok_and(|redacting_event| redacting_event.sender == sender)) } else { Err(Error::bad_database( "No m.room.power_levels or m.room.create events in database for room", @@ -454,7 +461,7 @@ impl Service { /// Returns an empty vec if not a restricted room pub fn allowed_room_ids(&self, join_rule: JoinRule) -> Vec { - let mut room_ids = vec![]; + let mut room_ids = Vec::with_capacity(1); if let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = join_rule { for rule in r.allow { if let AllowRule::RoomMembership(RoomMembership { diff --git a/src/service/sending/send.rs b/src/service/sending/send.rs index 5bf48aaa..6a8f1b1b 100644 --- a/src/service/sending/send.rs +++ b/src/service/sending/send.rs @@ -39,7 +39,7 @@ impl super::Service { .forbidden_remote_server_names .contains(dest) { - return Err!(Request(Forbidden(debug_warn!("Federation with this {dest} is not allowed.")))); + return Err!(Request(Forbidden(debug_warn!("Federation with {dest} is not allowed.")))); } let actual = self.services.resolver.get_actual_dest(dest).await?; diff --git a/src/service/sending/sender.rs b/src/service/sending/sender.rs index f4268293..f5d87504 100644 --- a/src/service/sending/sender.rs +++ b/src/service/sending/sender.rs @@ -235,13 +235,15 @@ impl Service { fn select_events_current(&self, dest: Destination, statuses: &mut CurTransactionStatus) -> Result<(bool, bool)> { let (mut allow, mut retry) = (true, false); statuses - .entry(dest) + .entry(dest.clone()) // TODO: can we avoid cloning? .and_modify(|e| match e { TransactionStatus::Failed(tries, time) => { // Fail if a request has failed recently (exponential backoff) let min = self.server.config.sender_timeout; let max = self.server.config.sender_retry_backoff_limit; - if continue_exponential_backoff_secs(min, max, time.elapsed(), *tries) { + if continue_exponential_backoff_secs(min, max, time.elapsed(), *tries) + && !matches!(dest, Destination::Appservice(_)) + { allow = false; } else { retry = true;