diff --git a/changelog.d/18073.bugfix b/changelog.d/18073.bugfix
new file mode 100644
index 0000000000..eeb56a7a61
--- /dev/null
+++ b/changelog.d/18073.bugfix
@@ -0,0 +1 @@
+Deactivated users will no longer automatically accept an invite when `auto_accept_invites` is enabled.
\ No newline at end of file
diff --git a/synapse/events/auto_accept_invites.py b/synapse/events/auto_accept_invites.py
index d88ec51d9d..4295107c47 100644
--- a/synapse/events/auto_accept_invites.py
+++ b/synapse/events/auto_accept_invites.py
@@ -66,49 +66,66 @@ class InviteAutoAccepter:
event: The incoming event.
"""
# Check if the event is an invite for a local user.
- is_invite_for_local_user = (
- event.type == EventTypes.Member
- and event.is_state()
- and event.membership == Membership.INVITE
- and self._api.is_mine(event.state_key)
- )
+ if (
+ event.type != EventTypes.Member
+ or event.is_state() is False
+ or event.membership != Membership.INVITE
+ or self._api.is_mine(event.state_key) is False
+ ):
+ return
# Only accept invites for direct messages if the configuration mandates it.
is_direct_message = event.content.get("is_direct", False)
- is_allowed_by_direct_message_rules = (
- not self._config.accept_invites_only_for_direct_messages
- or is_direct_message is True
- )
+ if (
+ self._config.accept_invites_only_for_direct_messages
+ and is_direct_message is False
+ ):
+ return
# Only accept invites from remote users if the configuration mandates it.
is_from_local_user = self._api.is_mine(event.sender)
- is_allowed_by_local_user_rules = (
- not self._config.accept_invites_only_from_local_users
- or is_from_local_user is True
- )
-
if (
- is_invite_for_local_user
- and is_allowed_by_direct_message_rules
- and is_allowed_by_local_user_rules
+ self._config.accept_invites_only_from_local_users
+ and is_from_local_user is False
):
- # Make the user join the room. We run this as a background process to circumvent a race condition
- # that occurs when responding to invites over federation (see https://github.com/matrix-org/synapse-auto-accept-invite/issues/12)
- run_as_background_process(
- "retry_make_join",
- self._retry_make_join,
- event.state_key,
- event.state_key,
- event.room_id,
- "join",
- bg_start_span=False,
- )
+ return
- if is_direct_message:
- # Mark this room as a direct message!
- await self._mark_room_as_direct_message(
- event.state_key, event.sender, event.room_id
- )
+ # Check the user is activated.
+ recipient = await self._api.get_userinfo_by_id(event.state_key)
+
+ # Ignore if the user doesn't exist.
+ if recipient is None:
+ return
+
+ # Never accept invites for deactivated users.
+ if recipient.is_deactivated:
+ return
+
+ # Never accept invites for suspended users.
+ if recipient.suspended:
+ return
+
+ # Never accept invites for locked users.
+ if recipient.locked:
+ return
+
+ # Make the user join the room. We run this as a background process to circumvent a race condition
+ # that occurs when responding to invites over federation (see https://github.com/matrix-org/synapse-auto-accept-invite/issues/12)
+ run_as_background_process(
+ "retry_make_join",
+ self._retry_make_join,
+ event.state_key,
+ event.state_key,
+ event.room_id,
+ "join",
+ bg_start_span=False,
+ )
+
+ if is_direct_message:
+ # Mark this room as a direct message!
+ await self._mark_room_as_direct_message(
+ event.state_key, event.sender, event.room_id
+ )
async def _mark_room_as_direct_message(
self, user_id: str, dm_user_id: str, room_id: str
diff --git a/tests/events/test_auto_accept_invites.py b/tests/events/test_auto_accept_invites.py
index 7fb4d4fa90..d4e87b1b7f 100644
--- a/tests/events/test_auto_accept_invites.py
+++ b/tests/events/test_auto_accept_invites.py
@@ -39,7 +39,7 @@ from synapse.module_api import ModuleApi
from synapse.rest import admin
from synapse.rest.client import login, room
from synapse.server import HomeServer
-from synapse.types import StreamToken, create_requester
+from synapse.types import StreamToken, UserID, UserInfo, create_requester
from synapse.util import Clock
from tests.handlers.test_sync import generate_sync_config
@@ -349,6 +349,169 @@ class AutoAcceptInvitesTestCase(FederatingHomeserverTestCase):
join_updates, _ = sync_join(self, invited_user_id)
self.assertEqual(len(join_updates), 0)
+ @override_config(
+ {
+ "auto_accept_invites": {
+ "enabled": True,
+ },
+ }
+ )
+ async def test_ignore_invite_for_missing_user(self) -> None:
+ """Tests that receiving an invite for a missing user is ignored."""
+ inviting_user_id = self.register_user("inviter", "pass")
+ inviting_user_tok = self.login("inviter", "pass")
+
+ # A local user who receives an invite
+ invited_user_id = "@fake:" + self.hs.config.server.server_name
+
+ # Create a room and send an invite to the other user
+ room_id = self.helper.create_room_as(
+ inviting_user_id,
+ tok=inviting_user_tok,
+ )
+
+ self.helper.invite(
+ room_id,
+ inviting_user_id,
+ invited_user_id,
+ tok=inviting_user_tok,
+ )
+
+ join_updates, _ = sync_join(self, inviting_user_id)
+ # Assert that the last event in the room was not a member event for the target user.
+ self.assertEqual(
+ join_updates[0].timeline.events[-1].content["membership"], "invite"
+ )
+
+ @override_config(
+ {
+ "auto_accept_invites": {
+ "enabled": True,
+ },
+ }
+ )
+ async def test_ignore_invite_for_deactivated_user(self) -> None:
+ """Tests that receiving an invite for a deactivated user is ignored."""
+ inviting_user_id = self.register_user("inviter", "pass", admin=True)
+ inviting_user_tok = self.login("inviter", "pass")
+
+ # A local user who receives an invite
+ invited_user_id = self.register_user("invitee", "pass")
+
+ # Create a room and send an invite to the other user
+ room_id = self.helper.create_room_as(
+ inviting_user_id,
+ tok=inviting_user_tok,
+ )
+
+ channel = self.make_request(
+ "PUT",
+ "/_synapse/admin/v2/users/%s" % invited_user_id,
+ {"deactivated": True},
+ access_token=inviting_user_tok,
+ )
+
+ assert channel.code == 200
+
+ self.helper.invite(
+ room_id,
+ inviting_user_id,
+ invited_user_id,
+ tok=inviting_user_tok,
+ )
+
+ join_updates, b = sync_join(self, inviting_user_id)
+ # Assert that the last event in the room was not a member event for the target user.
+ self.assertEqual(
+ join_updates[0].timeline.events[-1].content["membership"], "invite"
+ )
+
+ @override_config(
+ {
+ "auto_accept_invites": {
+ "enabled": True,
+ },
+ }
+ )
+ async def test_ignore_invite_for_suspended_user(self) -> None:
+ """Tests that receiving an invite for a suspended user is ignored."""
+ inviting_user_id = self.register_user("inviter", "pass", admin=True)
+ inviting_user_tok = self.login("inviter", "pass")
+
+ # A local user who receives an invite
+ invited_user_id = self.register_user("invitee", "pass")
+
+ # Create a room and send an invite to the other user
+ room_id = self.helper.create_room_as(
+ inviting_user_id,
+ tok=inviting_user_tok,
+ )
+
+ channel = self.make_request(
+ "PUT",
+ f"/_synapse/admin/v1/suspend/{invited_user_id}",
+ {"suspend": True},
+ access_token=inviting_user_tok,
+ )
+
+ assert channel.code == 200
+
+ self.helper.invite(
+ room_id,
+ inviting_user_id,
+ invited_user_id,
+ tok=inviting_user_tok,
+ )
+
+ join_updates, b = sync_join(self, inviting_user_id)
+ # Assert that the last event in the room was not a member event for the target user.
+ self.assertEqual(
+ join_updates[0].timeline.events[-1].content["membership"], "invite"
+ )
+
+ @override_config(
+ {
+ "auto_accept_invites": {
+ "enabled": True,
+ },
+ }
+ )
+ async def test_ignore_invite_for_locked_user(self) -> None:
+ """Tests that receiving an invite for a suspended user is ignored."""
+ inviting_user_id = self.register_user("inviter", "pass", admin=True)
+ inviting_user_tok = self.login("inviter", "pass")
+
+ # A local user who receives an invite
+ invited_user_id = self.register_user("invitee", "pass")
+
+ # Create a room and send an invite to the other user
+ room_id = self.helper.create_room_as(
+ inviting_user_id,
+ tok=inviting_user_tok,
+ )
+
+ channel = self.make_request(
+ "PUT",
+ f"/_synapse/admin/v2/users/{invited_user_id}",
+ {"locked": True},
+ access_token=inviting_user_tok,
+ )
+
+ assert channel.code == 200
+
+ self.helper.invite(
+ room_id,
+ inviting_user_id,
+ invited_user_id,
+ tok=inviting_user_tok,
+ )
+
+ join_updates, b = sync_join(self, inviting_user_id)
+ # Assert that the last event in the room was not a member event for the target user.
+ self.assertEqual(
+ join_updates[0].timeline.events[-1].content["membership"], "invite"
+ )
+
_request_key = 0
@@ -647,6 +810,22 @@ def create_module(
module_api.is_mine.side_effect = lambda a: a.split(":")[1] == "test"
module_api.worker_name = worker_name
module_api.sleep.return_value = make_multiple_awaitable(None)
+ module_api.get_userinfo_by_id.return_value = UserInfo(
+ user_id=UserID.from_string("@user:test"),
+ is_admin=False,
+ is_guest=False,
+ consent_server_notice_sent=None,
+ consent_ts=None,
+ consent_version=None,
+ appservice_id=None,
+ creation_ts=0,
+ user_type=None,
+ is_deactivated=False,
+ locked=False,
+ is_shadow_banned=False,
+ approved=True,
+ suspended=False,
+ )
if config_override is None:
config_override = {}
|