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

Commit 68a6717

Browse files
authored
Reject mentions on the C-S API which are invalid. (#15311)
Invalid mentions data received over the Client-Server API should be rejected with a 400 error. This will hopefully stop clients from sending invalid data, although does not help with data received over federation.
1 parent e6af49f commit 68a6717

File tree

4 files changed

+105
-54
lines changed

4 files changed

+105
-54
lines changed

changelog.d/15311.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reject events with an invalid "mentions" property pert [MSC3952](https://github.com/matrix-org/matrix-spec-proposals/pull/3952).

synapse/events/validator.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import collections.abc
15-
from typing import Iterable, Type, Union, cast
15+
from typing import Iterable, List, Type, Union, cast
1616

1717
import jsonschema
18+
from pydantic import Field, StrictBool, StrictStr
1819

19-
from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes, Membership
20+
from synapse.api.constants import (
21+
MAX_ALIAS_LENGTH,
22+
EventContentFields,
23+
EventTypes,
24+
Membership,
25+
)
2026
from synapse.api.errors import Codes, SynapseError
2127
from synapse.api.room_versions import EventFormatVersions
2228
from synapse.config.homeserver import HomeServerConfig
@@ -28,6 +34,8 @@
2834
validate_canonicaljson,
2935
)
3036
from synapse.federation.federation_server import server_matches_acl_event
37+
from synapse.http.servlet import validate_json_object
38+
from synapse.rest.models import RequestBodyModel
3139
from synapse.types import EventID, JsonDict, RoomID, UserID
3240

3341

@@ -88,27 +96,27 @@ def validate_new(self, event: EventBase, config: HomeServerConfig) -> None:
8896
Codes.INVALID_PARAM,
8997
)
9098

91-
if event.type == EventTypes.Retention:
99+
elif event.type == EventTypes.Retention:
92100
self._validate_retention(event)
93101

94-
if event.type == EventTypes.ServerACL:
102+
elif event.type == EventTypes.ServerACL:
95103
if not server_matches_acl_event(config.server.server_name, event):
96104
raise SynapseError(
97105
400, "Can't create an ACL event that denies the local server"
98106
)
99107

100-
if event.type == EventTypes.PowerLevels:
108+
elif event.type == EventTypes.PowerLevels:
101109
try:
102110
jsonschema.validate(
103111
instance=event.content,
104112
schema=POWER_LEVELS_SCHEMA,
105-
cls=plValidator,
113+
cls=POWER_LEVELS_VALIDATOR,
106114
)
107115
except jsonschema.ValidationError as e:
108116
if e.path:
109117
# example: "users_default": '0' is not of type 'integer'
110118
# cast safety: path entries can be integers, if we fail to validate
111-
# items in an array. However the POWER_LEVELS_SCHEMA doesn't expect
119+
# items in an array. However, the POWER_LEVELS_SCHEMA doesn't expect
112120
# to see any arrays.
113121
message = (
114122
'"' + cast(str, e.path[-1]) + '": ' + e.message # noqa: B306
@@ -125,6 +133,15 @@ def validate_new(self, event: EventBase, config: HomeServerConfig) -> None:
125133
errcode=Codes.BAD_JSON,
126134
)
127135

136+
# If the event contains a mentions key, validate it.
137+
if (
138+
EventContentFields.MSC3952_MENTIONS in event.content
139+
and config.experimental.msc3952_intentional_mentions
140+
):
141+
validate_json_object(
142+
event.content[EventContentFields.MSC3952_MENTIONS], Mentions
143+
)
144+
128145
def _validate_retention(self, event: EventBase) -> None:
129146
"""Checks that an event that defines the retention policy for a room respects the
130147
format enforced by the spec.
@@ -253,10 +270,15 @@ def _ensure_state_event(self, event: Union[EventBase, EventBuilder]) -> None:
253270
}
254271

255272

273+
class Mentions(RequestBodyModel):
274+
user_ids: List[StrictStr] = Field(default_factory=list)
275+
room: StrictBool = False
276+
277+
256278
# This could return something newer than Draft 7, but that's the current "latest"
257279
# validator.
258-
def _create_power_level_validator() -> Type[jsonschema.Draft7Validator]:
259-
validator = jsonschema.validators.validator_for(POWER_LEVELS_SCHEMA)
280+
def _create_validator(schema: JsonDict) -> Type[jsonschema.Draft7Validator]:
281+
validator = jsonschema.validators.validator_for(schema)
260282

261283
# by default jsonschema does not consider a immutabledict to be an object so
262284
# we need to use a custom type checker
@@ -268,4 +290,4 @@ def _create_power_level_validator() -> Type[jsonschema.Draft7Validator]:
268290
return jsonschema.validators.extend(validator, type_checker=type_checker)
269291

270292

271-
plValidator = _create_power_level_validator()
293+
POWER_LEVELS_VALIDATOR = _create_validator(POWER_LEVELS_SCHEMA)

synapse/http/servlet.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -778,17 +778,13 @@ def parse_json_object_from_request(
778778
Model = TypeVar("Model", bound=BaseModel)
779779

780780

781-
def parse_and_validate_json_object_from_request(
782-
request: Request, model_type: Type[Model]
783-
) -> Model:
784-
"""Parse a JSON object from the body of a twisted HTTP request, then deserialise and
785-
validate using the given pydantic model.
781+
def validate_json_object(content: JsonDict, model_type: Type[Model]) -> Model:
782+
"""Validate a deserialized JSON object using the given pydantic model.
786783
787784
Raises:
788785
SynapseError if the request body couldn't be decoded as JSON or
789786
if it wasn't a JSON object.
790787
"""
791-
content = parse_json_object_from_request(request, allow_empty_body=False)
792788
try:
793789
instance = model_type.parse_obj(content)
794790
except ValidationError as e:
@@ -811,6 +807,20 @@ def parse_and_validate_json_object_from_request(
811807
return instance
812808

813809

810+
def parse_and_validate_json_object_from_request(
811+
request: Request, model_type: Type[Model]
812+
) -> Model:
813+
"""Parse a JSON object from the body of a twisted HTTP request, then deserialise and
814+
validate using the given pydantic model.
815+
816+
Raises:
817+
SynapseError if the request body couldn't be decoded as JSON or
818+
if it wasn't a JSON object.
819+
"""
820+
content = parse_json_object_from_request(request, allow_empty_body=False)
821+
return validate_json_object(content, model_type)
822+
823+
814824
def assert_params_in_dict(body: JsonDict, required: Iterable[str]) -> None:
815825
absent = []
816826
for k in required:

tests/push/test_bulk_push_rule_evaluator.py

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -243,22 +243,28 @@ def test_user_mentions(self) -> None:
243243
)
244244

