Skip to content

Commit ee3b2ac

Browse files
author
David Robertson
authored
Validate device_keys for C-S /keys/query requests (matrix-org#10593)
* Validate device_keys for C-S /keys/query requests Closes matrix-org#10354 A small, not particularly critical fix. I'm interested in seeing if we can find a more systematic approach though. matrix-org#8445 is the place for any discussion.
1 parent e81d620 commit ee3b2ac

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-1
lines changed

changelog.d/10593.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reject Client-Server /keys/query requests which provide device_ids incorrectly.

synapse/api/errors.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ def error_dict(self):
147147
return cs_error(self.msg, self.errcode)
148148

149149

150+
class InvalidAPICallError(SynapseError):
151+
"""You called an existing API endpoint, but fed that endpoint
152+
invalid or incomplete data."""
153+
154+
def __init__(self, msg: str):
155+
super().__init__(HTTPStatus.BAD_REQUEST, msg, Codes.BAD_JSON)
156+
157+
150158
class ProxiedRequestError(SynapseError):
151159
"""An error from a general matrix endpoint, eg. from a proxied Matrix API call.
152160

synapse/rest/client/keys.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
# limitations under the License.
1616

1717
import logging
18+
from typing import Any
1819

19-
from synapse.api.errors import SynapseError
20+
from synapse.api.errors import InvalidAPICallError, SynapseError
2021
from synapse.http.servlet import (
2122
RestServlet,
2223
parse_integer,
@@ -163,6 +164,19 @@ async def on_POST(self, request):
163164
device_id = requester.device_id
164165
timeout = parse_integer(request, "timeout", 10 * 1000)
165166
body = parse_json_object_from_request(request)
167+
168+
device_keys = body.get("device_keys")
169+
if not isinstance(device_keys, dict):
170+
raise InvalidAPICallError("'device_keys' must be a JSON object")
171+
172+
def is_list_of_strings(values: Any) -> bool:
173+
return isinstance(values, list) and all(isinstance(v, str) for v in values)
174+
175+
if any(not is_list_of_strings(keys) for keys in device_keys.values()):
176+
raise InvalidAPICallError(
177+
"'device_keys' values must be a list of strings",
178+
)
179+
166180
result = await self.e2e_keys_handler.query_devices(
167181
body, timeout, user_id, device_id
168182
)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from http import HTTPStatus
2+
3+
from synapse.api.errors import Codes
4+
from synapse.rest import admin
5+
from synapse.rest.client import keys, login
6+
7+
from tests import unittest
8+
9+
10+
class KeyQueryTestCase(unittest.HomeserverTestCase):
11+
servlets = [
12+
keys.register_servlets,
13+
admin.register_servlets_for_client_rest_resource,
14+
login.register_servlets,
15+
]
16+
17+
def test_rejects_device_id_ice_key_outside_of_list(self):
18+
self.register_user("alice", "wonderland")
19+
alice_token = self.login("alice", "wonderland")
20+
bob = self.register_user("bob", "uncle")
21+
channel = self.make_request(
22+
"POST",
23+
"/_matrix/client/r0/keys/query",
24+
{
25+
"device_keys": {
26+
bob: "device_id1",
27+
},
28+
},
29+
alice_token,
30+
)
31+
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
32+
self.assertEqual(
33+
channel.json_body["errcode"],
34+
Codes.BAD_JSON,
35+
channel.result,
36+
)
37+
38+
def test_rejects_device_key_given_as_map_to_bool(self):
39+
self.register_user("alice", "wonderland")
40+
alice_token = self.login("alice", "wonderland")
41+
bob = self.register_user("bob", "uncle")
42+
channel = self.make_request(
43+
"POST",
44+
"/_matrix/client/r0/keys/query",
45+
{
46+
"device_keys": {
47+
bob: {
48+
"device_id1": True,
49+
},
50+
},
51+
},
52+
alice_token,
53+
)
54+
55+
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
56+
self.assertEqual(
57+
channel.json_body["errcode"],
58+
Codes.BAD_JSON,
59+
channel.result,
60+
)
61+
62+
def test_requires_device_key(self):
63+
"""`device_keys` is required. We should complain if it's missing."""
64+
self.register_user("alice", "wonderland")
65+
alice_token = self.login("alice", "wonderland")
66+
channel = self.make_request(
67+
"POST",
68+
"/_matrix/client/r0/keys/query",
69+
{},
70+
alice_token,
71+
)
72+
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
73+
self.assertEqual(
74+
channel.json_body["errcode"],
75+
Codes.BAD_JSON,
76+
channel.result,
77+
)

0 commit comments

Comments
 (0)