feat(presence): add granular allow configuration

This commit is contained in:
Jakub Kubík 2023-09-08 14:36:39 +02:00 committed by girlbossceo
parent ba03edfae9
commit 58a83f06b1
9 changed files with 133 additions and 98 deletions

View file

@ -11,6 +11,13 @@ use std::time::Duration;
pub async fn set_presence_route( pub async fn set_presence_route(
body: Ruma<set_presence::v3::Request>, body: Ruma<set_presence::v3::Request>,
) -> Result<set_presence::v3::Response> { ) -> Result<set_presence::v3::Response> {
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 sender_user = body.sender_user.as_ref().expect("user is authenticated");
for room_id in services().rooms.state_cache.rooms_joined(sender_user) { for room_id in services().rooms.state_cache.rooms_joined(sender_user) {
let room_id = room_id?; let room_id = room_id?;
@ -36,6 +43,13 @@ pub async fn set_presence_route(
pub async fn get_presence_route( pub async fn get_presence_route(
body: Ruma<get_presence::v3::Request>, body: Ruma<get_presence::v3::Request>,
) -> Result<get_presence::v3::Response> { ) -> Result<get_presence::v3::Response> {
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 sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut presence_event = None; let mut presence_event = None;

View file

@ -92,12 +92,14 @@ pub async fn set_displayname_route(
); );
} }
// Presence update if services().globals.allow_local_presence() {
services() // Presence update
.rooms services()
.edus .rooms
.presence .edus
.ping_presence(sender_user, PresenceState::Online)?; .presence
.ping_presence(sender_user, PresenceState::Online)?;
}
Ok(set_display_name::v3::Response {}) Ok(set_display_name::v3::Response {})
} }
@ -213,12 +215,14 @@ pub async fn set_avatar_url_route(
); );
} }
// Presence update if services().globals.allow_local_presence() {
services() // Presence update
.rooms services()
.edus .rooms
.presence .edus
.ping_presence(sender_user, PresenceState::Online)?; .presence
.ping_presence(sender_user, PresenceState::Online)?;
}
Ok(set_avatar_url::v3::Response {}) Ok(set_avatar_url::v3::Response {})
} }

View file

