diff --git a/changelog.d/10237.misc b/changelog.d/10237.misc
new file mode 100644
index 0000000000..d76c119a41
--- /dev/null
+++ b/changelog.d/10237.misc
@@ -0,0 +1 @@
+Improve the reliability of auto-joining remote rooms.
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index ca1ed6a5c0..4b4b579741 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -386,11 +386,32 @@ class RegistrationHandler(BaseHandler):
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.
@@ -448,22 +469,32 @@ class RegistrationHandler(BaseHandler):
)
# 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:
diff --git a/tests/handlers/test_register.py b/tests/handlers/test_register.py
index a9fd3036dc..c901003225 100644
--- a/tests/handlers/test_register.py
+++ b/tests/handlers/test_register.py
@@ -18,7 +18,7 @@ from synapse.api.auth import Auth
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
@@ -643,3 +643,50 @@ class RegistrationTestCase(unittest.HomeserverTestCase):
)
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"])
|