diff --git a/Cargo.toml b/Cargo.toml index 4fb605ef9..522883d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -383,6 +383,7 @@ features = [ "unstable-msc4354", "unstable-msc4480", "unstable-msc4466", + "unstable-msc4494", "unstable-extensible-events", ] diff --git a/src/core/matrix/versions.rs b/src/core/matrix/versions.rs index 42b147a40..1e8050cf2 100644 --- a/src/core/matrix/versions.rs +++ b/src/core/matrix/versions.rs @@ -46,5 +46,7 @@ pub fn unstable_features() -> BTreeMap { ("computer.gingershaped.msc4466".to_owned(), true), // web push (https://github.com/matrix-org/matrix-spec-proposals/pull/4174) ("org.matrix.msc4174".to_owned(), true), + // Membership-based invite blocking (https://github.com/matrix-org/matrix-spec-proposals/pull/4494) + ("uk.timedout.msc4494".to_owned(), true), ]) } diff --git a/src/service/users/account.rs b/src/service/users/account.rs index a7592408e..27510e138 100644 --- a/src/service/users/account.rs +++ b/src/service/users/account.rs @@ -4,7 +4,7 @@ }; use conduwuit::{ - Err, Result, debug_error, debug_warn, err, error, info, trace, + Err, Result, debug, debug_error, debug_warn, err, error, info, trace, utils::{self, stream::TryIgnore}, warn, }; @@ -16,10 +16,12 @@ api::client::profile::PropagateTo, events::{ GlobalAccountDataEventType, ignored_user_list::IgnoredUserListEvent, - push_rules::PushRulesEvent, room::message::RoomMessageEventContent, + invite_permission_config::InvitePermissionAction, push_rules::PushRulesEvent, + room::message::RoomMessageEventContent, }, profile::ProfileFieldValue, push::Ruleset, + room::JoinRule, }; use ruminuwuity::invite_permission_config::{FilterLevel, InvitePermissionConfigEvent}; @@ -105,13 +107,47 @@ pub async fn invite_filter_level( if stable.is_err() && unstable.is_err() { return FilterLevel::Allow; } - stable - .unwrap_or_else(|_| unstable.unwrap()) - .content - .user_filter_level(sender_user) + let content = stable.unwrap_or_else(|_| unstable.unwrap()).content; + let verdict = content.user_filter_level(sender_user); + if verdict != FilterLevel::Allow { + return verdict; + } + // Apply other layers of invite blocking first, so that user/server + // denies take priority over implicit allows. + if content.default_action != Some(InvitePermissionAction::DenyPublic) + && self + .check_msc4494_mutual_room(sender_user, recipient_user) + .await + { + FilterLevel::Block + } else { + FilterLevel::Allow + } } } + /// Checks that sender and recipient share at least one rule that has a join + /// rule other than `public`. + async fn check_msc4494_mutual_room(&self, sender: &UserId, recipient: &UserId) -> bool { + let mut mutual_rooms = std::pin::pin!( + self.services + .state_cache + .get_shared_rooms(sender, recipient) + ); + while let Some(room_id) = mutual_rooms.next().await { + if self.services.state_accessor.get_join_rules(&room_id).await != JoinRule::Public { + debug!( + %sender, + %recipient, + %room_id, + "Sender and recipient share a non-public room" + ); + return true; + } + } + false + } + /// Check if a user is an admin #[inline] pub async fn is_admin(&self, user_id: &UserId) -> bool {