Never autojoin deactivated & suspended users. (#18073)

This PR changes the logic so that deactivated users are always ignored.
Suspended users were already effectively ignored as Synapse forbids a
join while suspended.

---------

Co-authored-by: Devon Hudson <devon.dmytro@gmail.com>
This commit is contained in:
Will Hunt 2025-01-28 00:37:24 +00:00 committed by GitHub
parent 8f27b3af07
commit 628351b98d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 232 additions and 35 deletions

1
changelog.d/18073.bugfix Normal file
View File

@ -0,0 +1 @@
Deactivated users will no longer automatically accept an invite when `auto_accept_invites` is enabled.

View File

@ -66,32 +66,49 @@ class InviteAutoAccepter:
event: The incoming event. event: The incoming event.
""" """
# Check if the event is an invite for a local user. # Check if the event is an invite for a local user.
is_invite_for_local_user = ( if (
event.type == EventTypes.Member event.type != EventTypes.Member
and event.is_state() or event.is_state() is False
and event.membership == Membership.INVITE or event.membership != Membership.INVITE
and self._api.is_mine(event.state_key) or self._api.is_mine(event.state_key) is False
) ):
return
# Only accept invites for direct messages if the configuration mandates it. # Only accept invites for direct messages if the configuration mandates it.
is_direct_message = event.content.get("is_direct", False) is_direct_message = event.content.get("is_direct", False)
is_allowed_by_direct_message_rules = ( if (
not self._config.accept_invites_only_for_direct_messages self._config.accept_invites_only_for_direct_messages
or is_direct_message is True and is_direct_message is False
) ):
return
# Only accept invites from remote users if the configuration mandates it. # Only accept invites from remote users if the configuration mandates it.
is_from_local_user = self._api.is_mine(event.sender) 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 ( if (
is_invite_for_local_user self._config.accept_invites_only_from_local_users
and is_allowed_by_direct_message_rules and is_from_local_user is False
and is_allowed_by_local_user_rules
): ):
return
# 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 # 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) # that occurs when responding to invites over federation (see https://github.com/matrix-org/synapse-auto-accept-invite/issues/12)
run_as_background_process( run_as_background_process(

View File

@ -39,7 +39,7 @@ from synapse.module_api import ModuleApi
from synapse.rest import admin from synapse.rest import admin
from synapse.rest.client import login, room from synapse.rest.client import login, room
from synapse.server import HomeServer 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 synapse.util import Clock
from tests.handlers.test_sync import generate_sync_config from tests.handlers.test_sync import generate_sync_config
@ -349,6 +349,169 @@ class AutoAcceptInvitesTestCase(FederatingHomeserverTestCase):
join_updates, _ = sync_join(self, invited_user_id) join_updates, _ = sync_join(self, invited_user_id)
self.assertEqual(len(join_updates), 0) 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 _request_key = 0
@ -647,6 +810,22 @@ def create_module(
module_api.is_mine.side_effect = lambda a: a.split(":")[1] == "test" module_api.is_mine.side_effect = lambda a: a.split(":")[1] == "test"
module_api.worker_name = worker_name module_api.worker_name = worker_name
module_api.sleep.return_value = make_multiple_awaitable(None) 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: if config_override is None:
config_override = {} config_override = {}