summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/61.misc1
-rw-r--r--synapse/third_party_rules/access_rules.py68
-rw-r--r--tests/rest/client/test_room_access_rules.py57
3 files changed, 119 insertions, 7 deletions
diff --git a/changelog.d/61.misc b/changelog.d/61.misc
new file mode 100644

index 0000000000..0c3ba98628 --- /dev/null +++ b/changelog.d/61.misc
@@ -0,0 +1 @@ +Change the minimum power levels for invites and other state events in new rooms. \ No newline at end of file diff --git a/synapse/third_party_rules/access_rules.py b/synapse/third_party_rules/access_rules.py
index d4e57316de..bf3b9adb56 100644 --- a/synapse/third_party_rules/access_rules.py +++ b/synapse/third_party_rules/access_rules.py
@@ -110,6 +110,10 @@ class RoomAccessRules(object): If yes, make sure the event is correct. Otherwise, append an event with the default rule to the initial state. + Checks if a m.rooms.power_levels event is being set during room creation. + If yes, make sure the event is allowed. Otherwise, set power_level_content_override + in the config dict to our modified version of the default room power levels. + Args: requester: The user who is making the createRoom request. config: The createRoom config dict provided by the user. @@ -187,6 +191,10 @@ class RoomAccessRules(object): if not allowed: raise SynapseError(400, "Invalid power levels content override") + use_default_power_levels = True + if config.get("power_level_content_override"): + use_default_power_levels = False + # Second loop for events we need to know the current rule to process. for event in config.get("initial_state", []): if event["type"] == EventTypes.PowerLevels: @@ -196,8 +204,44 @@ class RoomAccessRules(object): if not allowed: raise SynapseError(400, "Invalid power levels content") + use_default_power_levels = False + + # If power levels were not overridden by the user, override with DINUM's preferred + # defaults instead + if use_default_power_levels: + config["power_level_content_override"] = self._get_default_power_levels( + requester.user.to_string() + ) + return True + # If power levels are not overridden by the user during room creation, the following + # rules are used instead. Changes from Synapse's default power levels are noted. + # + # The same power levels are currently applied regardless of room preset. + @staticmethod + def _get_default_power_levels(user_id: str) -> Dict: + return { + "users": {user_id: 100}, + "users_default": 0, + "events": { + EventTypes.Name: 50, + EventTypes.PowerLevels: 100, + EventTypes.RoomHistoryVisibility: 100, + EventTypes.CanonicalAlias: 50, + EventTypes.RoomAvatar: 50, + EventTypes.Tombstone: 100, + EventTypes.ServerACL: 100, + EventTypes.RoomEncryption: 100, + }, + "events_default": 0, + "state_default": 100, # Admins should be the only ones to perform other tasks + "ban": 50, + "kick": 50, + "redact": 50, + "invite": 50, # All rooms should require mod to invite, even private + } + @defer.inlineCallbacks def check_threepid_can_be_invited( self, medium: str, address: str, state_events: StateMap[EventBase], @@ -272,7 +316,9 @@ class RoomAccessRules(object): rule = self._get_rule_from_state(state_events) if event.type == EventTypes.PowerLevels: - return self._is_power_level_content_allowed(event.content, rule) + return self._is_power_level_content_allowed( + event.content, rule, on_room_creation=False + ) if event.type == EventTypes.Member or event.type == EventTypes.ThirdPartyInvite: return self._on_membership_or_invite(event, rule, state_events) @@ -468,7 +514,9 @@ class RoomAccessRules(object): return True - def _is_power_level_content_allowed(self, content: Dict, access_rule: str) -> bool: + def _is_power_level_content_allowed( + self, content: Dict, access_rule: str, on_room_creation: bool = True + ) -> bool: """Check if a given power levels event is permitted under the given access rule. It shouldn't be allowed if it either changes the default PL to a non-0 value or @@ -478,10 +526,26 @@ class RoomAccessRules(object): Args: content: The content of the m.room.power_levels event to check. access_rule: The access rule in place in this room. + on_room_creation: True if this call is happening during a room's + creation, False otherwise. Returns: Whether the content of the power levels event is valid. """ + # Only enforce these rules during room creation + # + # We want to allow admins to modify or fix the power levels in a room if they + # have a special circumstance, but still want to encourage a certain pattern during + # room creation. + if on_room_creation: + # If invite requirements are <PL50 + if content.get("invite", 50) < 50: + return False + + # If "other" state requirements are <PL100 + if content.get("state_default", 100) < 100: + return False + # Check if we need to apply the restrictions with the current rule. if access_rule not in RULES_WITH_RESTRICTED_POWER_LEVELS: return True diff --git a/tests/rest/client/test_room_access_rules.py b/tests/rest/client/test_room_access_rules.py
index 42580c0f05..fbb482f05b 100644 --- a/tests/rest/client/test_room_access_rules.py +++ b/tests/rest/client/test_room_access_rules.py
@@ -23,7 +23,11 @@ from twisted.internet import defer from synapse.api.constants import EventTypes, JoinRules, RoomCreationPreset from synapse.rest import admin from synapse.rest.client.v1 import login, room -from synapse.third_party_rules.access_rules import ACCESS_RULES_TYPE, AccessRules +from synapse.third_party_rules.access_rules import ( + ACCESS_RULES_TYPE, + AccessRules, + RoomAccessRules, +) from tests import unittest @@ -149,6 +153,52 @@ class RoomAccessTestCase(unittest.HomeserverTestCase): """ self.create_room(direct=True, rule=AccessRules.RESTRICTED, expected_code=400) + def test_create_room_default_power_level_rules(self): + """Tests that a room created with no power level overrides instead uses the dinum + defaults + """ + room_id = self.create_room(direct=True, rule=AccessRules.DIRECT) + power_levels = self.helper.get_state(room_id, "m.room.power_levels", self.tok) + + # Inviting another user should require PL50, even in private rooms + self.assertEqual(power_levels["invite"], 50) + # Sending arbitrary state events should require PL100 + self.assertEqual(power_levels["state_default"], 100) + + def test_create_room_fails_on_incorrect_power_level_rules(self): + """Tests that a room created with power levels lower than that required are rejected""" + modified_power_levels = RoomAccessRules._get_default_power_levels(self.user_id) + modified_power_levels["invite"] = 0 + modified_power_levels["state_default"] = 50 + + self.create_room( + direct=True, + rule=AccessRules.DIRECT, + initial_state=[ + {"type": "m.room.power_levels", "content": modified_power_levels} + ], + expected_code=400, + ) + + def test_existing_room_can_change_power_levels(self): + """Tests that a room created with default power levels can have their power levels + dropped after room creation + """ + # Creates a room with the default power levels + room_id = self.create_room( + direct=True, rule=AccessRules.DIRECT, expected_code=200, + ) + + # Attempt to drop invite and state_default power levels after the fact + room_power_levels = self.helper.get_state( + room_id, "m.room.power_levels", self.tok + ) + room_power_levels["invite"] = 0 + room_power_levels["state_default"] = 50 + self.helper.send_state( + room_id, "m.room.power_levels", room_power_levels, self.tok + ) + def test_public_room(self): """Tests that it's not possible to have a room with the public join rule and an access rule that's not restricted. @@ -642,10 +692,7 @@ class RoomAccessTestCase(unittest.HomeserverTestCase): content["initial_state"] += initial_state request, channel = self.make_request( - "POST", - "/_matrix/client/r0/createRoom", - json.dumps(content), - access_token=self.tok, + "POST", "/_matrix/client/r0/createRoom", content, access_token=self.tok, ) self.render(request)