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

Commit 5c24d7b

Browse files
authored
Check required power levels earlier in createRoom handler. (#15695)
* Check required power levels earlier in createRoom handler. - If a server was configured to reject the creation of rooms with E2EE enabled (by specifying an unattainably high power level for "m.room.encryption" in default_power_level_content_override), the 403 error was not being triggered until after the room was created and before the "m.room.power_levels" was sent. This allowed a user to access the partially-configured room and complete the setup of E2EE and power levels manually. - This change causes the power level overrides to be checked earlier and the request to be rejected before the user gains access to the room. - A new `_validate_room_config` method is added to contain checks that should be run before a room is created. - The new test case confirms that a user request is rejected by the new validation method. Signed-off-by: Grant McLean <grant@catalyst.net.nz> * Add a changelog file. * Formatting fix for black. * Remove unneeded line from test. --------- Signed-off-by: Grant McLean <grant@catalyst.net.nz>
1 parent 8934c11 commit 5c24d7b

File tree

3 files changed

+100
-14
lines changed

3 files changed

+100
-14
lines changed

changelog.d/15695.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Check permissions for enabling encryption earlier during room creation to avoid creating broken rooms.

synapse/handlers/room.py

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,8 @@ async def create_room(
872872
visibility = config.get("visibility", "private")
873873
is_public = visibility == "public"
874874

875+
self._validate_room_config(config, visibility)
876+
875877
room_id = await self._generate_and_create_room_id(
876878
creator_id=user_id,
877879
is_public=is_public,
@@ -1111,20 +1113,7 @@ async def create_event(
11111113

11121114
return new_event, new_unpersisted_context
11131115

1114-
visibility = room_config.get("visibility", "private")
1115-
preset_config = room_config.get(
1116-
"preset",
1117-
RoomCreationPreset.PRIVATE_CHAT
1118-
if visibility == "private"
1119-
else RoomCreationPreset.PUBLIC_CHAT,
1120-
)
1121-
1122-
try:
1123-
config = self._presets_dict[preset_config]
1124-
except KeyError:
1125-
raise SynapseError(
1126-
400, f"'{preset_config}' is not a valid preset", errcode=Codes.BAD_JSON
1127-
)
1116+
preset_config, config = self._room_preset_config(room_config)
11281117

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

1298+
def _validate_room_config(
1299+
self,
1300+
config: JsonDict,
1301+
visibility: str,
1302+
) -> None:
1303+
"""Checks configuration parameters for a /createRoom request.
1304+
1305+
If validation detects invalid parameters an exception may be raised to
1306+
cause room creation to be aborted and an error response to be returned
1307+
to the client.
1308+
1309+
Args:
1310+
config: A dict of configuration options. Originally from the body of
1311+
the /createRoom request
1312+
visibility: One of "public" or "private"
1313+
"""
1314+
1315+
# Validate the requested preset, raise a 400 error if not valid
1316+
preset_name, preset_config = self._room_preset_config(config)
1317+
1318+
# If the user is trying to create an encrypted room and this is forbidden
1319+
# by the configured default_power_level_content_override, then reject the
1320+
# request before the room is created.
1321+
raw_initial_state = config.get("initial_state", [])
1322+
room_encryption_event = any(
1323+
s.get("type", "") == EventTypes.RoomEncryption for s in raw_initial_state
1324+
)
1325+
1326+
if preset_config["encrypted"] or room_encryption_event:
1327+
if self._default_power_level_content_override:
1328+
override = self._default_power_level_content_override.get(preset_name)
1329+
if override is not None:
1330+
event_levels = override.get("events", {})
1331+
room_admin_level = event_levels.get(EventTypes.PowerLevels, 100)
1332+
encryption_level = event_levels.get(EventTypes.RoomEncryption, 100)
1333+
if encryption_level > room_admin_level:
1334+
raise SynapseError(
1335+
403,
1336+
f"You cannot create an encrypted room. user_level ({room_admin_level}) < send_level ({encryption_level})",
1337+
)
1338+
1339+
def _room_preset_config(self, room_config: JsonDict) -> Tuple[str, dict]:
1340+
# The spec says rooms should default to private visibility if
1341+
# `visibility` is not specified.
1342+
visibility = room_config.get("visibility", "private")
1343+
preset_name = room_config.get(
1344+
"preset",
1345+
RoomCreationPreset.PRIVATE_CHAT
1346+
if visibility == "private"
1347+
else RoomCreationPreset.PUBLIC_CHAT,
1348+
)
1349+
try:
1350+
preset_config = self._presets_dict[preset_name]
1351+
except KeyError:
1352+
raise SynapseError(
1353+
400, f"'{preset_name}' is not a valid preset", errcode=Codes.BAD_JSON
1354+
)
1355+
return preset_name, preset_config
1356+
13091357
def _generate_room_id(self) -> str:
13101358
"""Generates a random room ID.
13111359

tests/rest/client/test_rooms.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,43 @@ def test_config_override_applies_only_to_specific_preset(self) -> None:
19411941
channel.json_body["error"],
19421942
)
19431943

1944+
@unittest.override_config(
1945+
{
1946+
"default_power_level_content_override": {
1947+
"private_chat": {
1948+
"events": {
1949+
"m.room.avatar": 50,
1950+
"m.room.canonical_alias": 50,
1951+
"m.room.encryption": 999,
1952+
"m.room.history_visibility": 100,
1953+
"m.room.name": 50,
1954+
"m.room.power_levels": 100,
1955+
"m.room.server_acl": 100,
1956+
"m.room.tombstone": 100,
1957+
},
1958+
"events_default": 0,
1959+
},
1960+
}
1961+
},
1962+
)
1963+
def test_config_override_blocks_encrypted_room(self) -> None:
1964+
# Given the server has config for private_chats,
1965+
1966+
# When I attempt to create an encrypted private_chat room
1967+
channel = self.make_request(
1968+
"POST",
1969+
"/createRoom",
1970+
'{"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"}}]}',
1971+
)
1972+
1973+
# Then I am not allowed because the required power level is unattainable
1974+
self.assertEqual(HTTPStatus.FORBIDDEN, channel.code, msg=channel.result["body"])
1975+
self.assertEqual(
1976+
"You cannot create an encrypted room. "
1977+
+ "user_level (100) < send_level (999)",
1978+
channel.json_body["error"],
1979+
)
1980+
19441981

19451982
class RoomInitialSyncTestCase(RoomBase):
19461983
"""Tests /rooms/$room_id/initialSync."""

0 commit comments

Comments
 (0)