From ae76052378d99b6f49d8a7c54699c4ac8fdc6846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kub=C3=ADk?= Date: Fri, 8 Sep 2023 14:36:39 +0200 Subject: [PATCH] feat(presence): add granular allow configuration --- src/api/client_server/presence.rs | 14 +++ src/api/client_server/profile.rs | 28 +++--- src/api/client_server/sync.rs | 91 +++++++++++-------- src/api/server_server.rs | 4 + src/config/mod.rs | 6 +- src/database/key_value/rooms/edus/presence.rs | 18 +--- src/database/mod.rs | 2 +- src/service/globals/mod.rs | 12 ++- src/service/sending/mod.rs | 56 ++++++------ 9 files changed, 133 insertions(+), 98 deletions(-) diff --git a/src/api/client_server/presence.rs b/src/api/client_server/presence.rs index 46cad8fe..3d56e6f3 100644 --- a/src/api/client_server/presence.rs +++ b/src/api/client_server/presence.rs @@ -11,6 +11,13 @@ use std::time::Duration; pub async fn set_presence_route( body: Ruma, ) -> Result { + if !services().globals.allow_local_presence() { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Presence is disabled on this server", + )); + } + let sender_user = body.sender_user.as_ref().expect("user is authenticated"); for room_id in services().rooms.state_cache.rooms_joined(sender_user) { let room_id = room_id?; @@ -36,6 +43,13 @@ pub async fn set_presence_route( pub async fn get_presence_route( body: Ruma, ) -> Result { + if !services().globals.allow_local_presence() { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Presence is disabled on this server", + )); + } + let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let mut presence_event = None; diff --git a/src/api/client_server/profile.rs b/src/api/client_server/profile.rs index 00d52eb0..07355301 100644 --- a/src/api/client_server/profile.rs +++ b/src/api/client_server/profile.rs @@ -92,12 +92,14 @@ pub async fn set_displayname_route( .await; } - // Presence update - services() - .rooms - .edus - .presence - .ping_presence(sender_user, PresenceState::Online)?; + if services().globals.allow_local_presence() { + // Presence update + services() + .rooms + .edus + .presence + .ping_presence(sender_user, PresenceState::Online)?; + } Ok(set_display_name::v3::Response {}) } @@ -213,12 +215,14 @@ pub async fn set_avatar_url_route( .await; } - // Presence update - services() - .rooms - .edus - .presence - .ping_presence(sender_user, PresenceState::Online)?; + if services().globals.allow_local_presence() { + // Presence update + services() + .rooms + .edus + .presence + .ping_presence(sender_user, PresenceState::Online)?; + } Ok(set_avatar_url::v3::Response {}) } diff --git a/src/api/client_server/sync.rs b/src/api/client_server/sync.rs index 7b7224c3..ac8dcd5d 100644 --- a/src/api/client_server/sync.rs +++ b/src/api/client_server/sync.rs @@ -18,6 +18,7 @@ use ruma::{ uiaa::UiaaResponse, }, events::{ + presence::PresenceEvent, room::member::{MembershipState, RoomMemberEventContent}, StateEventType, TimelineEventType, }, @@ -175,11 +176,13 @@ async fn sync_helper( // bool = caching allowed ) -> Result<(sync_events::v3::Response, bool), Error> { // Presence update - services() - .rooms - .edus - .presence - .ping_presence(&sender_user, body.set_presence)?; + if services().globals.allow_local_presence() { + services() + .rooms + .edus + .presence + .ping_presence(&sender_user, body.set_presence)?; + } // Setup watchers, so if there's no response, we can wait for them let watcher = services().globals.watch(&sender_user, &sender_device); @@ -255,39 +258,8 @@ async fn sync_helper( joined_rooms.insert(room_id.clone(), joined_room); } - // Take presence updates from this room - for presence_data in services() - .rooms - .edus - .presence - .presence_since(&room_id, since) - { - let (user_id, _, presence_event) = presence_data?; - - match presence_updates.entry(user_id) { - Entry::Vacant(slot) => { - slot.insert(presence_event); - } - Entry::Occupied(mut slot) => { - let curr_event = slot.get_mut(); - let curr_content = &mut curr_event.content; - let new_content = presence_event.content; - - // Update existing presence event with more info - curr_content.presence = new_content.presence; - curr_content.status_msg = - curr_content.status_msg.clone().or(new_content.status_msg); - curr_content.last_active_ago = - curr_content.last_active_ago.or(new_content.last_active_ago); - curr_content.displayname = - curr_content.displayname.clone().or(new_content.displayname); - curr_content.avatar_url = - curr_content.avatar_url.clone().or(new_content.avatar_url); - curr_content.currently_active = curr_content - .currently_active - .or(new_content.currently_active); - } - } + if services().globals.allow_local_presence() { + process_room_presence_updates(&mut presence_updates, &room_id, since).await?; } } } @@ -599,6 +571,49 @@ async fn sync_helper( } } +async fn process_room_presence_updates( + presence_updates: &mut HashMap, + room_id: &RoomId, + since: u64, +) -> Result<()> { + // Take presence updates from this room + for presence_data in services() + .rooms + .edus + .presence + .presence_since(room_id, since) + { + let (user_id, _, presence_event) = presence_data?; + + match presence_updates.entry(user_id) { + Entry::Vacant(slot) => { + slot.insert(presence_event); + } + Entry::Occupied(mut slot) => { + let curr_event = slot.get_mut(); + let curr_content = &mut curr_event.content; + let new_content = presence_event.content; + + // Update existing presence event with more info + curr_content.presence = new_content.presence; + curr_content.status_msg = + curr_content.status_msg.clone().or(new_content.status_msg); + curr_content.last_active_ago = + curr_content.last_active_ago.or(new_content.last_active_ago); + curr_content.displayname = + curr_content.displayname.clone().or(new_content.displayname); + curr_content.avatar_url = + curr_content.avatar_url.clone().or(new_content.avatar_url); + curr_content.currently_active = curr_content + .currently_active + .or(new_content.currently_active); + } + } + } + + Ok(()) +} + #[allow(clippy::too_many_arguments)] async fn load_joined_room( sender_user: &UserId, diff --git a/src/api/server_server.rs b/src/api/server_server.rs index 178d0c99..59e656fe 100644 --- a/src/api/server_server.rs +++ b/src/api/server_server.rs @@ -778,6 +778,10 @@ pub async fn send_transaction_message_route( { match edu { Edu::Presence(presence) => { + if !services().globals.allow_incoming_presence() { + continue; + } + for update in presence.push { for room_id in services().rooms.state_cache.rooms_joined(&update.user_id) { services().rooms.edus.presence.set_presence( diff --git a/src/config/mod.rs b/src/config/mod.rs index bef5ebf8..16930fb0 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -84,7 +84,11 @@ pub struct Config { pub emergency_password: Option, #[serde(default = "false_fn")] - pub allow_presence: bool, + pub allow_local_presence: bool, + #[serde(default = "false_fn")] + pub allow_incoming_presence: bool, + #[serde(default = "false_fn")] + pub allow_outgoing_presence: bool, #[serde(default = "default_presence_idle_timeout_s")] pub presence_idle_timeout_s: u64, #[serde(default = "default_presence_offline_timeout_s")] diff --git a/src/database/key_value/rooms/edus/presence.rs b/src/database/key_value/rooms/edus/presence.rs index a2c8ee44..d70117e0 100644 --- a/src/database/key_value/rooms/edus/presence.rs +++ b/src/database/key_value/rooms/edus/presence.rs @@ -1,4 +1,4 @@ -use std::{iter, time::Duration}; +use std::time::Duration; use ruma::{ events::presence::PresenceEvent, presence::PresenceState, OwnedUserId, RoomId, UInt, UserId, @@ -14,10 +14,6 @@ use crate::{ impl service::rooms::edus::presence::Data for KeyValueDatabase { fn get_presence(&self, room_id: &RoomId, user_id: &UserId) -> Result> { - if !services().globals.config.allow_presence { - return Ok(None); - } - let key = presence_key(room_id, user_id); self.roomuserid_presence @@ -29,10 +25,6 @@ impl service::rooms::edus::presence::Data for KeyValueDatabase { } fn ping_presence(&self, user_id: &UserId, new_state: PresenceState) -> Result<()> { - if !services().globals.config.allow_presence { - return Ok(()); - } - let now = utils::millis_since_unix_epoch(); let mut state_changed = false; @@ -103,10 +95,6 @@ impl service::rooms::edus::presence::Data for KeyValueDatabase { last_active_ago: Option, status_msg: Option, ) -> Result<()> { - if !services().globals.config.allow_presence { - return Ok(()); - } - let now = utils::millis_since_unix_epoch(); let last_active_ts = match last_active_ago { Some(last_active_ago) => now.saturating_sub(last_active_ago.into()), @@ -153,10 +141,6 @@ impl service::rooms::edus::presence::Data for KeyValueDatabase { room_id: &RoomId, since: u64, ) -> Box> + 'a> { - if !services().globals.config.allow_presence { - return Box::new(iter::empty()); - } - let prefix = [room_id.as_bytes(), &[0xff]].concat(); Box::new( diff --git a/src/database/mod.rs b/src/database/mod.rs index 7d7dad7c..fb07c205 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -988,7 +988,7 @@ impl KeyValueDatabase { if services().globals.allow_check_for_updates() { Self::start_check_for_updates_task(); } - if services().globals.config.allow_presence { + if services().globals.allow_local_presence() { Self::start_presence_handler(presence_receiver).await; } diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 1f4fc382..d3185130 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -353,8 +353,16 @@ impl Service { &self.config.emergency_password } - pub fn allow_presence(&self) -> bool { - self.config.allow_presence + pub fn allow_local_presence(&self) -> bool { + self.config.allow_local_presence + } + + pub fn allow_incoming_presence(&self) -> bool { + self.config.allow_incoming_presence + } + + pub fn allow_outcoming_presence(&self) -> bool { + self.config.allow_outgoing_presence } pub fn presence_idle_timeout_s(&self) -> u64 { diff --git a/src/service/sending/mod.rs b/src/service/sending/mod.rs index eb8fe849..670463a7 100644 --- a/src/service/sending/mod.rs +++ b/src/service/sending/mod.rs @@ -286,39 +286,41 @@ impl Service { .filter(|user_id| user_id.server_name() == services().globals.server_name()), ); - // Look for presence updates in this room - let mut presence_updates = Vec::new(); + if services().globals.allow_outcoming_presence() { + // Look for presence updates in this room + let mut presence_updates = Vec::new(); - for presence_data in services() - .rooms - .edus - .presence - .presence_since(&room_id, since) - { - let (user_id, count, presence_event) = presence_data?; + for presence_data in services() + .rooms + .edus + .presence + .presence_since(&room_id, since) + { + let (user_id, count, presence_event) = presence_data?; - if count > max_edu_count { - max_edu_count = count; + if count > max_edu_count { + max_edu_count = count; + } + + if user_id.server_name() != services().globals.server_name() { + continue; + } + + presence_updates.push(PresenceUpdate { + user_id, + presence: presence_event.content.presence, + currently_active: presence_event.content.currently_active.unwrap_or(false), + last_active_ago: presence_event.content.last_active_ago.unwrap_or(uint!(0)), + status_msg: presence_event.content.status_msg, + }); } - if user_id.server_name() != services().globals.server_name() { - continue; - } - - presence_updates.push(PresenceUpdate { - user_id, - presence: presence_event.content.presence, - currently_active: presence_event.content.currently_active.unwrap_or(false), - last_active_ago: presence_event.content.last_active_ago.unwrap_or(uint!(0)), - status_msg: presence_event.content.status_msg, - }); + let presence_content = Edu::Presence(PresenceContent::new(presence_updates)); + events.push( + serde_json::to_vec(&presence_content).expect("PresenceEvent can be serialized"), + ); } - let presence_content = Edu::Presence(PresenceContent::new(presence_updates)); - events.push( - serde_json::to_vec(&presence_content).expect("PresenceEvent can be serialized"), - ); - // Look for read receipts in this room for r in services() .rooms