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

Commit

Permalink
Validate device_keys for C-S /keys/query requests (#10593)
Browse files Browse the repository at this point in the history
* Validate device_keys for C-S /keys/query requests

Closes #10354

A small, not particularly critical fix. I'm interested in seeing if we
can find a more systematic approach though. #8445 is the place for any discussion.
  • Loading branch information
David Robertson authored Aug 20, 2021
1 parent e81d620 commit ee3b2ac
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog.d/10593.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reject Client-Server /keys/query requests which provide device_ids incorrectly.
8 changes: 8 additions & 0 deletions synapse/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ def error_dict(self):
return cs_error(self.msg, self.errcode)


class InvalidAPICallError(SynapseError):
"""You called an existing API endpoint, but fed that endpoint
invalid or incomplete data."""

def __init__(self, msg: str):
super().__init__(HTTPStatus.BAD_REQUEST, msg, Codes.BAD_JSON)


class ProxiedRequestError(SynapseError):
"""An error from a general matrix endpoint, eg. from a proxied Matrix API call.
Expand Down
16 changes: 15 additions & 1 deletion synapse/rest/client/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
# limitations under the License.

import logging
from typing import Any

from synapse.api.errors import SynapseError
from synapse.api.errors import InvalidAPICallError, SynapseError
from synapse.http.servlet import (
RestServlet,
parse_integer,
Expand Down Expand Up @@ -163,6 +164,19 @@ async def on_POST(self, request):
device_id = requester.device_id
timeout = parse_integer(request, "timeout", 10 * 1000)
body = parse_json_object_from_request(request)

device_keys = body.get("device_keys")
if not isinstance(device_keys, dict):
raise InvalidAPICallError("'device_keys' must be a JSON object")

def is_list_of_strings(values: Any) -> bool:
return isinstance(values, list) and all(isinstance(v, str) for v in values)

if any(not is_list_of_strings(keys) for keys in device_keys.values()):
raise InvalidAPICallError(
"'device_keys' values must be a list of strings",
)

result = await self.e2e_keys_handler.query_devices(
body, timeout, user_id, device_id
)
Expand Down
77 changes: 77 additions & 0 deletions tests/rest/client/v2_alpha/test_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from http import HTTPStatus

from synapse.api.errors import Codes
from synapse.rest import admin
from synapse.rest.client import keys, login

from tests import unittest


class KeyQueryTestCase(unittest.HomeserverTestCase):
servlets = [
keys.register_servlets,
admin.register_servlets_for_client_rest_resource,
login.register_servlets,
]

def test_rejects_device_id_ice_key_outside_of_list(self):
self.register_user("alice", "wonderland")
alice_token = self.login("alice", "wonderland")
bob = self.register_user("bob", "uncle")
channel = self.make_request(
"POST",
"/_matrix/client/r0/keys/query",
{
"device_keys": {
bob: "device_id1",
},
},
alice_token,
)
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
self.assertEqual(
channel.json_body["errcode"],
Codes.BAD_JSON,
channel.result,
)

def test_rejects_device_key_given_as_map_to_bool(self):
self.register_user("alice", "wonderland")
alice_token = self.login("alice", "wonderland")
bob = self.register_user("bob", "uncle")
channel = self.make_request(
"POST",
"/_matrix/client/r0/keys/query",
{
"device_keys": {
bob: {
"device_id1": True,
},
},
},
alice_token,
)

self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
self.assertEqual(
channel.json_body["errcode"],
Codes.BAD_JSON,
channel.result,
)

def test_requires_device_key(self):
"""`device_keys` is required. We should complain if it's missing."""
self.register_user("alice", "wonderland")
alice_token = self.login("alice", "wonderland")
channel = self.make_request(
"POST",
"/_matrix/client/r0/keys/query",
{},
alice_token,
)
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
self.assertEqual(
channel.json_body["errcode"],
Codes.BAD_JSON,
channel.result,
)

0 comments on commit ee3b2ac

Please sign in to comment.