Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Option to allow server admins to join complex rooms #7902

Merged
merged 5 commits into from
Jul 28, 2020
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/7902.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add option to allow server admins to join rooms which fail complexity checks. Contributed by @lugino-emeritus.
4 changes: 4 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ limit_remote_rooms:
#
#complexity_error: "This room is too complex."

# allow server admins to join complex rooms. Default is false.
#
#admins_can_join: true

# Whether to require a user to be in the room to add an alias to it.
# Defaults to 'true'.
#
Expand Down
7 changes: 7 additions & 0 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,9 @@ class LimitRemoteRoomsConfig(object):
validator=attr.validators.instance_of(str),
default=ROOM_COMPLEXITY_TOO_GREAT,
)
admins_can_join = attr.ib(
validator=attr.validators.instance_of(bool), default=False
)

self.limit_remote_rooms = LimitRemoteRoomsConfig(
**(config.get("limit_remote_rooms") or {})
Expand Down Expand Up @@ -893,6 +896,10 @@ def generate_config_section(
#
#complexity_error: "This room is too complex."

# allow server admins to join complex rooms. Default is false.
#
#admins_can_join: true

# Whether to require a user to be in the room to add an alias to it.
# Defaults to 'true'.
#
Expand Down
8 changes: 6 additions & 2 deletions synapse/handlers/room_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,11 @@ async def _remote_join(
if len(remote_room_hosts) == 0:
raise SynapseError(404, "No known servers")

if self.hs.config.limit_remote_rooms.enabled:
check_complexity = self.hs.config.limit_remote_rooms.enabled
if check_complexity and self.hs.config.limit_remote_rooms.admins_can_join:
check_complexity = not await self.hs.auth.is_server_admin(user)

if check_complexity:
# Fetch the room complexity
too_complex = await self._is_remote_room_too_complex(
room_id, remote_room_hosts
Expand All @@ -975,7 +979,7 @@ async def _remote_join(

# Check the room we just joined wasn't too large, if we didn't fetch the
# complexity of it before.
if self.hs.config.limit_remote_rooms.enabled:
if check_complexity:
if too_complex is False:
# We checked, and we're under the limit.
return event_id, stream_id
Expand Down
109 changes: 109 additions & 0 deletions tests/federation/test_complexity.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,37 @@ def test_join_too_large(self):
self.assertEqual(f.value.code, 400, f.value)
self.assertEqual(f.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)

def test_join_too_large_admin(self):
# Check whether an admin can join if option "admins_can_join" is undefined,
# this option defaults to false, so the join should fail.

u1 = self.register_user("u1", "pass", admin=True)

handler = self.hs.get_room_member_handler()
fed_transport = self.hs.get_federation_transport_client()

# Mock out some things, because we don't want to test the whole join
fed_transport.client.get_json = Mock(return_value=defer.succeed({"v1": 9999}))
handler.federation_handler.do_invite_join = Mock(
return_value=defer.succeed(("", 1))
)

d = handler._remote_join(
None,
["other.example.com"],
"roomid",
UserID.from_string(u1),
{"membership": "join"},
)

self.pump()

# The request failed with a SynapseError saying the resource limit was
# exceeded.
f = self.get_failure(d, SynapseError)
self.assertEqual(f.value.code, 400, f.value)
self.assertEqual(f.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)

def test_join_too_large_once_joined(self):

u1 = self.register_user("u1", "pass")
Expand Down Expand Up @@ -141,3 +172,81 @@ def test_join_too_large_once_joined(self):
f = self.get_failure(d, SynapseError)
self.assertEqual(f.value.code, 400)
self.assertEqual(f.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)


class RoomComplexityAdminTests(unittest.FederatingHomeserverTestCase):
# Test the behavior of joining rooms which exceed the complexity if option
# limit_remote_rooms.admins_can_join is True.

servlets = [
admin.register_servlets,
room.register_servlets,
login.register_servlets,
]

def default_config(self):
config = super().default_config()
config["limit_remote_rooms"] = {
"enabled": True,
"complexity": 0.05,
"admins_can_join": True,
}
return config

def test_join_too_large_no_admin(self):
# A user which is not an admin should not be able to join a remote room
# which is too complex.

u1 = self.register_user("u1", "pass")

handler = self.hs.get_room_member_handler()
fed_transport = self.hs.get_federation_transport_client()

# Mock out some things, because we don't want to test the whole join
fed_transport.client.get_json = Mock(return_value=defer.succeed({"v1": 9999}))
handler.federation_handler.do_invite_join = Mock(
return_value=defer.succeed(("", 1))
)

d = handler._remote_join(
None,
["other.example.com"],
"roomid",
UserID.from_string(u1),
{"membership": "join"},
)

self.pump()

# The request failed with a SynapseError saying the resource limit was
# exceeded.
f = self.get_failure(d, SynapseError)
self.assertEqual(f.value.code, 400, f.value)
self.assertEqual(f.value.errcode, Codes.RESOURCE_LIMIT_EXCEEDED)

def test_join_too_large_admin(self):
# An admin should be able to join rooms where a complexity check fails.

u1 = self.register_user("u1", "pass", admin=True)

handler = self.hs.get_room_member_handler()
fed_transport = self.hs.get_federation_transport_client()

# Mock out some things, because we don't want to test the whole join
fed_transport.client.get_json = Mock(return_value=defer.succeed({"v1": 9999}))
handler.federation_handler.do_invite_join = Mock(
return_value=defer.succeed(("", 1))
)

d = handler._remote_join(
None,
["other.example.com"],
"roomid",
UserID.from_string(u1),
{"membership": "join"},
)

self.pump()

# The request success since the user is an admin
self.get_success(d)