test_federation.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  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.types import create_requester
  26. from synapse.util.stringutils import random_string
  27. from tests import unittest
  28. logger = logging.getLogger(__name__)
  29. def generate_fake_event_id() -> str:
  30. return "$fake_" + random_string(43)
  31. class FederationTestCase(unittest.HomeserverTestCase):
  32. servlets = [
  33. admin.register_servlets,
  34. login.register_servlets,
  35. room.register_servlets,
  36. ]
  37. def make_homeserver(self, reactor, clock):
  38. hs = self.setup_test_homeserver(federation_http_client=None)
  39. self.handler = hs.get_federation_handler()
  40. self.store = hs.get_datastore()
  41. self.state_store = hs.get_storage().state
  42. self._event_auth_handler = hs.get_event_auth_handler()
  43. return hs
  44. def test_exchange_revoked_invite(self):
  45. user_id = self.register_user("kermit", "test")
  46. tok = self.login("kermit", "test")
  47. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  48. # Send a 3PID invite event with an empty body so it's considered as a revoked one.
  49. invite_token = "sometoken"
  50. self.helper.send_state(
  51. room_id=room_id,
  52. event_type=EventTypes.ThirdPartyInvite,
  53. state_key=invite_token,
  54. body={},
  55. tok=tok,
  56. )
  57. d = self.handler.on_exchange_third_party_invite_request(
  58. event_dict={
  59. "type": EventTypes.Member,
  60. "room_id": room_id,
  61. "sender": user_id,
  62. "state_key": "@someone:example.org",
  63. "content": {
  64. "membership": "invite",
  65. "third_party_invite": {
  66. "display_name": "alice",
  67. "signed": {
  68. "mxid": "@alice:localhost",
  69. "token": invite_token,
  70. "signatures": {
  71. "magic.forest": {
  72. "ed25519:3": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
  73. }
  74. },
  75. },
  76. },
  77. },
  78. },
  79. )
  80. failure = self.get_failure(d, AuthError).value
  81. self.assertEqual(failure.code, 403, failure)
  82. self.assertEqual(failure.errcode, Codes.FORBIDDEN, failure)
  83. self.assertEqual(failure.msg, "You are not invited to this room.")
  84. def test_rejected_message_event_state(self):
  85. """
  86. Check that we store the state group correctly for rejected non-state events.
  87. Regression test for #6289.
  88. """
  89. OTHER_SERVER = "otherserver"
  90. OTHER_USER = "@otheruser:" + OTHER_SERVER
  91. # create the room
  92. user_id = self.register_user("kermit", "test")
  93. tok = self.login("kermit", "test")
  94. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  95. room_version = self.get_success(self.store.get_room_version(room_id))
  96. # pretend that another server has joined
  97. join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id)
  98. # check the state group
  99. sg = self.successResultOf(
  100. self.store._get_state_group_for_event(join_event.event_id)
  101. )
  102. # build and send an event which will be rejected
  103. ev = event_from_pdu_json(
  104. {
  105. "type": EventTypes.Message,
  106. "content": {},
  107. "room_id": room_id,
  108. "sender": "@yetanotheruser:" + OTHER_SERVER,
  109. "depth": join_event["depth"] + 1,
  110. "prev_events": [join_event.event_id],
  111. "auth_events": [],
  112. "origin_server_ts": self.clock.time_msec(),
  113. },
  114. room_version,
  115. )
  116. with LoggingContext("send_rejected"):
  117. d = run_in_background(
  118. self.hs.get_federation_event_handler().on_receive_pdu, OTHER_SERVER, ev
  119. )
  120. self.get_success(d)
  121. # that should have been rejected
  122. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  123. self.assertIsNotNone(e.rejected_reason)
  124. # ... and the state group should be the same as before
  125. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  126. self.assertEqual(sg, sg2)
  127. def test_rejected_state_event_state(self):
  128. """
  129. Check that we store the state group correctly for rejected state events.
  130. Regression test for #6289.
  131. """
  132. OTHER_SERVER = "otherserver"
  133. OTHER_USER = "@otheruser:" + OTHER_SERVER
  134. # create the room
  135. user_id = self.register_user("kermit", "test")
  136. tok = self.login("kermit", "test")
  137. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  138. room_version = self.get_success(self.store.get_room_version(room_id))
  139. # pretend that another server has joined
  140. join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id)
  141. # check the state group
  142. sg = self.successResultOf(
  143. self.store._get_state_group_for_event(join_event.event_id)
  144. )
  145. # build and send an event which will be rejected
  146. ev = event_from_pdu_json(
  147. {
  148. "type": "org.matrix.test",
  149. "state_key": "test_key",
  150. "content": {},
  151. "room_id": room_id,
  152. "sender": "@yetanotheruser:" + OTHER_SERVER,
  153. "depth": join_event["depth"] + 1,
  154. "prev_events": [join_event.event_id],
  155. "auth_events": [],
  156. "origin_server_ts": self.clock.time_msec(),
  157. },
  158. room_version,
  159. )
  160. with LoggingContext("send_rejected"):
  161. d = run_in_background(
  162. self.hs.get_federation_event_handler().on_receive_pdu, OTHER_SERVER, ev
  163. )
  164. self.get_success(d)
  165. # that should have been rejected
  166. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  167. self.assertIsNotNone(e.rejected_reason)
  168. # ... and the state group should be the same as before
  169. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  170. self.assertEqual(sg, sg2)
  171. def test_backfill_with_many_backward_extremities(self):
  172. """
  173. Check that we can backfill with many backward extremities.
  174. The goal is to make sure that when we only use a portion
  175. of backwards extremities(the magic number is more than 5),
  176. no errors are thrown.
  177. Regression test, see #11027
  178. """
  179. # create the room
  180. user_id = self.register_user("kermit", "test")
  181. tok = self.login("kermit", "test")
  182. requester = create_requester(user_id)
  183. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  184. ev1 = self.helper.send(room_id, "first message", tok=tok)
  185. # Create "many" backward extremities. The magic number we're trying to
  186. # create more than is 5 which corresponds to the number of backward
  187. # extremities we slice off in `_maybe_backfill_inner`
  188. for _ in range(0, 8):
  189. event_handler = self.hs.get_event_creation_handler()
  190. event, context = self.get_success(
  191. event_handler.create_event(
  192. requester,
  193. {
  194. "type": "m.room.message",
  195. "content": {
  196. "msgtype": "m.text",
  197. "body": "message connected to fake event",
  198. },
  199. "room_id": room_id,
  200. "sender": user_id,
  201. },
  202. prev_event_ids=[
  203. ev1["event_id"],
  204. # We're creating an backward extremity each time thanks
  205. # to this fake event
  206. generate_fake_event_id(),
  207. ],
  208. )
  209. )
  210. self.get_success(
  211. event_handler.handle_new_client_event(requester, event, context)
  212. )
  213. current_depth = 1
  214. limit = 100
  215. with LoggingContext("receive_pdu"):
  216. # Make sure backfill still works
  217. d = run_in_background(
  218. self.hs.get_federation_handler().maybe_backfill,
  219. room_id,
  220. current_depth,
  221. limit,
  222. )
  223. self.get_success(d)
  224. def test_backfill_floating_outlier_membership_auth(self):
  225. """
  226. As the local homeserver, check that we can properly process a federated
  227. event from the OTHER_SERVER with auth_events that include a floating
  228. membership event from the OTHER_SERVER.
  229. Regression test, see #10439.
  230. """
  231. OTHER_SERVER = "otherserver"
  232. OTHER_USER = "@otheruser:" + OTHER_SERVER
  233. # create the room
  234. user_id = self.register_user("kermit", "test")
  235. tok = self.login("kermit", "test")
  236. room_id = self.helper.create_room_as(
  237. room_creator=user_id,
  238. is_public=True,
  239. tok=tok,
  240. extra_content={
  241. "preset": "public_chat",
  242. },
  243. )
  244. room_version = self.get_success(self.store.get_room_version(room_id))
  245. prev_event_ids = self.get_success(self.store.get_prev_events_for_room(room_id))
  246. (
  247. most_recent_prev_event_id,
  248. most_recent_prev_event_depth,
  249. ) = self.get_success(self.store.get_max_depth_of(prev_event_ids))
  250. # mapping from (type, state_key) -> state_event_id
  251. prev_state_map = self.get_success(
  252. self.state_store.get_state_ids_for_event(most_recent_prev_event_id)
  253. )
  254. # List of state event ID's
  255. prev_state_ids = list(prev_state_map.values())
  256. auth_event_ids = prev_state_ids
  257. auth_events = list(
  258. self.get_success(self.store.get_events(auth_event_ids)).values()
  259. )
  260. # build a floating outlier member state event
  261. fake_prev_event_id = "$" + random_string(43)
  262. member_event_dict = {
  263. "type": EventTypes.Member,
  264. "content": {
  265. "membership": "join",
  266. },
  267. "state_key": OTHER_USER,
  268. "room_id": room_id,
  269. "sender": OTHER_USER,
  270. "depth": most_recent_prev_event_depth,
  271. "prev_events": [fake_prev_event_id],
  272. "origin_server_ts": self.clock.time_msec(),
  273. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  274. }
  275. builder = self.hs.get_event_builder_factory().for_room_version(
  276. room_version, member_event_dict
  277. )
  278. member_event = self.get_success(
  279. builder.build(
  280. prev_event_ids=member_event_dict["prev_events"],
  281. auth_event_ids=self._event_auth_handler.compute_auth_events(
  282. builder,
  283. prev_state_map,
  284. for_verification=False,
  285. ),
  286. depth=member_event_dict["depth"],
  287. )
  288. )
  289. # Override the signature added from "test" homeserver that we created the event with
  290. member_event.signatures = member_event_dict["signatures"]
  291. # Add the new member_event to the StateMap
  292. prev_state_map[
  293. (member_event.type, member_event.state_key)
  294. ] = member_event.event_id
  295. auth_events.append(member_event)
  296. # build and send an event authed based on the member event
  297. message_event_dict = {
  298. "type": EventTypes.Message,
  299. "content": {},
  300. "room_id": room_id,
  301. "sender": OTHER_USER,
  302. "depth": most_recent_prev_event_depth,
  303. "prev_events": prev_event_ids.copy(),
  304. "origin_server_ts": self.clock.time_msec(),
  305. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  306. }
  307. builder = self.hs.get_event_builder_factory().for_room_version(
  308. room_version, message_event_dict
  309. )
  310. message_event = self.get_success(
  311. builder.build(
  312. prev_event_ids=message_event_dict["prev_events"],
  313. auth_event_ids=self._event_auth_handler.compute_auth_events(
  314. builder,
  315. prev_state_map,
  316. for_verification=False,
  317. ),
  318. depth=message_event_dict["depth"],
  319. )
  320. )
  321. # Override the signature added from "test" homeserver that we created the event with
  322. message_event.signatures = message_event_dict["signatures"]
  323. # Stub the /event_auth response from the OTHER_SERVER
  324. async def get_event_auth(
  325. destination: str, room_id: str, event_id: str
  326. ) -> List[EventBase]:
  327. return [
  328. event_from_pdu_json(
  329. ae.get_pdu_json(), room_version=room_version, outlier=True
  330. )
  331. for ae in auth_events
  332. ]
  333. self.handler.federation_client.get_event_auth = get_event_auth
  334. with LoggingContext("receive_pdu"):
  335. # Fake the OTHER_SERVER federating the message event over to our local homeserver
  336. d = run_in_background(
  337. self.hs.get_federation_event_handler().on_receive_pdu,
  338. OTHER_SERVER,
  339. message_event,
  340. )
  341. self.get_success(d)
  342. # Now try and get the events on our local homeserver
  343. stored_event = self.get_success(
  344. self.store.get_event(message_event.event_id, allow_none=True)
  345. )
  346. self.assertTrue(stored_event is not None)
  347. @unittest.override_config(
  348. {"rc_invites": {"per_user": {"per_second": 0.5, "burst_count": 3}}}
  349. )
  350. def test_invite_by_user_ratelimit(self):
  351. """Tests that invites from federation to a particular user are
  352. actually rate-limited.
  353. """
  354. other_server = "otherserver"
  355. other_user = "@otheruser:" + other_server
  356. # create the room
  357. user_id = self.register_user("kermit", "test")
  358. tok = self.login("kermit", "test")
  359. def create_invite():
  360. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  361. room_version = self.get_success(self.store.get_room_version(room_id))
  362. return event_from_pdu_json(
  363. {
  364. "type": EventTypes.Member,
  365. "content": {"membership": "invite"},
  366. "room_id": room_id,
  367. "sender": other_user,
  368. "state_key": "@user:test",
  369. "depth": 32,
  370. "prev_events": [],
  371. "auth_events": [],
  372. "origin_server_ts": self.clock.time_msec(),
  373. },
  374. room_version,
  375. )
  376. for _ in range(3):
  377. event = create_invite()
  378. self.get_success(
  379. self.handler.on_invite_request(
  380. other_server,
  381. event,
  382. event.room_version,
  383. )
  384. )
  385. event = create_invite()
  386. self.get_failure(
  387. self.handler.on_invite_request(
  388. other_server,
  389. event,
  390. event.room_version,
  391. ),
  392. exc=LimitExceededError,
  393. )
  394. def _build_and_send_join_event(self, other_server, other_user, room_id):
  395. join_event = self.get_success(
  396. self.handler.on_make_join_request(other_server, room_id, other_user)
  397. )
  398. # the auth code requires that a signature exists, but doesn't check that
  399. # signature... go figure.
  400. join_event.signatures[other_server] = {"x": "y"}
  401. with LoggingContext("send_join"):
  402. d = run_in_background(
  403. self.hs.get_federation_event_handler().on_send_membership_event,
  404. other_server,
  405. join_event,
  406. )
  407. self.get_success(d)
  408. # sanity-check: the room should show that the new user is a member
  409. r = self.get_success(self.store.get_current_state_ids(room_id))
  410. self.assertEqual(r[(EventTypes.Member, other_user)], join_event.event_id)
  411. return join_event
  412. class EventFromPduTestCase(TestCase):
  413. def test_valid_json(self):
  414. """Valid JSON should be turned into an event."""
  415. ev = event_from_pdu_json(
  416. {
  417. "type": EventTypes.Message,
  418. "content": {"bool": True, "null": None, "int": 1, "str": "foobar"},
  419. "room_id": "!room:test",
  420. "sender": "@user:test",
  421. "depth": 1,
  422. "prev_events": [],
  423. "auth_events": [],
  424. "origin_server_ts": 1234,
  425. },
  426. RoomVersions.V6,
  427. )
  428. self.assertIsInstance(ev, EventBase)
  429. def test_invalid_numbers(self):
  430. """Invalid values for an integer should be rejected, all floats should be rejected."""
  431. for value in [
  432. -(2 ** 53),
  433. 2 ** 53,
  434. 1.0,
  435. float("inf"),
  436. float("-inf"),
  437. float("nan"),
  438. ]:
  439. with self.assertRaises(SynapseError):
  440. event_from_pdu_json(
  441. {
  442. "type": EventTypes.Message,
  443. "content": {"foo": value},
  444. "room_id": "!room:test",
  445. "sender": "@user:test",
  446. "depth": 1,
  447. "prev_events": [],
  448. "auth_events": [],
  449. "origin_server_ts": 1234,
  450. },
  451. RoomVersions.V6,
  452. )
  453. def test_invalid_nested(self):
  454. """List and dictionaries are recursively searched."""
  455. with self.assertRaises(SynapseError):
  456. event_from_pdu_json(
  457. {
  458. "type": EventTypes.Message,
  459. "content": {"foo": [{"bar": 2 ** 56}]},
  460. "room_id": "!room:test",
  461. "sender": "@user:test",
  462. "depth": 1,
  463. "prev_events": [],
  464. "auth_events": [],
  465. "origin_server_ts": 1234,
  466. },
  467. RoomVersions.V6,
  468. )