Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Merged
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/10237.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve the reliability of auto-joining remote rooms.
63 changes: 47 additions & 16 deletions synapse/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,11 +386,32 @@ async def _create_and_join_rooms(self, user_id: str) -> None:
room_alias = RoomAlias.from_string(r)

if self.hs.hostname != room_alias.domain:
logger.warning(
"Cannot create room alias %s, "
"it does not match server domain",
# If the alias is remote, try to join the room. This might fail
# because the room might be invite only, but we don't have any local
# user in the room to invite this one with, so at this point that's
# the best we can do.
logger.info(
"Cannot automatically create room with alias %s as it isn't"
" local, trying to join the room instead",
r,
)

(
room,
remote_room_hosts,
) = await room_member_handler.lookup_room_alias(room_alias)
room_id = room.to_string()

await room_member_handler.update_membership(
requester=create_requester(
user_id, authenticated_entity=self._server_name
),
target=UserID.from_string(user_id),
room_id=room_id,
remote_room_hosts=remote_room_hosts,
action="join",
ratelimit=False,
)
else:
# A shallow copy is OK here since the only key that is
# modified is room_alias_name.
Expand Down Expand Up @@ -448,22 +469,32 @@ async def _join_rooms(self, user_id: str) -> None:
)

# Calculate whether the room requires an invite or can be
# joined directly. Note that unless a join rule of public exists,
# it is treated as requiring an invite.
requires_invite = True

state = await self.store.get_filtered_current_state_ids(
room_id, StateFilter.from_types([(EventTypes.JoinRules, "")])
# joined directly. By default, we consider the room as requiring an
# invite if the homeserver is in the room (unless told otherwise by the
# join rules). Otherwise we consider it as being joinable, at the risk of
# failing to join, but in this case there's little more we can do since
# we don't have a local user in the room to craft up an invite with.
requires_invite = await self.store.is_host_joined(
room_id,
self.server_name,
)

event_id = state.get((EventTypes.JoinRules, ""))
if event_id:
join_rules_event = await self.store.get_event(
event_id, allow_none=True
if requires_invite:
# If the server is in the room, check if the room is public.
state = await self.store.get_filtered_current_state_ids(
room_id, StateFilter.from_types([(EventTypes.JoinRules, "")])
)
if join_rules_event:
join_rule = join_rules_event.content.get("join_rule", None)
requires_invite = join_rule and join_rule != JoinRules.PUBLIC

event_id = state.get((EventTypes.JoinRules, ""))
if event_id:
join_rules_event = await self.store.get_event(
event_id, allow_none=True
)
if join_rules_event:
join_rule = join_rules_event.content.get("join_rule", None)
requires_invite = (
join_rule and join_rule != JoinRules.PUBLIC
)

# Send the invite, if necessary.
if requires_invite:
Expand Down
49 changes: 48 additions & 1 deletion tests/handlers/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from synapse.api.constants import UserTypes
from synapse.api.errors import Codes, ResourceLimitError, SynapseError
from synapse.spam_checker_api import RegistrationBehaviour
from synapse.types import RoomAlias, UserID, create_requester
from synapse.types import RoomAlias, RoomID, UserID, create_requester

from tests.test_utils import make_awaitable
from tests.unittest import override_config
Expand Down Expand Up @@ -595,3 +595,50 @@ async def get_or_create_user(
)

return user_id, token


class RemoteAutoJoinTestCase(unittest.HomeserverTestCase):
"""Tests auto-join on remote rooms."""

def make_homeserver(self, reactor, clock):
self.room_id = "!roomid:remotetest"

async def update_membership(*args, **kwargs):
pass

async def lookup_room_alias(*args, **kwargs):
return RoomID.from_string(self.room_id), ["remotetest"]

self.room_member_handler = Mock(spec=["update_membership", "lookup_room_alias"])
self.room_member_handler.update_membership.side_effect = update_membership
self.room_member_handler.lookup_room_alias.side_effect = lookup_room_alias

hs = self.setup_test_homeserver(room_member_handler=self.room_member_handler)
return hs

def prepare(self, reactor, clock, hs):
self.handler = self.hs.get_registration_handler()
self.store = self.hs.get_datastore()

@override_config({"auto_join_rooms": ["#room:remotetest"]})
def test_auto_create_auto_join_remote_room(self):
"""Tests that we don't attempt to create remote rooms, and that we don't attempt
to invite ourselves to rooms we're not in."""

# Register a first user; this should call _create_and_join_rooms
self.get_success(self.handler.register_user(localpart="jeff"))

_, kwargs = self.room_member_handler.update_membership.call_args

self.assertEqual(kwargs["room_id"], self.room_id)
self.assertEqual(kwargs["action"], "join")
self.assertEqual(kwargs["remote_room_hosts"], ["remotetest"])

# Register a second user; this should call _join_rooms
self.get_success(self.handler.register_user(localpart="jeff2"))

_, kwargs = self.room_member_handler.update_membership.call_args

self.assertEqual(kwargs["room_id"], self.room_id)
self.assertEqual(kwargs["action"], "join")
self.assertEqual(kwargs["remote_room_hosts"], ["remotetest"])