diff --git a/synapse/events/validator.py b/synapse/events/validator.py index 463fff0c8ab7..66341c3372a6 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. import collections.abc -from typing import Iterable, Type, Union, cast +from typing import Iterable, List, Type, Union, cast import jsonschema +from pydantic import StrictBool, StrictStr, Field from synapse.api.constants import ( MAX_ALIAS_LENGTH, @@ -33,6 +34,8 @@ validate_canonicaljson, ) 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 @@ -135,13 +138,9 @@ def validate_new(self, event: EventBase, config: HomeServerConfig) -> None: EventContentFields.MSC3952_MENTIONS in event.content and config.experimental.msc3952_intentional_mentions ): - mentions = event.content[EventContentFields.MSC3952_MENTIONS] - try: - jsonschema.validate( - instance=mentions, schema=MENTIONS_SCHEMA, cls=MENTIONS_VALIDATOR - ) - except jsonschema.ValidationError as e: - raise SynapseError(400, e.message) + validate_json_object( + event.content[EventContentFields.MSC3952_MENTIONS], Mentions + ) def _validate_retention(self, event: EventBase) -> None: """Checks that an event that defines the retention policy for a room respects the @@ -270,20 +269,10 @@ def _ensure_state_event(self, event: Union[EventBase, EventBuilder]) -> None: }, } -MENTIONS_SCHEMA = { - "type": "object", - "properties": { - "user_ids": { - "type": "array", - "items": { - "type": "string", - }, - }, - "room": { - "type": "boolean", - }, - }, -} + +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" @@ -302,4 +291,3 @@ def _create_validator(schema: JsonDict) -> Type[jsonschema.Draft7Validator]: POWER_LEVELS_VALIDATOR = _create_validator(POWER_LEVELS_SCHEMA) -MENTIONS_VALIDATOR = _create_validator(MENTIONS_SCHEMA) diff --git a/synapse/http/servlet.py b/synapse/http/servlet.py index 0070bd2940ea..fc6279362881 100644 --- a/synapse/http/servlet.py +++ b/synapse/http/servlet.py @@ -778,17 +778,13 @@ def parse_json_object_from_request( Model = TypeVar("Model", bound=BaseModel) -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. +def validate_json_object(content: JsonDict, model_type: Type[Model]) -> Model: + """Validate a deserialized JSON object 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) try: instance = model_type.parse_obj(content) except ValidationError as e: @@ -811,6 +807,20 @@ def parse_and_validate_json_object_from_request( 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: absent = [] for k in required: