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

Check required power levels earlier in createRoom handler. #15695

Merged
merged 4 commits into from
Jun 7, 2023
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/15695.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Check permissions for enabling encryption earlier during room creation to avoid creating broken rooms.
76 changes: 62 additions & 14 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,8 @@ async def create_room(
visibility = config.get("visibility", "private")
is_public = visibility == "public"

self._validate_room_config(config, visibility)

room_id = await self._generate_and_create_room_id(
creator_id=user_id,
is_public=is_public,
Expand Down Expand Up @@ -1111,20 +1113,7 @@ async def create_event(

return new_event, new_unpersisted_context

visibility = room_config.get("visibility", "private")
preset_config = room_config.get(
"preset",
RoomCreationPreset.PRIVATE_CHAT
if visibility == "private"
else RoomCreationPreset.PUBLIC_CHAT,
)

try:
config = self._presets_dict[preset_config]
except KeyError:
raise SynapseError(
400, f"'{preset_config}' is not a valid preset", errcode=Codes.BAD_JSON
)
preset_config, config = self._room_preset_config(room_config)

# MSC2175 removes the creator field from the create event.
if not room_version.msc2175_implicit_room_creator:
Expand Down Expand Up @@ -1306,6 +1295,65 @@ async def create_event(
assert last_event.internal_metadata.stream_ordering is not None
return last_event.internal_metadata.stream_ordering, last_event.event_id, depth

def _validate_room_config(
self,
config: JsonDict,
visibility: str,
) -> None:
"""Checks configuration parameters for a /createRoom request.

If validation detects invalid parameters an exception may be raised to
cause room creation to be aborted and an error response to be returned
to the client.

Args:
config: A dict of configuration options. Originally from the body of
the /createRoom request
visibility: One of "public" or "private"
"""

# Validate the requested preset, raise a 400 error if not valid
preset_name, preset_config = self._room_preset_config(config)

# If the user is trying to create an encrypted room and this is forbidden
# by the configured default_power_level_content_override, then reject the
# request before the room is created.
raw_initial_state = config.get("initial_state", [])
room_encryption_event = any(
s.get("type", "") == EventTypes.RoomEncryption for s in raw_initial_state
)

if preset_config["encrypted"] or room_encryption_event:
if self._default_power_level_content_override:
override = self._default_power_level_content_override.get(preset_name)
if override is not None:
event_levels = override.get("events", {})
room_admin_level = event_levels.get(EventTypes.PowerLevels, 100)
encryption_level = event_levels.get(EventTypes.RoomEncryption, 100)
if encryption_level > room_admin_level:
raise SynapseError(
403,
f"You cannot create an encrypted room. user_level ({room_admin_level}) < send_level ({encryption_level})",
)

def _room_preset_config(self, room_config: JsonDict) -> Tuple[str, dict]:
# The spec says rooms should default to private visibility if
# `visibility` is not specified.
visibility = room_config.get("visibility", "private")
preset_name = room_config.get(
"preset",
RoomCreationPreset.PRIVATE_CHAT
if visibility == "private"
else RoomCreationPreset.PUBLIC_CHAT,
)
try:
preset_config = self._presets_dict[preset_name]
except KeyError:
raise SynapseError(
400, f"'{preset_name}' is not a valid preset", errcode=Codes.BAD_JSON
)
return preset_name, preset_config

def _generate_room_id(self) -> str:
"""Generates a random room ID.

Expand Down
37 changes: 37 additions & 0 deletions tests/rest/client/test_rooms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1941,6 +1941,43 @@ def test_config_override_applies_only_to_specific_preset(self) -> None:
channel.json_body["error"],
)

@unittest.override_config(
{
"default_power_level_content_override": {
"private_chat": {
"events": {
"m.room.avatar": 50,
"m.room.canonical_alias": 50,
"m.room.encryption": 999,
"m.room.history_visibility": 100,
"m.room.name": 50,
"m.room.power_levels": 100,
"m.room.server_acl": 100,
"m.room.tombstone": 100,
},
"events_default": 0,
},
}
},
)
def test_config_override_blocks_encrypted_room(self) -> None:
# Given the server has config for private_chats,

# When I attempt to create an encrypted private_chat room
channel = self.make_request(
"POST",
"/createRoom",
'{"creation_content": {"m.federate": false},"name": "Secret Private Room","preset": "private_chat","initial_state": [{"type": "m.room.encryption","state_key": "","content": {"algorithm": "m.megolm.v1.aes-sha2"}}]}',
)

# Then I am not allowed because the required power level is unattainable
self.assertEqual(HTTPStatus.FORBIDDEN, channel.code, msg=channel.result["body"])
self.assertEqual(
"You cannot create an encrypted room. "
+ "user_level (100) < send_level (999)",
channel.json_body["error"],
)


class RoomInitialSyncTestCase(RoomBase):
"""Tests /rooms/$room_id/initialSync."""
Expand Down