test_federation.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. # Copyright 2019 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import logging
  15. from typing import List
  16. from unittest import TestCase
  17. from synapse.api.constants import EventTypes
  18. from synapse.api.errors import AuthError, Codes, LimitExceededError, SynapseError
  19. from synapse.api.room_versions import RoomVersions
  20. from synapse.events import EventBase
  21. from synapse.federation.federation_base import event_from_pdu_json
  22. from synapse.logging.context import LoggingContext, run_in_background
  23. from synapse.rest import admin
  24. from synapse.rest.client import login, room
  25. from synapse.util.stringutils import random_string
  26. from tests import unittest
  27. logger = logging.getLogger(__name__)
  28. class FederationTestCase(unittest.HomeserverTestCase):
  29. servlets = [
  30. admin.register_servlets,
  31. login.register_servlets,
  32. room.register_servlets,
  33. ]
  34. def make_homeserver(self, reactor, clock):
  35. hs = self.setup_test_homeserver(federation_http_client=None)
  36. self.handler = hs.get_federation_handler()
  37. self.store = hs.get_datastore()
  38. self.state_store = hs.get_storage().state
  39. self._event_auth_handler = hs.get_event_auth_handler()
  40. return hs
  41. def test_exchange_revoked_invite(self):
  42. user_id = self.register_user("kermit", "test")
  43. tok = self.login("kermit", "test")
  44. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  45. # Send a 3PID invite event with an empty body so it's considered as a revoked one.
  46. invite_token = "sometoken"
  47. self.helper.send_state(
  48. room_id=room_id,
  49. event_type=EventTypes.ThirdPartyInvite,
  50. state_key=invite_token,
  51. body={},
  52. tok=tok,
  53. )
  54. d = self.handler.on_exchange_third_party_invite_request(
  55. event_dict={
  56. "type": EventTypes.Member,
  57. "room_id": room_id,
  58. "sender": user_id,
  59. "state_key": "@someone:example.org",
  60. "content": {
  61. "membership": "invite",
  62. "third_party_invite": {
  63. "display_name": "alice",
  64. "signed": {
  65. "mxid": "@alice:localhost",
  66. "token": invite_token,
  67. "signatures": {
  68. "magic.forest": {
  69. "ed25519:3": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
  70. }
  71. },
  72. },
  73. },
  74. },
  75. },
  76. )
  77. failure = self.get_failure(d, AuthError).value
  78. self.assertEqual(failure.code, 403, failure)
  79. self.assertEqual(failure.errcode, Codes.FORBIDDEN, failure)
  80. self.assertEqual(failure.msg, "You are not invited to this room.")
  81. def test_rejected_message_event_state(self):
  82. """
  83. Check that we store the state group correctly for rejected non-state events.
  84. Regression test for #6289.
  85. """
  86. OTHER_SERVER = "otherserver"
  87. OTHER_USER = "@otheruser:" + OTHER_SERVER
  88. # create the room
  89. user_id = self.register_user("kermit", "test")
  90. tok = self.login("kermit", "test")
  91. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  92. room_version = self.get_success(self.store.get_room_version(room_id))
  93. # pretend that another server has joined
  94. join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id)
  95. # check the state group
  96. sg = self.successResultOf(
  97. self.store._get_state_group_for_event(join_event.event_id)
  98. )
  99. # build and send an event which will be rejected
  100. ev = event_from_pdu_json(
  101. {
  102. "type": EventTypes.Message,
  103. "content": {},
  104. "room_id": room_id,
  105. "sender": "@yetanotheruser:" + OTHER_SERVER,
  106. "depth": join_event["depth"] + 1,
  107. "prev_events": [join_event.event_id],
  108. "auth_events": [],
  109. "origin_server_ts": self.clock.time_msec(),
  110. },
  111. room_version,
  112. )
  113. with LoggingContext("send_rejected"):
  114. d = run_in_background(
  115. self.hs.get_federation_event_handler().on_receive_pdu, OTHER_SERVER, ev
  116. )
  117. self.get_success(d)
  118. # that should have been rejected
  119. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  120. self.assertIsNotNone(e.rejected_reason)
  121. # ... and the state group should be the same as before
  122. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  123. self.assertEqual(sg, sg2)
  124. def test_rejected_state_event_state(self):
  125. """
  126. Check that we store the state group correctly for rejected state events.
  127. Regression test for #6289.
  128. """
  129. OTHER_SERVER = "otherserver"
  130. OTHER_USER = "@otheruser:" + OTHER_SERVER
  131. # create the room
  132. user_id = self.register_user("kermit", "test")
  133. tok = self.login("kermit", "test")
  134. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  135. room_version = self.get_success(self.store.get_room_version(room_id))
  136. # pretend that another server has joined
  137. join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id)
  138. # check the state group
  139. sg = self.successResultOf(
  140. self.store._get_state_group_for_event(join_event.event_id)
  141. )
  142. # build and send an event which will be rejected
  143. ev = event_from_pdu_json(
  144. {
  145. "type": "org.matrix.test",
  146. "state_key": "test_key",
  147. "content": {},
  148. "room_id": room_id,
  149. "sender": "@yetanotheruser:" + OTHER_SERVER,
  150. "depth": join_event["depth"] + 1,
  151. "prev_events": [join_event.event_id],
  152. "auth_events": [],
  153. "origin_server_ts": self.clock.time_msec(),
  154. },
  155. room_version,
  156. )
  157. with LoggingContext("send_rejected"):
  158. d = run_in_background(
  159. self.hs.get_federation_event_handler().on_receive_pdu, OTHER_SERVER, ev
  160. )
  161. self.get_success(d)
  162. # that should have been rejected
  163. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  164. self.assertIsNotNone(e.rejected_reason)
  165. # ... and the state group should be the same as before
  166. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  167. self.assertEqual(sg, sg2)
  168. def test_backfill_floating_outlier_membership_auth(self):
  169. """
  170. As the local homeserver, check that we can properly process a federated
  171. event from the OTHER_SERVER with auth_events that include a floating
  172. membership event from the OTHER_SERVER.
  173. Regression test, see #10439.
  174. """
  175. OTHER_SERVER = "otherserver"
  176. OTHER_USER = "@otheruser:" + OTHER_SERVER
  177. # create the room
  178. user_id = self.register_user("kermit", "test")
  179. tok = self.login("kermit", "test")
  180. room_id = self.helper.create_room_as(
  181. room_creator=user_id,
  182. is_public=True,
  183. tok=tok,
  184. extra_content={
  185. "preset": "public_chat",
  186. },
  187. )
  188. room_version = self.get_success(self.store.get_room_version(room_id))
  189. prev_event_ids = self.get_success(self.store.get_prev_events_for_room(room_id))
  190. (
  191. most_recent_prev_event_id,
  192. most_recent_prev_event_depth,
  193. ) = self.get_success(self.store.get_max_depth_of(prev_event_ids))
  194. # mapping from (type, state_key) -> state_event_id
  195. prev_state_map = self.get_success(
  196. self.state_store.get_state_ids_for_event(most_recent_prev_event_id)
  197. )
  198. # List of state event ID's
  199. prev_state_ids = list(prev_state_map.values())
  200. auth_event_ids = prev_state_ids
  201. auth_events = list(
  202. self.get_success(self.store.get_events(auth_event_ids)).values()
  203. )
  204. # build a floating outlier member state event
  205. fake_prev_event_id = "$" + random_string(43)
  206. member_event_dict = {
  207. "type": EventTypes.Member,
  208. "content": {
  209. "membership": "join",
  210. },
  211. "state_key": OTHER_USER,
  212. "room_id": room_id,
  213. "sender": OTHER_USER,
  214. "depth": most_recent_prev_event_depth,
  215. "prev_events": [fake_prev_event_id],
  216. "origin_server_ts": self.clock.time_msec(),
  217. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  218. }
  219. builder = self.hs.get_event_builder_factory().for_room_version(
  220. room_version, member_event_dict
  221. )
  222. member_event = self.get_success(
  223. builder.build(
  224. prev_event_ids=member_event_dict["prev_events"],
  225. auth_event_ids=self._event_auth_handler.compute_auth_events(
  226. builder,
  227. prev_state_map,
  228. for_verification=False,
  229. ),
  230. depth=member_event_dict["depth"],
  231. )
  232. )
  233. # Override the signature added from "test" homeserver that we created the event with
  234. member_event.signatures = member_event_dict["signatures"]
  235. # Add the new member_event to the StateMap
  236. prev_state_map[
  237. (member_event.type, member_event.state_key)
  238. ] = member_event.event_id
  239. auth_events.append(member_event)
  240. # build and send an event authed based on the member event
  241. message_event_dict = {
  242. "type": EventTypes.Message,
  243. "content": {},
  244. "room_id": room_id,
  245. "sender": OTHER_USER,
  246. "depth": most_recent_prev_event_depth,
  247. "prev_events": prev_event_ids.copy(),
  248. "origin_server_ts": self.clock.time_msec(),
  249. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  250. }
  251. builder = self.hs.get_event_builder_factory().for_room_version(
  252. room_version, message_event_dict
  253. )
  254. message_event = self.get_success(
  255. builder.build(
  256. prev_event_ids=message_event_dict["prev_events"],
  257. auth_event_ids=self._event_auth_handler.compute_auth_events(
  258. builder,
  259. prev_state_map,
  260. for_verification=False,
  261. ),
  262. depth=message_event_dict["depth"],
  263. )
  264. )
  265. # Override the signature added from "test" homeserver that we created the event with
  266. message_event.signatures = message_event_dict["signatures"]
  267. # Stub the /event_auth response from the OTHER_SERVER
  268. async def get_event_auth(
  269. destination: str, room_id: str, event_id: str
  270. ) -> List[EventBase]:
  271. return auth_events
  272. self.handler.federation_client.get_event_auth = get_event_auth
  273. with LoggingContext("receive_pdu"):
  274. # Fake the OTHER_SERVER federating the message event over to our local homeserver
  275. d = run_in_background(
  276. self.hs.get_federation_event_handler().on_receive_pdu,
  277. OTHER_SERVER,
  278. message_event,
  279. )
  280. self.get_success(d)
  281. # Now try and get the events on our local homeserver
  282. stored_event = self.get_success(
  283. self.store.get_event(message_event.event_id, allow_none=True)
  284. )
  285. self.assertTrue(stored_event is not None)
  286. @unittest.override_config(
  287. {"rc_invites": {"per_user": {"per_second": 0.5, "burst_count": 3}}}
  288. )
  289. def test_invite_by_user_ratelimit(self):
  290. """Tests that invites from federation to a particular user are
  291. actually rate-limited.
  292. """
  293. other_server = "otherserver"
  294. other_user = "@otheruser:" + other_server
  295. # create the room
  296. user_id = self.register_user("kermit", "test")
  297. tok = self.login("kermit", "test")
  298. def create_invite():
  299. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  300. room_version = self.get_success(self.store.get_room_version(room_id))
  301. return event_from_pdu_json(
  302. {
  303. "type": EventTypes.Member,
  304. "content": {"membership": "invite"},
  305. "room_id": room_id,
  306. "sender": other_user,
  307. "state_key": "@user:test",
  308. "depth": 32,
  309. "prev_events": [],
  310. "auth_events": [],
  311. "origin_server_ts": self.clock.time_msec(),
  312. },
  313. room_version,
  314. )
  315. for _ in range(3):
  316. event = create_invite()
  317. self.get_success(
  318. self.handler.on_invite_request(
  319. other_server,
  320. event,
  321. event.room_version,
  322. )
  323. )
  324. event = create_invite()
  325. self.get_failure(
  326. self.handler.on_invite_request(
  327. other_server,
  328. event,
  329. event.room_version,
  330. ),
  331. exc=LimitExceededError,
  332. )
  333. def _build_and_send_join_event(self, other_server, other_user, room_id):
  334. join_event = self.get_success(
  335. self.handler.on_make_join_request(other_server, room_id, other_user)
  336. )
  337. # the auth code requires that a signature exists, but doesn't check that
  338. # signature... go figure.
  339. join_event.signatures[other_server] = {"x": "y"}
  340. with LoggingContext("send_join"):
  341. d = run_in_background(
  342. self.hs.get_federation_event_handler().on_send_membership_event,
  343. other_server,
  344. join_event,
  345. )
  346. self.get_success(d)
  347. # sanity-check: the room should show that the new user is a member
  348. r = self.get_success(self.store.get_current_state_ids(room_id))
  349. self.assertEqual(r[(EventTypes.Member, other_user)], join_event.event_id)
  350. return join_event
  351. class EventFromPduTestCase(TestCase):
  352. def test_valid_json(self):
  353. """Valid JSON should be turned into an event."""
  354. ev = event_from_pdu_json(
  355. {
  356. "type": EventTypes.Message,
  357. "content": {"bool": True, "null": None, "int": 1, "str": "foobar"},
  358. "room_id": "!room:test",
  359. "sender": "@user:test",
  360. "depth": 1,
  361. "prev_events": [],
  362. "auth_events": [],
  363. "origin_server_ts": 1234,
  364. },
  365. RoomVersions.V6,
  366. )
  367. self.assertIsInstance(ev, EventBase)
  368. def test_invalid_numbers(self):
  369. """Invalid values for an integer should be rejected, all floats should be rejected."""
  370. for value in [
  371. -(2 ** 53),
  372. 2 ** 53,
  373. 1.0,
  374. float("inf"),
  375. float("-inf"),
  376. float("nan"),
  377. ]:
  378. with self.assertRaises(SynapseError):
  379. event_from_pdu_json(
  380. {
  381. "type": EventTypes.Message,
  382. "content": {"foo": value},
  383. "room_id": "!room:test",
  384. "sender": "@user:test",
  385. "depth": 1,
  386. "prev_events": [],
  387. "auth_events": [],
  388. "origin_server_ts": 1234,
  389. },
  390. RoomVersions.V6,
  391. )
  392. def test_invalid_nested(self):
  393. """List and dictionaries are recursively searched."""
  394. with self.assertRaises(SynapseError):
  395. event_from_pdu_json(
  396. {
  397. "type": EventTypes.Message,
  398. "content": {"foo": [{"bar": 2 ** 56}]},
  399. "room_id": "!room:test",
  400. "sender": "@user:test",
  401. "depth": 1,
  402. "prev_events": [],
  403. "auth_events": [],
  404. "origin_server_ts": 1234,
  405. },
  406. RoomVersions.V6,
  407. )