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>
This commit is contained in:
Grant McLean 2023-06-08 03:21:25 +12:00 committed by GitHub
parent 8934c11935
commit 5c24d7b9eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 14 deletions

1
changelog.d/15695.bugfix Normal file
View File

@ -0,0 +1 @@
Check permissions for enabling encryption earlier during room creation to avoid creating broken rooms.

View File

@ -872,6 +872,8 @@ class RoomCreationHandler:
visibility = config.get("visibility", "private") visibility = config.get("visibility", "private")
is_public = visibility == "public" is_public = visibility == "public"
self._validate_room_config(config, visibility)
room_id = await self._generate_and_create_room_id( room_id = await self._generate_and_create_room_id(
creator_id=user_id, creator_id=user_id,
is_public=is_public, is_public=is_public,
@ -1111,20 +1113,7 @@ class RoomCreationHandler:
return new_event, new_unpersisted_context return new_event, new_unpersisted_context
visibility = room_config.get("visibility", "private") preset_config, config = self._room_preset_config(room_config)
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
)
# MSC2175 removes the creator field from the create event. # MSC2175 removes the creator field from the create event.
if not room_version.msc2175_implicit_room_creator: if not room_version.msc2175_implicit_room_creator:
@ -1306,6 +1295,65 @@ class RoomCreationHandler:
assert last_event.internal_metadata.stream_ordering is not None assert last_event.internal_metadata.stream_ordering is not None
return last_event.internal_metadata.stream_ordering, last_event.event_id, depth 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: def _generate_room_id(self) -> str:
"""Generates a random room ID. """Generates a random room ID.

View File

@ -1941,6 +1941,43 @@ class RoomPowerLevelOverridesInPracticeTestCase(RoomBase):
channel.json_body["error"], 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): class RoomInitialSyncTestCase(RoomBase):
"""Tests /rooms/$room_id/initialSync.""" """Tests /rooms/$room_id/initialSync."""