Skip to content

Commit 784a28b

Browse files
authored
Reject device_keys: null in POST /keys/upload (#19637)
The spec says `device_keys` may be omitted, but not set to `null`. This was temporarily allowed as a workaround for misbehaving clients (see #19023), which have since been fixed. Fixes #19030
1 parent 0e3e947 commit 784a28b

3 files changed

Lines changed: 27 additions & 8 deletions

File tree

changelog.d/19637.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reject `device_keys: null` in the request to [`POST /_matrix/client/v3/keys/upload`](https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3keysupload), as per the spec. This was temporarily allowed as a workaround for misbehaving clients.

synapse/rest/client/keys.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,23 @@ class KeyObject(RequestBodyModel):
159159
device_keys: DeviceKeys | None = None
160160
"""Identity keys for the device. May be absent if no new identity keys are required."""
161161

162+
@field_validator("device_keys", mode="before")
163+
@classmethod
164+
def validate_device_keys_not_null(cls, v: Any) -> Any:
165+
"""Reject explicit `null` for `device_keys` while still allowing
166+
the field to be omitted (in which case the default `None` is used).
167+
168+
The spec says `device_keys` may be omitted, but when present it
169+
must be a `DeviceKeys` object — not `null`.
170+
171+
Pydantic's experimental `Missing` sentinel would be a cleaner way
172+
to express this, but it's not stable yet:
173+
https://docs.pydantic.dev/latest/concepts/experimental/#missing-sentinel
174+
"""
175+
if v is None:
176+
raise ValueError("device_keys must not be null")
177+
return v
178+
162179
fallback_keys: Mapping[StrictStr, StrictStr | KeyObject] | None = None
163180
"""
164181
The public key which should be used if the device's one-time keys are
@@ -241,21 +258,21 @@ async def on_POST(self, request: SynapseRequest) -> tuple[int, JsonDict]:
241258
400, "To upload keys, you must pass device_id when authenticating"
242259
)
243260

244-
if "device_keys" in body and isinstance(body["device_keys"], dict):
261+
if "device_keys" in body:
245262
# Validate the provided `user_id` and `device_id` fields in
246263
# `device_keys` match that of the requesting user. We can't do
247264
# this directly in the pydantic model as we don't have access
248265
# to the requester yet.
249266
#
250267
# TODO: We could use ValidationInfo when we switch to Pydantic v2.
251268
# https://docs.pydantic.dev/latest/concepts/validators/#validation-info
252-
if body["device_keys"].get("user_id") != user_id:
269+
if body["device_keys"]["user_id"] != user_id:
253270
raise SynapseError(
254271
code=HTTPStatus.BAD_REQUEST,
255272
errcode=Codes.BAD_JSON,
256273
msg="Provided `user_id` in `device_keys` does not match that of the authenticated user",
257274
)
258-
if body["device_keys"].get("device_id") != device_id:
275+
if body["device_keys"]["device_id"] != device_id:
259276
raise SynapseError(
260277
code=HTTPStatus.BAD_REQUEST,
261278
errcode=Codes.BAD_JSON,

tests/rest/client/test_keys.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,12 @@ def test_upload_keys_fails_on_invalid_user_id_or_device_id(self) -> None:
160160
channel.result,
161161
)
162162

163-
def test_upload_keys_succeeds_when_fields_are_explicitly_set_to_null(self) -> None:
163+
def test_upload_keys_rejects_device_keys_set_to_null(self) -> None:
164164
"""
165-
This is a regression test for https://github.com/element-hq/synapse/pull/19023.
165+
Test that sending `device_keys: null` is rejected per spec.
166+
The spec says `device_keys` may be omitted, but not set to `null`.
167+
168+
See https://github.com/element-hq/synapse/issues/19030.
166169
"""
167170
device_id = "DEVICE_ID"
168171
self.register_user("alice", "wonderland")
@@ -173,12 +176,10 @@ def test_upload_keys_succeeds_when_fields_are_explicitly_set_to_null(self) -> No
173176
"/_matrix/client/v3/keys/upload",
174177
{
175178
"device_keys": None,
176-
"one_time_keys": None,
177-
"fallback_keys": None,
178179
},
179180
alice_token,
180181
)
181-
self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
182+
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
182183

183184

184185
class KeyQueryTestCase(unittest.HomeserverTestCase):

0 commit comments

Comments
 (0)