fix: Correctly filter out ignored invites in /sync

This commit is contained in:
Ginger committed 2025-09-11 14:54:12 -04:00
1 parent a42769121e
commit 7f37f7f8b0
10 files changed
+102 -55

No files matched your search

+17 -14
View File
@@ -53,13 +53,8 @@ pub(crate) async fn invite_user_route(
| invite_user::v3::InvitationRecipient::UserId { user_id: recipient_user } => {
let sender_filter_level = services
.users
.invite_filter_level(recipient_user, sender_user);
let recipient_filter_level = services
.users
.invite_filter_level(sender_user, recipient_user);
let (sender_filter_level, recipient_filter_level) =
join!(sender_filter_level, recipient_filter_level);
.invite_filter_level(recipient_user, sender_user)
.await;
if !matches!(sender_filter_level, FilterLevel::Allow) {
// drop invites if the sender has the recipient filtered
@@ -77,13 +72,21 @@ pub(crate) async fn invite_user_route(
}
}
// return an error for blocked invites. ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in api::client::message::is_ignored_pdu
if matches!(recipient_filter_level, FilterLevel::Block) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
// check for blocked invites if the recipient is a local user.
if services.globals.user_is_local(&recipient_user) {
let recipient_filter_level = services
.users
.invite_filter_level(sender_user, recipient_user)
.await;
// ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in the individual /sync handlers.
if matches!(recipient_filter_level, FilterLevel::Block) {
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from you."
)));
}
}
invite_helper(
+25 -24
View File
@@ -7,7 +7,7 @@ use conduwuit::{
},
ref_at,
utils::{
IterStream, ReadyExt,
IterStream, ReadyExt, TryFutureExtExt,
result::LogErr,
stream::{BroadbandExt, TryIgnore, WidebandExt},
},
@@ -22,7 +22,7 @@ use conduwuit_service::{
};
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, pin_mut};
use ruma::{
DeviceId, RoomId, UserId,
DeviceId, OwnedRoomId, RoomId, UserId,
api::{
Direction,
client::{filter::RoomEventFilter, message::get_message_events},
@@ -305,28 +305,6 @@ where
return true;
}
if *event.kind() == RoomMember {
if event
.get_content::<RoomMemberEventContent>()
.is_ok_and(|content| content.membership == MembershipState::Invite)
{
if event
.state_key()
.is_some_and(|key| key == recipient_user.as_str())
{
if services
.users
.invite_filter_level(sender_user, recipient_user)
.await == FilterLevel::Ignore
{
// this PDU is inviting the recipient to a room and is from a sender that the
// recipient has ignored invites from
return true;
}
}
}
}
false
}
@@ -352,6 +330,29 @@ pub(crate) fn event_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Opti
filter.matches(pdu).then_some(item)
}
#[inline]
pub(crate) async fn is_ignored_invite(
services: &Services,
recipient_user: &UserId,
room_id: &RoomId,
) -> bool {
let Ok(sender_user) = services
.rooms
.state_cache
.invite_sender(recipient_user, room_id)
.await
else {
// the invite may have been sent before the invite_sender table existed.
// assume it's not ignored
return false;
};
services
.users
.invite_filter_level(&sender_user, recipient_user)
.await == FilterLevel::Ignore
}
#[cfg_attr(debug_assertions, ctor::ctor)]
fn _is_sorted() {
debug_assert!(
+1 -1
View File
@@ -119,7 +119,7 @@ pub(crate) async fn create_room_route(
// if the recipient of the invite is local and has the sender blocked, error
// out. if the recipient is remote we can't tell yet, and if they're local and
// have the sender _ignored_ their invite will be filtered out in
// api::client::message::is_ignored_pdu
// the handlers for the individual /sync endpoints
if services.globals.user_is_local(recipient_user)
&& matches!(
services
+11 -1
View File
@@ -60,7 +60,10 @@ use ruma::{
use service::rooms::short::{ShortEventId, ShortStateKey};
use super::{load_timeline, share_encrypted_room};
use crate::{Ruma, RumaResponse, client::ignored_filter};
use crate::{
Ruma, RumaResponse,
client::{ignored_filter, is_ignored_invite},
};
#[derive(Default)]
struct StateChanges {
@@ -238,6 +241,13 @@ pub(crate) async fn build_sync_events(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(&services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.fold_default(|mut invited_rooms: BTreeMap<_, _>, (room_id, invite_state)| async move {
let invite_count = services
.rooms
+9 -1
View File
@@ -11,6 +11,7 @@ use conduwuit::{
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
math::{ruma_from_usize, usize_from_ruma, usize_from_u64_truncated},
stream::WidebandExt,
},
warn,
};
@@ -39,7 +40,7 @@ use ruma::{
use super::{load_timeline, share_encrypted_room};
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter},
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite},
};
type TodoRooms = BTreeMap<OwnedRoomId, (BTreeSet<TypeStateKey>, usize, u64)>;
@@ -102,6 +103,13 @@ pub(crate) async fn sync_events_v4_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(&services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect()
.await;
+9 -1
View File
@@ -14,6 +14,7 @@ use conduwuit::{
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::ReadyEqExt,
math::{ruma_from_usize, usize_from_ruma},
stream::WidebandExt,
},
warn,
};
@@ -38,7 +39,7 @@ use ruma::{
use super::share_encrypted_room;
use crate::{
Ruma,
client::{DEFAULT_BUMP_TYPES, ignored_filter, sync::load_timeline},
client::{DEFAULT_BUMP_TYPES, ignored_filter, is_ignored_invite, sync::load_timeline},
};
type SyncInfo<'a> = (&'a UserId, &'a DeviceId, u64, &'a sync_events::v5::Request);
@@ -106,6 +107,13 @@ pub(crate) async fn sync_events_v5_route(
.rooms
.state_cache
.rooms_invited(sender_user)
.wide_filter_map(async |(room_id, invite_state)| {
if is_ignored_invite(&services, sender_user, &room_id).await {
None
} else {
Some((room_id, invite_state))
}
})
.map(|r| r.0)
.collect::<Vec<OwnedRoomId>>();
-9
View File
@@ -116,15 +116,6 @@ pub(crate) async fn create_invite_route(
.invite_filter_level(sender_user, &recipient_user)
.await;
if matches!(recipient_invite_filter_level, FilterLevel::Block) {
// return an error for blocked invites. ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in api::client::message::is_ignored_pdu
return Err!(Request(InviteBlocked(
"{recipient_user} has blocked invites from {sender_user}."
)));
}
let mut invite_state = body.invite_room_state.clone();
let mut event: JsonObject = serde_json::from_str(body.event.get())
+4
View File
@@ -434,4 +434,8 @@ pub(super) static MAPS: &[Descriptor] = &[
name: "userroomid_notificationcount",
..descriptor::RANDOM
},
Descriptor {
name: "userroomid_invitesender",
..descriptor::RANDOM_SMALL
},
];
+14 -1
View File
@@ -12,7 +12,7 @@ use conduwuit::{
use database::{Deserialized, Ignore, Interfix, Map};
use futures::{Stream, StreamExt, future::join5, pin_mut};
use ruma::{
OwnedRoomId, RoomId, ServerName, UserId,
OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
events::{AnyStrippedStateEvent, AnySyncStateEvent, room::member::MembershipState},
serde::Raw,
};
@@ -49,6 +49,7 @@ struct Data {
userroomid_joined: Arc<Map>,
userroomid_leftstate: Arc<Map>,
userroomid_knockedstate: Arc<Map>,
userroomid_invitesender: Arc<Map>,
}
type AppServiceInRoomCache = SyncRwLock<HashMap<OwnedRoomId, HashMap<String, bool>>>;
@@ -83,6 +84,7 @@ impl crate::Service for Service {
userroomid_joined: args.db["userroomid_joined"].clone(),
userroomid_leftstate: args.db["userroomid_leftstate"].clone(),
userroomid_knockedstate: args.db["userroomid_knockedstate"].clone(),
userroomid_invitesender: args.db["userroomid_invitesender"].clone(),
},
}))
}
@@ -523,3 +525,14 @@ pub async fn is_left(&self, user_id: &UserId, room_id: &RoomId) -> bool {
let key = (user_id, room_id);
self.db.userroomid_leftstate.qry(&key).await.is_ok()
}
#[implement(Service)]
#[tracing::instrument(skip(self), level = "trace")]
pub async fn invite_sender(&self, user_id: &UserId, room_id: &RoomId) -> Result<OwnedUserId> {
let key = (user_id, room_id);
self.db
.userroomid_invitesender
.qry(&key)
.await
.deserialized()
}
+12 -3
View File
@@ -124,7 +124,7 @@ pub async fn update_membership(
| MembershipState::Invite => {
// return an error for blocked invites. ignored invites aren't handled here
// since the recipient's membership should still be changed to `invite`.
// they're filtered out in api::client::message::is_ignored_pdu
// they're filtered out in the individual /sync handlers
if matches!(
self.services
.users
@@ -132,9 +132,11 @@ pub async fn update_membership(
.await,
FilterLevel::Block
) {
return Err!(Request(InviteBlocked("{user_id} has blocked invites from you.")));
return Err!(Request(InviteBlocked(
"{user_id} has blocked invites from {sender}."
)));
}
self.mark_as_invited(user_id, room_id, last_state, invite_via)
self.mark_as_invited(user_id, room_id, sender, last_state, invite_via)
.await;
},
| MembershipState::Leave | MembershipState::Ban => {
@@ -239,6 +241,7 @@ pub fn mark_as_joined(&self, user_id: &UserId, room_id: &RoomId) {
self.db.userroomid_invitestate.remove(&userroom_id);
self.db.roomuserid_invitecount.remove(&roomuser_id);
self.db.userroomid_invitesender.remove(&userroom_id);
self.db.userroomid_leftstate.remove(&userroom_id);
self.db.roomuserid_leftcount.remove(&roomuser_id);
@@ -276,6 +279,7 @@ pub fn mark_as_left(&self, user_id: &UserId, room_id: &RoomId) {
self.db.userroomid_invitestate.remove(&userroom_id);
self.db.roomuserid_invitecount.remove(&roomuser_id);
self.db.userroomid_invitesender.remove(&userroom_id);
self.db.userroomid_knockedstate.remove(&userroom_id);
self.db.roomuserid_knockedcount.remove(&roomuser_id);
@@ -312,6 +316,7 @@ pub fn mark_as_knocked(
self.db.userroomid_invitestate.remove(&userroom_id);
self.db.roomuserid_invitecount.remove(&roomuser_id);
self.db.userroomid_invitesender.remove(&userroom_id);
self.db.userroomid_leftstate.remove(&userroom_id);
self.db.roomuserid_leftcount.remove(&roomuser_id);
@@ -343,6 +348,7 @@ pub async fn mark_as_invited(
&self,
user_id: &UserId,
room_id: &RoomId,
sender_user: &UserId,
last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
invite_via: Option<Vec<OwnedServerName>>,
) {
@@ -358,6 +364,9 @@ pub async fn mark_as_invited(
self.db
.roomuserid_invitecount
.raw_aput::<8, _, _>(&roomuser_id, self.services.globals.next_count().unwrap());
self.db
.userroomid_invitesender
.raw_put(&userroom_id, sender_user);
self.db.userroomid_joined.remove(&userroom_id);
self.db.roomuserid_joined.remove(&roomuser_id);