Skip to content

Commit 8010377

Browse files
Add support for MSC4155 Invite filtering (#18288)
This implements matrix-org/matrix-spec-proposals#4155, which adds support for a new account data type that blocks an invite based on some conditions in the event contents. --------- Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com>
1 parent 586b82e commit 8010377

File tree

17 files changed

+542
-5
lines changed

17 files changed

+542
-5
lines changed

changelog.d/18288.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for [MSC4155](https://github.com/matrix-org/matrix-spec-proposals/pull/4155) Invite Filtering.

docker/complement/conf/workers-shared-extra.yaml.j2

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ experimental_features:
127127
msc3983_appservice_otk_claims: true
128128
# Proxy key queries to exclusive ASes
129129
msc3984_appservice_key_query: true
130+
# Invite filtering
131+
msc4155_enabled: true
130132

131133
server_notices:
132134
system_mxid_localpart: _server

scripts-dev/complement.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ test_packages=(
229229
./tests/msc3902
230230
./tests/msc3967
231231
./tests/msc4140
232+
./tests/msc4155
232233
)
233234

234235
# Enable dirty runs, so tests will reuse the same container where possible.

synapse/api/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ class AccountDataTypes:
286286
IGNORED_USER_LIST: Final = "m.ignored_user_list"
287287
TAG: Final = "m.tag"
288288
PUSH_RULES: Final = "m.push_rules"
289+
# MSC4155: Invite filtering
290+
MSC4155_INVITE_PERMISSION_CONFIG: Final = (
291+
"org.matrix.msc4155.invite_permission_config"
292+
)
289293

290294

291295
class HistoryVisibility:

synapse/api/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class Codes(str, Enum):
137137
PROFILE_TOO_LARGE = "M_PROFILE_TOO_LARGE"
138138
KEY_TOO_LARGE = "M_KEY_TOO_LARGE"
139139

140+
# Part of MSC4155
141+
INVITE_BLOCKED = "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED"
142+
140143

141144
class CodeMessageException(RuntimeError):
142145
"""An exception with integer code, a message string attributes and optional headers.

synapse/config/experimental.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,3 +566,6 @@ def read_config(
566566
"msc4263_limit_key_queries_to_users_who_share_rooms",
567567
False,
568568
)
569+
570+
# MSC4155: Invite filtering
571+
self.msc4155_enabled: bool = experimental.get("msc4155_enabled", False)

synapse/handlers/federation.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
ReplicationStoreRoomOnOutlierMembershipRestServlet,
7979
)
8080
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
81+
from synapse.storage.invite_rule import InviteRule
8182
from synapse.types import JsonDict, StrCollection, get_domain_from_id
8283
from synapse.types.state import StateFilter
8384
from synapse.util.async_helpers import Linearizer
@@ -1089,6 +1090,20 @@ async def on_invite_request(
10891090
if event.state_key == self._server_notices_mxid:
10901091
raise SynapseError(HTTPStatus.FORBIDDEN, "Cannot invite this user")
10911092

1093+
# check the invitee's configuration and apply rules
1094+
invite_config = await self.store.get_invite_config_for_user(event.state_key)
1095+
rule = invite_config.get_invite_rule(event.sender)
1096+
if rule == InviteRule.BLOCK:
1097+
logger.info(
1098+
f"Automatically rejecting invite from {event.sender} due to the invite filtering rules of {event.state_key}"
1099+
)
1100+
raise SynapseError(
1101+
403,
1102+
"You are not permitted to invite this user.",
1103+
errcode=Codes.INVITE_BLOCKED,
1104+
)
1105+
# InviteRule.IGNORE is handled at the sync layer
1106+
10921107
# We retrieve the room member handler here as to not cause a cyclic dependency
10931108
member_handler = self.hs.get_room_member_handler()
10941109
# We don't rate limit based on room ID, as that should be done by

synapse/handlers/room_member.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from synapse.metrics.background_process_metrics import run_as_background_process
5454
from synapse.replication.http.push import ReplicationCopyPusherRestServlet
5555
from synapse.storage.databases.main.state_deltas import StateDelta
56+
from synapse.storage.invite_rule import InviteRule
5657
from synapse.types import (
5758
JsonDict,
5859
Requester,
@@ -915,6 +916,21 @@ async def update_membership_locked(
915916
additional_fields=block_invite_result[1],
916917
)
917918

919+
# check the invitee's configuration and apply rules. Admins on the server can bypass.
920+
if not is_requester_admin:
921+
invite_config = await self.store.get_invite_config_for_user(target_id)
922+
rule = invite_config.get_invite_rule(requester.user.to_string())
923+
if rule == InviteRule.BLOCK:
924+
logger.info(
925+
f"Automatically rejecting invite from {target_id} due to the the invite filtering rules of {requester.user}"
926+
)
927+
raise SynapseError(
928+
403,
929+
"You are not permitted to invite this user.",
930+
errcode=Codes.INVITE_BLOCKED,
931+
)
932+
# InviteRule.IGNORE is handled at the sync layer.
933+
918934
# An empty prev_events list is allowed as long as the auth_event_ids are present
919935
if prev_event_ids is not None:
920936
return await self._local_membership_update(

synapse/handlers/sliding_sync/room_lists.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
Sentinel as StateSentinel,
5050
)
5151
from synapse.storage.databases.main.stream import CurrentStateDeltaMembership
52+
from synapse.storage.invite_rule import InviteRule
5253
from synapse.storage.roommember import (
5354
RoomsForUser,
5455
RoomsForUserSlidingSync,
@@ -278,6 +279,7 @@ async def _compute_interested_rooms_new_tables(
278279

279280
# Remove invites from ignored users
280281
ignored_users = await self.store.ignored_users(user_id)
282+
invite_config = await self.store.get_invite_config_for_user(user_id)
281283
if ignored_users:
282284
# FIXME: It would be nice to avoid this copy but since
283285
# `get_sliding_sync_rooms_for_user_from_membership_snapshots` is cached, it
@@ -292,7 +294,14 @@ async def _compute_interested_rooms_new_tables(
292294
room_for_user_sliding_sync = room_membership_for_user_map[room_id]
293295
if (
294296
room_for_user_sliding_sync.membership == Membership.INVITE
295-
and room_for_user_sliding_sync.sender in ignored_users
297+
and room_for_user_sliding_sync.sender
298+
and (
299+
room_for_user_sliding_sync.sender in ignored_users
300+
or invite_config.get_invite_rule(
301+
room_for_user_sliding_sync.sender
302+
)
303+
== InviteRule.IGNORE
304+
)
296305
):
297306
room_membership_for_user_map.pop(room_id, None)
298307

synapse/handlers/sync.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from synapse.storage.databases.main.event_push_actions import RoomNotifCounts
6767
from synapse.storage.databases.main.roommember import extract_heroes_from_room_summary
6868
from synapse.storage.databases.main.stream import PaginateFunction
69+
from synapse.storage.invite_rule import InviteRule
6970
from synapse.storage.roommember import MemberSummary
7071
from synapse.types import (
7172
DeviceListUpdates,
@@ -2549,6 +2550,7 @@ async def _get_room_changes_for_incremental_sync(
25492550
room_entries: List[RoomSyncResultBuilder] = []
25502551
invited: List[InvitedSyncResult] = []
25512552
knocked: List[KnockedSyncResult] = []
2553+
invite_config = await self.store.get_invite_config_for_user(user_id)
25522554
for room_id, events in mem_change_events_by_room_id.items():
25532555
# The body of this loop will add this room to at least one of the five lists
25542556
# above. Things get messy if you've e.g. joined, left, joined then left the
@@ -2631,7 +2633,11 @@ async def _get_room_changes_for_incremental_sync(
26312633
# Only bother if we're still currently invited
26322634
should_invite = last_non_join.membership == Membership.INVITE
26332635
if should_invite:
2634-
if last_non_join.sender not in ignored_users:
2636+
if (
2637+
last_non_join.sender not in ignored_users
2638+
and invite_config.get_invite_rule(last_non_join.sender)
2639+
!= InviteRule.IGNORE
2640+
):
26352641
invite_room_sync = InvitedSyncResult(room_id, invite=last_non_join)
26362642
if invite_room_sync:
26372643
invited.append(invite_room_sync)
@@ -2786,6 +2792,7 @@ async def _get_room_changes_for_initial_sync(
27862792
membership_list=Membership.LIST,
27872793
excluded_rooms=sync_result_builder.excluded_room_ids,
27882794
)
2795+
invite_config = await self.store.get_invite_config_for_user(user_id)
27892796

27902797
room_entries = []
27912798
invited = []
@@ -2811,6 +2818,8 @@ async def _get_room_changes_for_initial_sync(
28112818
elif event.membership == Membership.INVITE:
28122819
if event.sender in ignored_users:
28132820
continue
2821+
if invite_config.get_invite_rule(event.sender) == InviteRule.IGNORE:
2822+
continue
28142823
invite = await self.store.get_event(event.event_id)
28152824
invited.append(InvitedSyncResult(room_id=event.room_id, invite=invite))
28162825
elif event.membership == Membership.KNOCK:

0 commit comments

Comments
 (0)