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

Commit c99b511

Browse files
David Robertsonclokep
andauthored
Fix destination_is errors seen in sentry. (#13041)
* Rename test_fedclient to match its source file * Require at least one destination to be truthy * Explicitly validate user ID in profile endpoint GETs Co-authored-by: Patrick Cloke <[email protected]>
1 parent aef3984 commit c99b511

File tree

7 files changed

+59
-8
lines changed

7 files changed

+59
-8
lines changed

changelog.d/13041.bugfix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a bug introduced in Synapse 1.58 where profile requests for a malformed user ID would ccause an internal error. Synapse now returns 400 Bad Request in this situation.
2+

synapse/http/matrixfederationclient.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -731,8 +731,11 @@ def build_auth_headers(
731731
Returns:
732732
A list of headers to be added as "Authorization:" headers
733733
"""
734-
if destination is None and destination_is is None:
735-
raise ValueError("destination and destination_is cannot both be None!")
734+
if not destination and not destination_is:
735+
raise ValueError(
736+
"At least one of the arguments destination and destination_is "
737+
"must be a nonempty bytestring."
738+
)
736739

737740
request: JsonDict = {
738741
"method": method.decode("ascii"),

synapse/rest/client/profile.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
""" This module contains REST servlets to do with profile: /profile/<paths> """
16-
16+
from http import HTTPStatus
1717
from typing import TYPE_CHECKING, Tuple
1818

1919
from synapse.api.errors import Codes, SynapseError
@@ -45,8 +45,12 @@ async def on_GET(
4545
requester = await self.auth.get_user_by_req(request)
4646
requester_user = requester.user
4747

48-
user = UserID.from_string(user_id)
48+
if not UserID.is_valid(user_id):
49+
raise SynapseError(
50+
HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM
51+
)
4952

53+
user = UserID.from_string(user_id)
5054
await self.profile_handler.check_profile_query_allowed(user, requester_user)
5155

5256
displayname = await self.profile_handler.get_displayname(user)
@@ -98,8 +102,12 @@ async def on_GET(
98102
requester = await self.auth.get_user_by_req(request)
99103
requester_user = requester.user
100104

101-
user = UserID.from_string(user_id)
105+
if not UserID.is_valid(user_id):
106+
raise SynapseError(
107+
HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM
108+
)
102109

110+
user = UserID.from_string(user_id)
103111
await self.profile_handler.check_profile_query_allowed(user, requester_user)
104112

105113
avatar_url = await self.profile_handler.get_avatar_url(user)
@@ -150,8 +158,12 @@ async def on_GET(
150158
requester = await self.auth.get_user_by_req(request)
151159
requester_user = requester.user
152160

153-
user = UserID.from_string(user_id)
161+
if not UserID.is_valid(user_id):
162+
raise SynapseError(
163+
HTTPStatus.BAD_REQUEST, "Invalid user id", Codes.INVALID_PARAM
164+
)
154165

166+
user = UserID.from_string(user_id)
155167
await self.profile_handler.check_profile_query_allowed(user, requester_user)
156168

157169
displayname = await self.profile_handler.get_displayname(user)

synapse/types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@ def from_string(cls: Type[DS], s: str) -> DS:
267267
)
268268

269269
domain = parts[1]
270-
271270
# This code will need changing if we want to support multiple domain
272271
# names on one HS
273272
return cls(localpart=parts[0], domain=domain)
@@ -279,6 +278,8 @@ def to_string(self) -> str:
279278
@classmethod
280279
def is_valid(cls: Type[DS], s: str) -> bool:
281280
"""Parses the input string and attempts to ensure it is valid."""
281+
# TODO: this does not reject an empty localpart or an overly-long string.
282+
# See https://spec.matrix.org/v1.2/appendices/#identifier-grammar
282283
try:
283284
obj = cls.from_string(s)
284285
# Apply additional validation to the domain. This is only done

tests/http/test_fedclient.py renamed to tests/http/test_matrixfederationclient.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,17 @@ def test_too_big(self):
617617
self.assertIsInstance(f.value, RequestSendFailed)
618618

619619
self.assertTrue(transport.disconnecting)
620+
621+
def test_build_auth_headers_rejects_falsey_destinations(self) -> None:
622+
with self.assertRaises(ValueError):
623+
self.cl.build_auth_headers(None, b"GET", b"https://example.com")
624+
with self.assertRaises(ValueError):
625+
self.cl.build_auth_headers(b"", b"GET", b"https://example.com")
626+
with self.assertRaises(ValueError):
627+
self.cl.build_auth_headers(
628+
None, b"GET", b"https://example.com", destination_is=b""
629+
)
630+
with self.assertRaises(ValueError):
631+
self.cl.build_auth_headers(
632+
b"", b"GET", b"https://example.com", destination_is=b""
633+
)

tests/rest/client/test_profile.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# limitations under the License.
1414

1515
"""Tests REST events for /profile paths."""
16+
import urllib.parse
17+
from http import HTTPStatus
1618
from typing import Any, Dict, Optional
1719

1820
from twisted.test.proto_helpers import MemoryReactor
@@ -49,6 +51,12 @@ def test_get_displayname(self) -> None:
4951
res = self._get_displayname()
5052
self.assertEqual(res, "owner")
5153

54+
def test_get_displayname_rejects_bad_username(self) -> None:
55+
channel = self.make_request(
56+
"GET", f"/profile/{urllib.parse.quote('@alice:')}/displayname"
57+
)
58+
self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
59+
5260
def test_set_displayname(self) -> None:
5361
channel = self.make_request(
5462
"PUT",

tests/test_types.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,21 @@ def test_parse(self):
2626
self.assertEqual("test", user.domain)
2727
self.assertEqual(True, self.hs.is_mine(user))
2828

29-
def test_pase_empty(self):
29+
def test_parse_rejects_empty_id(self):
3030
with self.assertRaises(SynapseError):
3131
UserID.from_string("")
3232

33+
def test_parse_rejects_missing_sigil(self):
34+
with self.assertRaises(SynapseError):
35+
UserID.from_string("alice:example.com")
36+
37+
def test_parse_rejects_missing_separator(self):
38+
with self.assertRaises(SynapseError):
39+
UserID.from_string("@alice.example.com")
40+
41+
def test_validation_rejects_missing_domain(self):
42+
self.assertFalse(UserID.is_valid("@alice:"))
43+
3344
def test_build(self):
3445
user = UserID("5678efgh", "my.domain")
3546

0 commit comments

Comments
 (0)