Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Never autojoin deactived & suspended users. #18073

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/18073.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deactivated users will no longer automatically accept an invite when `auto_accept_invites` is enabled.
81 changes: 47 additions & 34 deletions synapse/events/auto_accept_invites.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,49 +66,62 @@ async def on_new_event(self, event: EventBase, *args: Any) -> None:
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

# 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
Expand Down
139 changes: 138 additions & 1 deletion tests/events/test_auto_accept_invites.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
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
Expand Down Expand Up @@ -349,6 +349,127 @@ def test_accept_invite_local_user(
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_from_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_from_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,
)

# Check that the invite receiving user has automatically joined the room when syncing
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_from_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"
)


_request_key = 0

Expand Down Expand Up @@ -647,6 +768,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 = {}
Expand Down
Loading