diff --git a/synapse/events/validator.py b/synapse/events/validator.py index e8d6cc9710..a31247d90c 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -106,25 +106,14 @@ class EventValidator: ) elif event.type == EventTypes.PowerLevels: - try: - jsonschema.validate( - instance=event.content, - schema=POWER_LEVELS_SCHEMA, - cls=POWER_LEVELS_VALIDATOR, - ) - except jsonschema.ValidationError as e: - raise _validation_error_to_api_error(e) + _validate_against_schema(_POWER_LEVELS_SCHEMA_VALIDATOR, event.content) # If the event contains a mentions key, validate it. if EventContentFields.MENTIONS in event.content: - try: - jsonschema.validate( - instance=event.content[EventContentFields.MENTIONS], - schema=MENTIONS_SCHEMA, - cls=MENTIONS_VALIDATOR, - ) - except jsonschema.ValidationError as e: - raise _validation_error_to_api_error(e) + _validate_against_schema( + _MENTIONS_SCHEMA_VALIDATOR, + event.content[EventContentFields.MENTIONS], + ) def _validate_retention(self, event: EventBase) -> None: """Checks that an event that defines the retention policy for a room respects the @@ -329,3 +318,34 @@ def _validation_error_to_api_error(err: jsonschema.ValidationError) -> SynapseEr POWER_LEVELS_VALIDATOR = _create_validator(POWER_LEVELS_SCHEMA) MENTIONS_VALIDATOR = _create_validator(MENTIONS_SCHEMA) + + +def _compile_validator( + cls: type[jsonschema.Draft7Validator], schema: JsonDict +) -> jsonschema.Draft7Validator: + """Check `schema` once and return a validator instance bound to it. + + `jsonschema.validate` checks the schema against the metaschema on every + call, which costs more than validating a power levels event. + """ + cls.check_schema(schema) + return cls(schema) + + +_POWER_LEVELS_SCHEMA_VALIDATOR = _compile_validator( + POWER_LEVELS_VALIDATOR, POWER_LEVELS_SCHEMA +) + +_MENTIONS_SCHEMA_VALIDATOR = _compile_validator(MENTIONS_VALIDATOR, MENTIONS_SCHEMA) + + +def _validate_against_schema( + validator: jsonschema.Draft7Validator, instance: object +) -> None: + """Validate `instance`, raising a 400 `SynapseError` if it does not match. + + Reports the same error as `jsonschema.validate` would. + """ + error = jsonschema.exceptions.best_match(validator.iter_errors(instance)) + if error is not None: + raise _validation_error_to_api_error(error) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index a980678955..bd161dbc32 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -800,7 +800,7 @@ class EventCreationHandler: 403, "You must be in the room to create an alias for it" ) - self.validator.validate_new(event, self.config) + # `create_new_client_event` has already run `validate_new` on the event. return event, unpersisted_context async def _is_exempt_from_privacy_policy( diff --git a/tests/push/test_bulk_push_rule_evaluator.py b/tests/push/test_bulk_push_rule_evaluator.py index f6be933871..3fb252e7e3 100644 --- a/tests/push/test_bulk_push_rule_evaluator.py +++ b/tests/push/test_bulk_push_rule_evaluator.py @@ -126,7 +126,7 @@ class TestBulkPushRuleEvaluator(HomeserverTestCase): # some of this validation was missing in the past. with ( patch("synapse.events.validator.validate_canonicaljson"), - patch("synapse.events.validator.jsonschema.validate"), + patch("synapse.events.validator._validate_against_schema"), patch("synapse.handlers.event_auth.check_state_dependent_auth_rules"), ): pl_event_id = self.helper.send_state( diff --git a/tests/rest/client/test_upgrade_room.py b/tests/rest/client/test_upgrade_room.py index cc4073cafc..dfc33a772f 100644 --- a/tests/rest/client/test_upgrade_room.py +++ b/tests/rest/client/test_upgrade_room.py @@ -229,7 +229,7 @@ class UpgradeRoomTest(unittest.HomeserverTestCase): # Synapse refuses to accept new stringy power level events. Bypass this by # neutering the validation. - with patch("synapse.events.validator.jsonschema.validate"): + with patch("synapse.events.validator._validate_against_schema"): # Note: https://github.com/matrix-org/matrix-spec/issues/853 plans to forbid # string power levels in new rooms. For this test to have a clean # conscience, we ought to ensure it's upgrading from a sufficiently old