mirror of
https://github.com/matrix-org/synapse.git
synced 2025-01-12 11:16:44 +00:00
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.
This commit is contained in:
parent
e6af49fbea
commit
68a6717312
1
changelog.d/15311.misc
Normal file
1
changelog.d/15311.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Reject events with an invalid "mentions" property pert [MSC3952](https://github.com/matrix-org/matrix-spec-proposals/pull/3952).
|
@ -12,11 +12,17 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import collections.abc
|
import collections.abc
|
||||||
from typing import Iterable, Type, Union, cast
|
from typing import Iterable, List, Type, Union, cast
|
||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
from pydantic import Field, StrictBool, StrictStr
|
||||||
|
|
||||||
from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes, Membership
|
from synapse.api.constants import (
|
||||||
|
MAX_ALIAS_LENGTH,
|
||||||
|
EventContentFields,
|
||||||
|
EventTypes,
|
||||||
|
Membership,
|
||||||
|
)
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
from synapse.api.room_versions import EventFormatVersions
|
from synapse.api.room_versions import EventFormatVersions
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
@ -28,6 +34,8 @@ from synapse.events.utils import (
|
|||||||
validate_canonicaljson,
|
validate_canonicaljson,
|
||||||
)
|
)
|
||||||
from synapse.federation.federation_server import server_matches_acl_event
|
from synapse.federation.federation_server import server_matches_acl_event
|
||||||
|
from synapse.http.servlet import validate_json_object
|
||||||
|
from synapse.rest.models import RequestBodyModel
|
||||||
from synapse.types import EventID, JsonDict, RoomID, UserID
|
from synapse.types import EventID, JsonDict, RoomID, UserID
|
||||||
|
|
||||||
|
|
||||||
@ -88,27 +96,27 @@ class EventValidator:
|
|||||||
Codes.INVALID_PARAM,
|
Codes.INVALID_PARAM,
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.type == EventTypes.Retention:
|
elif event.type == EventTypes.Retention:
|
||||||
self._validate_retention(event)
|
self._validate_retention(event)
|
||||||
|
|
||||||
if event.type == EventTypes.ServerACL:
|
elif event.type == EventTypes.ServerACL:
|
||||||
if not server_matches_acl_event(config.server.server_name, event):
|
if not server_matches_acl_event(config.server.server_name, event):
|
||||||
raise SynapseError(
|
raise SynapseError(
|
||||||
400, "Can't create an ACL event that denies the local server"
|
400, "Can't create an ACL event that denies the local server"
|
||||||
)
|
)
|
||||||
|
|
||||||
if event.type == EventTypes.PowerLevels:
|
elif event.type == EventTypes.PowerLevels:
|
||||||
try:
|
try:
|
||||||
jsonschema.validate(
|
jsonschema.validate(
|
||||||
instance=event.content,
|
instance=event.content,
|
||||||
schema=POWER_LEVELS_SCHEMA,
|
schema=POWER_LEVELS_SCHEMA,
|
||||||
cls=plValidator,
|
cls=POWER_LEVELS_VALIDATOR,
|
||||||
)
|
)
|
||||||
except jsonschema.ValidationError as e:
|
except jsonschema.ValidationError as e:
|
||||||
if e.path:
|
if e.path:
|
||||||
# example: "users_default": '0' is not of type 'integer'
|
# example: "users_default": '0' is not of type 'integer'
|
||||||
# cast safety: path entries can be integers, if we fail to validate
|
# cast safety: path entries can be integers, if we fail to validate
|
||||||
# items in an array. However the POWER_LEVELS_SCHEMA doesn't expect
|
# items in an array. However, the POWER_LEVELS_SCHEMA doesn't expect
|
||||||
# to see any arrays.
|
# to see any arrays.
|
||||||
message = (
|
message = (
|
||||||
'"' + cast(str, e.path[-1]) + '": ' + e.message # noqa: B306
|
'"' + cast(str, e.path[-1]) + '": ' + e.message # noqa: B306
|
||||||
@ -125,6 +133,15 @@ class EventValidator:
|
|||||||
errcode=Codes.BAD_JSON,
|
errcode=Codes.BAD_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# If the event contains a mentions key, validate it.
|
||||||
|
if (
|
||||||
|
EventContentFields.MSC3952_MENTIONS in event.content
|
||||||
|
and config.experimental.msc3952_intentional_mentions
|
||||||
|
):
|
||||||
|
validate_json_object(
|
||||||
|
event.content[EventContentFields.MSC3952_MENTIONS], Mentions
|
||||||
|
)
|
||||||
|
|
||||||
def _validate_retention(self, event: EventBase) -> None:
|
def _validate_retention(self, event: EventBase) -> None:
|
||||||
"""Checks that an event that defines the retention policy for a room respects the
|
"""Checks that an event that defines the retention policy for a room respects the
|
||||||
format enforced by the spec.
|
format enforced by the spec.
|
||||||
@ -253,10 +270,15 @@ POWER_LEVELS_SCHEMA = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Mentions(RequestBodyModel):
|
||||||
|
user_ids: List[StrictStr] = Field(default_factory=list)
|
||||||
|
room: StrictBool = False
|
||||||
|
|
||||||
|
|
||||||
# This could return something newer than Draft 7, but that's the current "latest"
|
# This could return something newer than Draft 7, but that's the current "latest"
|
||||||
# validator.
|
# validator.
|
||||||
def _create_power_level_validator() -> Type[jsonschema.Draft7Validator]:
|
def _create_validator(schema: JsonDict) -> Type[jsonschema.Draft7Validator]:
|
||||||
validator = jsonschema.validators.validator_for(POWER_LEVELS_SCHEMA)
|
validator = jsonschema.validators.validator_for(schema)
|
||||||
|
|
||||||
# by default jsonschema does not consider a immutabledict to be an object so
|
# by default jsonschema does not consider a immutabledict to be an object so
|
||||||
# we need to use a custom type checker
|
# we need to use a custom type checker
|
||||||
@ -268,4 +290,4 @@ def _create_power_level_validator() -> Type[jsonschema.Draft7Validator]:
|
|||||||
return jsonschema.validators.extend(validator, type_checker=type_checker)
|
return jsonschema.validators.extend(validator, type_checker=type_checker)
|
||||||
|
|
||||||
|
|
||||||
plValidator = _create_power_level_validator()
|
POWER_LEVELS_VALIDATOR = _create_validator(POWER_LEVELS_SCHEMA)
|
||||||
|
@ -778,17 +778,13 @@ def parse_json_object_from_request(
|
|||||||
Model = TypeVar("Model", bound=BaseModel)
|
Model = TypeVar("Model", bound=BaseModel)
|
||||||
|
|
||||||
|
|
||||||
def parse_and_validate_json_object_from_request(
|
def validate_json_object(content: JsonDict, model_type: Type[Model]) -> Model:
|
||||||
request: Request, model_type: Type[Model]
|
"""Validate a deserialized JSON object using the given pydantic model.
|
||||||
) -> Model:
|
|
||||||
"""Parse a JSON object from the body of a twisted HTTP request, then deserialise and
|
|
||||||
validate using the given pydantic model.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SynapseError if the request body couldn't be decoded as JSON or
|
SynapseError if the request body couldn't be decoded as JSON or
|
||||||
if it wasn't a JSON object.
|
if it wasn't a JSON object.
|
||||||
"""
|
"""
|
||||||
content = parse_json_object_from_request(request, allow_empty_body=False)
|
|
||||||
try:
|
try:
|
||||||
instance = model_type.parse_obj(content)
|
instance = model_type.parse_obj(content)
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
@ -811,6 +807,20 @@ def parse_and_validate_json_object_from_request(
|
|||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
def parse_and_validate_json_object_from_request(
|
||||||
|
request: Request, model_type: Type[Model]
|
||||||
|
) -> Model:
|
||||||
|
"""Parse a JSON object from the body of a twisted HTTP request, then deserialise and
|
||||||
|
validate using the given pydantic model.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
SynapseError if the request body couldn't be decoded as JSON or
|
||||||
|
if it wasn't a JSON object.
|
||||||
|
"""
|
||||||
|
content = parse_json_object_from_request(request, allow_empty_body=False)
|
||||||
|
return validate_json_object(content, model_type)
|
||||||
|
|
||||||
|
|
||||||
def assert_params_in_dict(body: JsonDict, required: Iterable[str]) -> None:
|
def assert_params_in_dict(body: JsonDict, required: Iterable[str]) -> None:
|
||||||
absent = []
|
absent = []
|
||||||
for k in required:
|
for k in required:
|
||||||
|
@ -243,6 +243,12 @@ class TestBulkPushRuleEvaluator(HomeserverTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Non-dict mentions should be ignored.
|
# Non-dict mentions should be ignored.
|
||||||
|
#
|
||||||
|
# Avoid C-S validation as these aren't expected.
|
||||||
|
with patch(
|
||||||
|
"synapse.events.validator.EventValidator.validate_new",
|
||||||
|
new=lambda s, event, config: True,
|
||||||
|
):
|
||||||
mentions: Any
|
mentions: Any
|
||||||
for mentions in (None, True, False, 1, "foo", []):
|
for mentions in (None, True, False, 1, "foo", []):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
@ -291,6 +297,12 @@ class TestBulkPushRuleEvaluator(HomeserverTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Invalid entries in the list are ignored.
|
# Invalid entries in the list are ignored.
|
||||||
|
#
|
||||||
|
# Avoid C-S validation as these aren't expected.
|
||||||
|
with patch(
|
||||||
|
"synapse.events.validator.EventValidator.validate_new",
|
||||||
|
new=lambda s, event, config: True,
|
||||||
|
):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
self._create_and_process(
|
self._create_and_process(
|
||||||
bulk_evaluator,
|
bulk_evaluator,
|
||||||
@ -351,6 +363,12 @@ class TestBulkPushRuleEvaluator(HomeserverTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Invalid data should not notify.
|
# Invalid data should not notify.
|
||||||
|
#
|
||||||
|
# Avoid C-S validation as these aren't expected.
|
||||||
|
with patch(
|
||||||
|
"synapse.events.validator.EventValidator.validate_new",
|
||||||
|
new=lambda s, event, config: True,
|
||||||
|
):
|
||||||
mentions: Any
|
mentions: Any
|
||||||
for mentions in (None, False, 1, "foo", [], {}):
|
for mentions in (None, False, 1, "foo", [], {}):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
|
Loading…
Reference in New Issue
Block a user