From b26c01b2d217ded65fc911395488f7027e3a4941 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 11:37:32 -0400 Subject: [PATCH 01/11] Add a new room version. --- synapse/api/room_versions.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/synapse/api/room_versions.py b/synapse/api/room_versions.py index 871179749a78..9de9de955eb2 100644 --- a/synapse/api/room_versions.py +++ b/synapse/api/room_versions.py @@ -58,7 +58,12 @@ class RoomVersion(object): enforce_key_validity = attr.ib() # bool # bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules - special_case_aliases_auth = attr.ib(type=bool, default=False) + special_case_aliases_auth = attr.ib(type=bool) + # Strictly enforce canonicaljson, do not allow: + # * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1] + # * Floats + # * NaN, Infinity, -Infinity + strict_canonicaljson = attr.ib(type=bool) class RoomVersions(object): @@ -69,6 +74,7 @@ class RoomVersions(object): StateResolutionVersions.V1, enforce_key_validity=False, special_case_aliases_auth=True, + strict_canonicaljson=False, ) V2 = RoomVersion( "2", @@ -77,6 +83,7 @@ class RoomVersions(object): StateResolutionVersions.V2, enforce_key_validity=False, special_case_aliases_auth=True, + strict_canonicaljson=False, ) V3 = RoomVersion( "3", @@ -85,6 +92,7 @@ class RoomVersions(object): StateResolutionVersions.V2, enforce_key_validity=False, special_case_aliases_auth=True, + strict_canonicaljson=False, ) V4 = RoomVersion( "4", @@ -93,6 +101,7 @@ class RoomVersions(object): StateResolutionVersions.V2, enforce_key_validity=False, special_case_aliases_auth=True, + strict_canonicaljson=False, ) V5 = RoomVersion( "5", @@ -101,6 +110,7 @@ class RoomVersions(object): StateResolutionVersions.V2, enforce_key_validity=True, special_case_aliases_auth=True, + strict_canonicaljson=False, ) MSC2432_DEV = RoomVersion( "org.matrix.msc2432", @@ -109,6 +119,16 @@ class RoomVersions(object): StateResolutionVersions.V2, enforce_key_validity=True, special_case_aliases_auth=False, + strict_canonicaljson=False, + ) + STRICT_CANONICALJSON = RoomVersion( + "org.matrix.strict_canonicaljson", + RoomDisposition.UNSTABLE, + EventFormatVersions.V3, + StateResolutionVersions.V2, + enforce_key_validity=True, + special_case_aliases_auth=True, + strict_canonicaljson=True, ) @@ -121,5 +141,6 @@ class RoomVersions(object): RoomVersions.V4, RoomVersions.V5, RoomVersions.MSC2432_DEV, + RoomVersions.STRICT_CANONICALJSON, ) } # type: Dict[str, RoomVersion] From 04c81b1237e97787c516a55b367e161ae3e54056 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 12:59:36 -0400 Subject: [PATCH 02/11] Check strict canonical JSON if the room version calls for it. --- synapse/federation/federation_base.py | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py index 4b115aac04f0..c746ed7b4c09 100644 --- a/synapse/federation/federation_base.py +++ b/synapse/federation/federation_base.py @@ -15,7 +15,7 @@ # limitations under the License. import logging from collections import namedtuple -from typing import Iterable, List +from typing import Any, Iterable, List import six @@ -275,6 +275,39 @@ def _is_invite_via_3pid(event: EventBase) -> bool: ) +def _check_strict_canonicaljson(data: Any): + """ + Ensure that the JSON object is valid according to the rules of canonical JSON. + + See the appendix section 3.1: Canonical JSON. + + This rejects JSON that has: + * An integer outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1] + * Floats + * NaN, Infinity, -Infinity + """ + if isinstance(data, int): + if data <= -(2 ** 53) or 2 ** 53 <= data: + print(data) + raise SynapseError(400, "JSON integer out of range", Codes.BAD_JSON) + + elif isinstance(data, float): + # Note that Infinity, -Infinity, and NaN are also considered floats. + raise SynapseError(400, "Bad JSON value: float", Codes.BAD_JSON) + + elif isinstance(data, dict): + for v in data.values(): + _check_strict_canonicaljson(v) + + elif isinstance(data, (list, tuple)): + for i in data: + _check_strict_canonicaljson(i) + + elif not isinstance(data, (bool, str)) and data is not None: + # Other potential JSON values (bool, None, str) are safe. + raise SynapseError(400, "Unknown JSON value", Codes.BAD_JSON) + + def event_from_pdu_json( pdu_json: JsonDict, room_version: RoomVersion, outlier: bool = False ) -> EventBase: @@ -302,6 +335,10 @@ def event_from_pdu_json( elif depth > MAX_DEPTH: raise SynapseError(400, "Depth too large", Codes.BAD_JSON) + # Validate that the JSON conforms to the specification. + if room_version.strict_canonicaljson: + _check_strict_canonicaljson(pdu_json) + event = make_event_from_dict(pdu_json, room_version) event.internal_metadata.outlier = outlier From b8eb8d43a8fac925677adc4f756f9dc97f3aa976 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 13:30:39 -0400 Subject: [PATCH 03/11] Also check incoming client events. --- synapse/handlers/message.py | 16 +++++++++++++--- synapse/util/frozenutils.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 522271eed1bc..c35393521d9f 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -43,6 +43,7 @@ from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions from synapse.api.urls import ConsentURIBuilder from synapse.events.validator import EventValidator +from synapse.federation.federation_base import _check_strict_canonicaljson from synapse.logging.context import run_in_background from synapse.metrics.background_process_metrics import run_as_background_process from synapse.replication.http.send_event import ReplicationSendEventRestServlet @@ -792,9 +793,14 @@ def handle_new_client_event( EventTypes.Create, "", ): - room_version = event.content.get("room_version", RoomVersions.V1.identifier) + room_version_id = event.content.get( + "room_version", RoomVersions.V1.identifier + ) + room_version = KNOWN_ROOM_VERSIONS[room_version_id] else: - room_version = yield self.store.get_room_version_id(event.room_id) + room_version = yield defer.ensureDeferred( + self.store.get_room_version(event.room_id) + ) event_allowed = yield self.third_party_event_rules.check_event_allowed( event, context @@ -805,11 +811,15 @@ def handle_new_client_event( ) try: - yield self.auth.check_from_context(room_version, event, context) + yield self.auth.check_from_context(room_version.identifier, event, context) except AuthError as err: logger.warning("Denying new event %r because %s", event, err) raise err + # Ensure the data is spec compliant JSON. + if room_version.strict_canonicaljson: + _check_strict_canonicaljson(event.content) + # Ensure that we can round trip before trying to persist in db try: dump = frozendict_json_encoder.encode(event.content) diff --git a/synapse/util/frozenutils.py b/synapse/util/frozenutils.py index f2ccd5e7c6bd..9815bb8667f4 100644 --- a/synapse/util/frozenutils.py +++ b/synapse/util/frozenutils.py @@ -65,5 +65,5 @@ def _handle_frozendict(obj): ) -# A JSONEncoder which is capable of encoding frozendics without barfing +# A JSONEncoder which is capable of encoding frozendicts without barfing frozendict_json_encoder = json.JSONEncoder(default=_handle_frozendict) From 97cf2a91f782fa76226f92e00648089f09a571bb Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 15:16:30 -0400 Subject: [PATCH 04/11] Refactor the validation function. --- synapse/events/utils.py | 36 ++++++++++++++++++++++++- synapse/federation/federation_base.py | 39 +++------------------------ synapse/handlers/message.py | 4 +-- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/synapse/events/utils.py b/synapse/events/utils.py index b75b097e5ef9..edeb6ddbf2a0 100644 --- a/synapse/events/utils.py +++ b/synapse/events/utils.py @@ -14,7 +14,7 @@ # limitations under the License. import collections import re -from typing import Mapping, Union +from typing import Any, Mapping, Union from six import string_types @@ -23,6 +23,7 @@ from twisted.internet import defer from synapse.api.constants import EventTypes, RelationTypes +from synapse.api.errors import Codes, SynapseError from synapse.api.room_versions import RoomVersion from synapse.util.async_helpers import yieldable_gather_results @@ -449,3 +450,36 @@ def copy_power_levels_contents( raise TypeError("Invalid power_levels value for %s: %r" % (k, v)) return power_levels + + +def validate_canonicaljson(value: Any): + """ + Ensure that the JSON object is valid according to the rules of canonical JSON. + + See the appendix section 3.1: Canonical JSON. + + This rejects JSON that has: + * An integer outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1] + * Floats + * NaN, Infinity, -Infinity + """ + if isinstance(value, int): + if value <= -(2 ** 53) or 2 ** 53 <= value: + print(value) + raise SynapseError(400, "JSON integer out of range", Codes.BAD_JSON) + + elif isinstance(value, float): + # Note that Infinity, -Infinity, and NaN are also considered floats. + raise SynapseError(400, "Bad JSON value: float", Codes.BAD_JSON) + + elif isinstance(value, dict): + for v in value.values(): + validate_canonicaljson(v) + + elif isinstance(value, (list, tuple)): + for i in value: + validate_canonicaljson(i) + + elif not isinstance(value, (bool, str)) and value is not None: + # Other potential JSON values (bool, None, str) are safe. + raise SynapseError(400, "Unknown JSON value", Codes.BAD_JSON) diff --git a/synapse/federation/federation_base.py b/synapse/federation/federation_base.py index c746ed7b4c09..c0012c687242 100644 --- a/synapse/federation/federation_base.py +++ b/synapse/federation/federation_base.py @@ -15,7 +15,7 @@ # limitations under the License. import logging from collections import namedtuple -from typing import Any, Iterable, List +from typing import Iterable, List import six @@ -29,7 +29,7 @@ from synapse.crypto.event_signing import check_event_content_hash from synapse.crypto.keyring import Keyring from synapse.events import EventBase, make_event_from_dict -from synapse.events.utils import prune_event +from synapse.events.utils import prune_event, validate_canonicaljson from synapse.http.servlet import assert_params_in_dict from synapse.logging.context import ( PreserveLoggingContext, @@ -275,39 +275,6 @@ def _is_invite_via_3pid(event: EventBase) -> bool: ) -def _check_strict_canonicaljson(data: Any): - """ - Ensure that the JSON object is valid according to the rules of canonical JSON. - - See the appendix section 3.1: Canonical JSON. - - This rejects JSON that has: - * An integer outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1] - * Floats - * NaN, Infinity, -Infinity - """ - if isinstance(data, int): - if data <= -(2 ** 53) or 2 ** 53 <= data: - print(data) - raise SynapseError(400, "JSON integer out of range", Codes.BAD_JSON) - - elif isinstance(data, float): - # Note that Infinity, -Infinity, and NaN are also considered floats. - raise SynapseError(400, "Bad JSON value: float", Codes.BAD_JSON) - - elif isinstance(data, dict): - for v in data.values(): - _check_strict_canonicaljson(v) - - elif isinstance(data, (list, tuple)): - for i in data: - _check_strict_canonicaljson(i) - - elif not isinstance(data, (bool, str)) and data is not None: - # Other potential JSON values (bool, None, str) are safe. - raise SynapseError(400, "Unknown JSON value", Codes.BAD_JSON) - - def event_from_pdu_json( pdu_json: JsonDict, room_version: RoomVersion, outlier: bool = False ) -> EventBase: @@ -337,7 +304,7 @@ def event_from_pdu_json( # Validate that the JSON conforms to the specification. if room_version.strict_canonicaljson: - _check_strict_canonicaljson(pdu_json) + validate_canonicaljson(pdu_json) event = make_event_from_dict(pdu_json, room_version) event.internal_metadata.outlier = outlier diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index c35393521d9f..41a2729c4b74 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -42,8 +42,8 @@ ) from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions from synapse.api.urls import ConsentURIBuilder +from synapse.events.utils import validate_canonicaljson from synapse.events.validator import EventValidator -from synapse.federation.federation_base import _check_strict_canonicaljson from synapse.logging.context import run_in_background from synapse.metrics.background_process_metrics import run_as_background_process from synapse.replication.http.send_event import ReplicationSendEventRestServlet @@ -818,7 +818,7 @@ def handle_new_client_event( # Ensure the data is spec compliant JSON. if room_version.strict_canonicaljson: - _check_strict_canonicaljson(event.content) + validate_canonicaljson(event.content) # Ensure that we can round trip before trying to persist in db try: From 6c06fa3c2f647fc0b31dbce46985668980316339 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 15:17:15 -0400 Subject: [PATCH 05/11] Handle frozendicts properly. --- synapse/events/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/events/utils.py b/synapse/events/utils.py index edeb6ddbf2a0..a6f535446317 100644 --- a/synapse/events/utils.py +++ b/synapse/events/utils.py @@ -472,7 +472,7 @@ def validate_canonicaljson(value: Any): # Note that Infinity, -Infinity, and NaN are also considered floats. raise SynapseError(400, "Bad JSON value: float", Codes.BAD_JSON) - elif isinstance(value, dict): + elif isinstance(value, (dict, frozendict)): for v in value.values(): validate_canonicaljson(v) From 6ef13b67e9b896c89b37160c5cca1240a3726eca Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Tue, 28 Apr 2020 15:44:12 -0400 Subject: [PATCH 06/11] Add some basic test cases. --- synapse/events/utils.py | 1 - synapse/handlers/message.py | 2 +- tests/handlers/test_federation.py | 67 ++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/synapse/events/utils.py b/synapse/events/utils.py index a6f535446317..dd340be9a7ad 100644 --- a/synapse/events/utils.py +++ b/synapse/events/utils.py @@ -465,7 +465,6 @@ def validate_canonicaljson(value: Any): """ if isinstance(value, int): if value <= -(2 ** 53) or 2 ** 53 <= value: - print(value) raise SynapseError(400, "JSON integer out of range", Codes.BAD_JSON) elif isinstance(value, float): diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 41a2729c4b74..3ec254128aa3 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -818,7 +818,7 @@ def handle_new_client_event( # Ensure the data is spec compliant JSON. if room_version.strict_canonicaljson: - validate_canonicaljson(event.content) + validate_canonicaljson(event.get_pdu_json()) # Ensure that we can round trip before trying to persist in db try: diff --git a/tests/handlers/test_federation.py b/tests/handlers/test_federation.py index 132e35651dbb..dfef58e70474 100644 --- a/tests/handlers/test_federation.py +++ b/tests/handlers/test_federation.py @@ -13,9 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +from unittest import TestCase from synapse.api.constants import EventTypes -from synapse.api.errors import AuthError, Codes +from synapse.api.errors import AuthError, Codes, SynapseError +from synapse.api.room_versions import RoomVersions +from synapse.events import EventBase from synapse.federation.federation_base import event_from_pdu_json from synapse.logging.context import LoggingContext, run_in_background from synapse.rest import admin @@ -207,3 +210,65 @@ def _build_and_send_join_event(self, other_server, other_user, room_id): self.assertEqual(r[(EventTypes.Member, other_user)], join_event.event_id) return join_event + + +class EventFromPduTestCase(TestCase): + def test_valid_json(self): + """Valid JSON should be turned into an event.""" + ev = event_from_pdu_json( + { + "type": EventTypes.Message, + "content": {"bool": True, "null": None, "int": 1, "str": "foobar"}, + "room_id": "!room:test", + "sender": "@user:test", + "depth": 1, + "prev_events": [], + "auth_events": [], + "origin_server_ts": 1234, + }, + RoomVersions.STRICT_CANONICALJSON, + ) + + self.assertIsInstance(ev, EventBase) + + def test_invalid_numbers(self): + """Invalid values for an integer should be rejected, all floats should be rejected.""" + for value in [ + -(2 ** 53), + 2 ** 53, + 1.0, + float("inf"), + float("-inf"), + float("nan"), + ]: + with self.assertRaises(SynapseError): + event_from_pdu_json( + { + "type": EventTypes.Message, + "content": {"foo": value}, + "room_id": "!room:test", + "sender": "@user:test", + "depth": 1, + "prev_events": [], + "auth_events": [], + "origin_server_ts": 1234, + }, + RoomVersions.STRICT_CANONICALJSON, + ) + + def test_invalid_nested(self): + """List and dictionaries are recursively searched.""" + with self.assertRaises(SynapseError): + event_from_pdu_json( + { + "type": EventTypes.Message, + "content": {"foo": [{"bar": 2 ** 56}]}, + "room_id": "!room:test", + "sender": "@user:test", + "depth": 1, + "prev_events": [], + "auth_events": [], + "origin_server_ts": 1234, + }, + RoomVersions.STRICT_CANONICALJSON, + ) From c9647b47165735710547c22506eacd5056baff4d Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 30 Apr 2020 14:08:51 -0400 Subject: [PATCH 07/11] Add newsfragment. --- changelog.d/7381.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7381.bugfix diff --git a/changelog.d/7381.bugfix b/changelog.d/7381.bugfix new file mode 100644 index 000000000000..e5f93571dcfa --- /dev/null +++ b/changelog.d/7381.bugfix @@ -0,0 +1 @@ +Add an experimental room version which strictly adheres to the canonical JSON specification. From fc6f5a3c73699dd2bd7e7ad0ac51daf8f215f0db Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 14 May 2020 11:55:03 -0400 Subject: [PATCH 08/11] Move check from handler to validator. --- synapse/events/validator.py | 4 ++++ synapse/handlers/message.py | 16 +++------------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/synapse/events/validator.py b/synapse/events/validator.py index 9b90c9ce04bd..180d90ca8b27 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -17,6 +17,7 @@ from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes, Membership from synapse.api.errors import Codes, SynapseError +from synapse.events.utils import validate_canonicaljson from synapse.api.room_versions import EventFormatVersions from synapse.types import EventID, RoomID, UserID @@ -55,6 +56,9 @@ def validate_new(self, event, config): if not isinstance(getattr(event, s), string_types): raise SynapseError(400, "'%s' not a string type" % (s,)) + if event.room_version.strict_canonicaljson: + validate_canonicaljson(event.content) + if event.type == EventTypes.Aliases: if "aliases" in event.content: for alias in event.content["aliases"]: diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 3ec254128aa3..522271eed1bc 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -42,7 +42,6 @@ ) from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersions from synapse.api.urls import ConsentURIBuilder -from synapse.events.utils import validate_canonicaljson from synapse.events.validator import EventValidator from synapse.logging.context import run_in_background from synapse.metrics.background_process_metrics import run_as_background_process @@ -793,14 +792,9 @@ def handle_new_client_event( EventTypes.Create, "", ): - room_version_id = event.content.get( - "room_version", RoomVersions.V1.identifier - ) - room_version = KNOWN_ROOM_VERSIONS[room_version_id] + room_version = event.content.get("room_version", RoomVersions.V1.identifier) else: - room_version = yield defer.ensureDeferred( - self.store.get_room_version(event.room_id) - ) + room_version = yield self.store.get_room_version_id(event.room_id) event_allowed = yield self.third_party_event_rules.check_event_allowed( event, context @@ -811,15 +805,11 @@ def handle_new_client_event( ) try: - yield self.auth.check_from_context(room_version.identifier, event, context) + yield self.auth.check_from_context(room_version, event, context) except AuthError as err: logger.warning("Denying new event %r because %s", event, err) raise err - # Ensure the data is spec compliant JSON. - if room_version.strict_canonicaljson: - validate_canonicaljson(event.get_pdu_json()) - # Ensure that we can round trip before trying to persist in db try: dump = frozendict_json_encoder.encode(event.content) From a4485a920d1b822be9820ff306d525ac6d1749bc Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 14 May 2020 12:15:57 -0400 Subject: [PATCH 09/11] Add a comment. --- synapse/events/validator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/events/validator.py b/synapse/events/validator.py index 180d90ca8b27..74e94a4bc4df 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -56,6 +56,7 @@ def validate_new(self, event, config): if not isinstance(getattr(event, s), string_types): raise SynapseError(400, "'%s' not a string type" % (s,)) + # Depending on the room version, ensure the data is spec compliant JSON. if event.room_version.strict_canonicaljson: validate_canonicaljson(event.content) From 534032b8dd352720a1e8a1bb0d481a6588a08b61 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 14 May 2020 12:20:09 -0400 Subject: [PATCH 10/11] Add additional comments. --- synapse/events/validator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/synapse/events/validator.py b/synapse/events/validator.py index 74e94a4bc4df..1f496afbd5e0 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -58,6 +58,8 @@ def validate_new(self, event, config): # Depending on the room version, ensure the data is spec compliant JSON. if event.room_version.strict_canonicaljson: + # Note that only the client controlled portion of the event is + # checked, since we trust the portions of the event we created. validate_canonicaljson(event.content) if event.type == EventTypes.Aliases: From e8edfec597cddc22f1712a362375c4fbacc1f455 Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Thu, 14 May 2020 12:22:59 -0400 Subject: [PATCH 11/11] Fix import order. --- synapse/events/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/events/validator.py b/synapse/events/validator.py index 1f496afbd5e0..b001c64bb4f8 100644 --- a/synapse/events/validator.py +++ b/synapse/events/validator.py @@ -17,8 +17,8 @@ from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes, Membership from synapse.api.errors import Codes, SynapseError -from synapse.events.utils import validate_canonicaljson from synapse.api.room_versions import EventFormatVersions +from synapse.events.utils import validate_canonicaljson from synapse.types import EventID, RoomID, UserID