@ -16,6 +16,7 @@ use ruma::{
uiaa::UiaaResponse, uiaa::UiaaResponse,
}, },
events::{ events::{
presence::PresenceEvent,
room::member::{MembershipState, RoomMemberEventContent}, room::member::{MembershipState, RoomMemberEventContent},
StateEventType, TimelineEventType, StateEventType, TimelineEventType,
}, },
@ -171,11 +172,13 @@ async fn sync_helper(
// bool = caching allowed // bool = caching allowed
) -> Result<(sync_events::v3::Response, bool), Error> { ) -> Result<(sync_events::v3::Response, bool), Error> {
// Presence update // Presence update
services() if services().globals.allow_local_presence() {
.rooms services()
.edus .rooms
.presence .edus
.ping_presence(&sender_user, body.set_presence)?; .presence
.ping_presence(&sender_user, body.set_presence)?;
}
// Setup watchers, so if there's no response, we can wait for them // Setup watchers, so if there's no response, we can wait for them
let watcher = services().globals.watch(&sender_user, &sender_device); let watcher = services().globals.watch(&sender_user, &sender_device);
@ -251,39 +254,8 @@ async fn sync_helper(
joined_rooms.insert(room_id.clone(), joined_room); joined_rooms.insert(room_id.clone(), joined_room);
} }
// Take presence updates from this room if services().globals.allow_local_presence() {
for presence_data in services() process_room_presence_updates(&mut presence_updates, &room_id, since).await?;
.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);
}
}
} }
} }
} }
@ -558,6 +530,49 @@ async fn sync_helper(
} }
} }
async fn process_room_presence_updates(
presence_updates: &mut HashMap<OwnedUserId, PresenceEvent>,
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(())
}
async fn load_joined_room( async fn load_joined_room(
sender_user: &UserId, sender_user: &UserId,
sender_device: &DeviceId, sender_device: &DeviceId,

View file

@ -839,6 +839,10 @@ pub async fn send_transaction_message_route(
{ {
match edu { match edu {
Edu::Presence(presence) => { Edu::Presence(presence) => {
if !services().globals.allow_incoming_presence() {
continue;
}
for update in presence.push { for update in presence.push {
for room_id in services().rooms.state_cache.rooms_joined(&update.user_id) { for room_id in services().rooms.state_cache.rooms_joined(&update.user_id) {
services().rooms.edus.presence.set_presence( services().rooms.edus.presence.set_presence(

View file

@ -93,7 +93,11 @@ pub struct Config {
pub emergency_password: Option<String>, pub emergency_password: Option<String>,
#[serde(default = "false_fn")] #[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")] #[serde(default = "default_presence_idle_timeout_s")]
pub presence_idle_timeout_s: u64, pub presence_idle_timeout_s: u64,
#[serde(default = "default_presence_offline_timeout_s")] #[serde(default = "default_presence_offline_timeout_s")]

View file

@ -1,4 +1,4 @@
use std::{iter, time::Duration}; use std::time::Duration;
use ruma::{ use ruma::{
events::presence::PresenceEvent, presence::PresenceState, OwnedUserId, RoomId, UInt, UserId, events::presence::PresenceEvent, presence::PresenceState, OwnedUserId, RoomId, UInt, UserId,
@ -14,10 +14,6 @@ use crate::{
impl service::rooms::edus::presence::Data for KeyValueDatabase { impl service::rooms::edus::presence::Data for KeyValueDatabase {
fn get_presence(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<PresenceEvent>> { fn get_presence(&self, room_id: &RoomId, user_id: &UserId) -> Result<Option<PresenceEvent>> {
if !services().globals.config.allow_presence {
return Ok(None);
}
let key = presence_key(room_id, user_id); let key = presence_key(room_id, user_id);
self.roomuserid_presence 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<()> { 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 now = utils::millis_since_unix_epoch();
let mut state_changed = false; let mut state_changed = false;
@ -103,10 +95,6 @@ impl service::rooms::edus::presence::Data for KeyValueDatabase {
last_active_ago: Option<UInt>, last_active_ago: Option<UInt>,
status_msg: Option<String>, status_msg: Option<String>,
) -> Result<()> { ) -> Result<()> {
if !services().globals.config.allow_presence {
return Ok(());
}
let now = utils::millis_since_unix_epoch(); let now = utils::millis_since_unix_epoch();
let last_active_ts = match last_active_ago { let last_active_ts = match last_active_ago {
Some(last_active_ago) => now.saturating_sub(last_active_ago.into()), 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, room_id: &RoomId,
since: u64, since: u64,
) -> Box<dyn Iterator<Item = Result<(OwnedUserId, u64, PresenceEvent)>> + 'a> { ) -> Box<dyn Iterator<Item = Result<(OwnedUserId, u64, PresenceEvent)>> + 'a> {
if !services().globals.config.allow_presence {
return Box::new(iter::empty());
}
let prefix = [room_id.as_bytes(), &[0xff]].concat(); let prefix = [room_id.as_bytes(), &[0xff]].concat();
Box::new( Box::new(

View file

@ -991,7 +991,7 @@ impl KeyValueDatabase {
if services().globals.allow_check_for_updates() { if services().globals.allow_check_for_updates() {
Self::start_check_for_updates_task(); 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; Self::start_presence_handler(presence_receiver).await;
} }

View file

@ -367,8 +367,16 @@ impl Service {
&self.config.emergency_password &self.config.emergency_password
} }
pub fn allow_presence(&self) -> bool { pub fn allow_local_presence(&self) -> bool {
self.config.allow_presence 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 { pub fn presence_idle_timeout_s(&self) -> u64 {

View file

@ -286,39 +286,41 @@ impl Service {
.filter(|user_id| user_id.server_name() == services().globals.server_name()), .filter(|user_id| user_id.server_name() == services().globals.server_name()),
); );
// Look for presence updates in this room if services().globals.allow_outcoming_presence() {
let mut presence_updates = Vec::new(); // Look for presence updates in this room
let mut presence_updates = Vec::new();
for presence_data in services() for presence_data in services()
.rooms .rooms
.edus .edus
.presence .presence
.presence_since(&room_id, since) .presence_since(&room_id, since)
{ {
let (user_id, count, presence_event) = presence_data?; let (user_id, count, presence_event) = presence_data?;
if count > max_edu_count { if count > max_edu_count {
max_edu_count = 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() { let presence_content = Edu::Presence(PresenceContent::new(presence_updates));
continue; events.push(
} serde_json::to_vec(&presence_content).expect("PresenceEvent can be serialized"),
);
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"),
);
// Look for read receipts in this room // Look for read receipts in this room
for r in services() for r in services()
.rooms .rooms