245245
# Non-dict mentions should be ignored.
246-
mentions: Any
247-
for mentions in (None, True, False, 1, "foo", []):
248-
self.assertFalse(
249-
self._create_and_process(
250-
bulk_evaluator, {EventContentFields.MSC3952_MENTIONS: mentions}
246+
#
247+
# Avoid C-S validation as these aren't expected.
248+
with patch(
249+
"synapse.events.validator.EventValidator.validate_new",
250+
new=lambda s, event, config: True,
251+
):
252+
mentions: Any
253+
for mentions in (None, True, False, 1, "foo", []):
254+
self.assertFalse(
255+
self._create_and_process(
256+
bulk_evaluator, {EventContentFields.MSC3952_MENTIONS: mentions}
257+
)
251258
)
252-
)
253259

254-
# A non-list should be ignored.
255-
for mentions in (None, True, False, 1, "foo", {}):
256-
self.assertFalse(
257-
self._create_and_process(
258-
bulk_evaluator,
259-
{EventContentFields.MSC3952_MENTIONS: {"user_ids": mentions}},
260+
# A non-list should be ignored.
261+
for mentions in (None, True, False, 1, "foo", {}):
262+
self.assertFalse(
263+
self._create_and_process(
264+
bulk_evaluator,
265+
{EventContentFields.MSC3952_MENTIONS: {"user_ids": mentions}},
266+
)
260267
)
261-
)
262268

263269
# The Matrix ID appearing anywhere in the list should notify.
264270
self.assertTrue(
@@ -291,26 +297,32 @@ def test_user_mentions(self) -> None:
291297
)
292298

293299
# Invalid entries in the list are ignored.
294-
self.assertFalse(
295-
self._create_and_process(
296-
bulk_evaluator,
297-
{
298-
EventContentFields.MSC3952_MENTIONS: {
299-
"user_ids": [None, True, False, {}, []]
300-
}
301-
},
300+
#
301+
# Avoid C-S validation as these aren't expected.
302+
with patch(
303+
"synapse.events.validator.EventValidator.validate_new",
304+
new=lambda s, event, config: True,
305+
):
306+
self.assertFalse(
307+
self._create_and_process(
308+
bulk_evaluator,
309+
{
310+
EventContentFields.MSC3952_MENTIONS: {
311+
"user_ids": [None, True, False, {}, []]
312+
}
313+
},
314+
)
302315
)
303-
)
304-
self.assertTrue(
305-
self._create_and_process(
306-
bulk_evaluator,
307-
{
308-
EventContentFields.MSC3952_MENTIONS: {
309-
"user_ids": [None, True, False, {}, [], self.alice]
310-
}
311-
},
316+
self.assertTrue(
317+
self._create_and_process(
318+
bulk_evaluator,
319+
{
320+
EventContentFields.MSC3952_MENTIONS: {
321+
"user_ids": [None, True, False, {}, [], self.alice]
322+
}
323+
},
324+
)
312325
)
313-
)
314326

315327
# The legacy push rule should not mention if the mentions field exists.
316328
self.assertFalse(
@@ -351,14 +363,20 @@ def test_room_mentions(self) -> None:
351363
)
352364

353365
# Invalid data should not notify.
354-
mentions: Any
355-
for mentions in (None, False, 1, "foo", [], {}):
356-
self.assertFalse(
357-
self._create_and_process(
358-
bulk_evaluator,
359-
{EventContentFields.MSC3952_MENTIONS: {"room": mentions}},
366+
#
367+
# Avoid C-S validation as these aren't expected.
368+
with patch(
369+
"synapse.events.validator.EventValidator.validate_new",
370+
new=lambda s, event, config: True,
371+
):
372+
mentions: Any
373+
for mentions in (None, False, 1, "foo", [], {}):
374+
self.assertFalse(
375+
self._create_and_process(
376+
bulk_evaluator,
377+
{EventContentFields.MSC3952_MENTIONS: {"room": mentions}},
378+
)
360379
)
361-
)
362380

363381
# The legacy push rule should not mention if the mentions field exists.
364382
self.assertFalse(

0 commit comments

Comments
 (0)