test_federation.py 22 KB

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