You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

456 lines
17 KiB

  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(self.handler.on_receive_pdu, OTHER_SERVER, ev)
  115. self.get_success(d)
  116. # that should have been rejected
  117. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  118. self.assertIsNotNone(e.rejected_reason)
  119. # ... and the state group should be the same as before
  120. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  121. self.assertEqual(sg, sg2)
  122. def test_rejected_state_event_state(self):
  123. """
  124. Check that we store the state group correctly for rejected state events.
  125. Regression test for #6289.
  126. """
  127. OTHER_SERVER = "otherserver"
  128. OTHER_USER = "@otheruser:" + OTHER_SERVER
  129. # create the room
  130. user_id = self.register_user("kermit", "test")
  131. tok = self.login("kermit", "test")
  132. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  133. room_version = self.get_success(self.store.get_room_version(room_id))
  134. # pretend that another server has joined
  135. join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id)
  136. # check the state group
  137. sg = self.successResultOf(
  138. self.store._get_state_group_for_event(join_event.event_id)
  139. )
  140. # build and send an event which will be rejected
  141. ev = event_from_pdu_json(
  142. {
  143. "type": "org.matrix.test",
  144. "state_key": "test_key",
  145. "content": {},
  146. "room_id": room_id,
  147. "sender": "@yetanotheruser:" + OTHER_SERVER,
  148. "depth": join_event["depth"] + 1,
  149. "prev_events": [join_event.event_id],
  150. "auth_events": [],
  151. "origin_server_ts": self.clock.time_msec(),
  152. },
  153. room_version,
  154. )
  155. with LoggingContext("send_rejected"):
  156. d = run_in_background(self.handler.on_receive_pdu, OTHER_SERVER, ev)
  157. self.get_success(d)
  158. # that should have been rejected
  159. e = self.get_success(self.store.get_event(ev.event_id, allow_rejected=True))
  160. self.assertIsNotNone(e.rejected_reason)
  161. # ... and the state group should be the same as before
  162. sg2 = self.successResultOf(self.store._get_state_group_for_event(ev.event_id))
  163. self.assertEqual(sg, sg2)
  164. def test_backfill_floating_outlier_membership_auth(self):
  165. """
  166. As the local homeserver, check that we can properly process a federated
  167. event from the OTHER_SERVER with auth_events that include a floating
  168. membership event from the OTHER_SERVER.
  169. Regression test, see #10439.
  170. """
  171. OTHER_SERVER = "otherserver"
  172. OTHER_USER = "@otheruser:" + OTHER_SERVER
  173. # create the room
  174. user_id = self.register_user("kermit", "test")
  175. tok = self.login("kermit", "test")
  176. room_id = self.helper.create_room_as(
  177. room_creator=user_id,
  178. is_public=True,
  179. tok=tok,
  180. extra_content={
  181. "preset": "public_chat",
  182. },
  183. )
  184. room_version = self.get_success(self.store.get_room_version(room_id))
  185. prev_event_ids = self.get_success(self.store.get_prev_events_for_room(room_id))
  186. (
  187. most_recent_prev_event_id,
  188. most_recent_prev_event_depth,
  189. ) = self.get_success(self.store.get_max_depth_of(prev_event_ids))
  190. # mapping from (type, state_key) -> state_event_id
  191. prev_state_map = self.get_success(
  192. self.state_store.get_state_ids_for_event(most_recent_prev_event_id)
  193. )
  194. # List of state event ID's
  195. prev_state_ids = list(prev_state_map.values())
  196. auth_event_ids = prev_state_ids
  197. auth_events = list(
  198. self.get_success(self.store.get_events(auth_event_ids)).values()
  199. )
  200. # build a floating outlier member state event
  201. fake_prev_event_id = "$" + random_string(43)
  202. member_event_dict = {
  203. "type": EventTypes.Member,
  204. "content": {
  205. "membership": "join",
  206. },
  207. "state_key": OTHER_USER,
  208. "room_id": room_id,
  209. "sender": OTHER_USER,
  210. "depth": most_recent_prev_event_depth,
  211. "prev_events": [fake_prev_event_id],
  212. "origin_server_ts": self.clock.time_msec(),
  213. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  214. }
  215. builder = self.hs.get_event_builder_factory().for_room_version(
  216. room_version, member_event_dict
  217. )
  218. member_event = self.get_success(
  219. builder.build(
  220. prev_event_ids=member_event_dict["prev_events"],
  221. auth_event_ids=self._event_auth_handler.compute_auth_events(
  222. builder,
  223. prev_state_map,
  224. for_verification=False,
  225. ),
  226. depth=member_event_dict["depth"],
  227. )
  228. )
  229. # Override the signature added from "test" homeserver that we created the event with
  230. member_event.signatures = member_event_dict["signatures"]
  231. # Add the new member_event to the StateMap
  232. prev_state_map[
  233. (member_event.type, member_event.state_key)
  234. ] = member_event.event_id
  235. auth_events.append(member_event)
  236. # build and send an event authed based on the member event
  237. message_event_dict = {
  238. "type": EventTypes.Message,
  239. "content": {},
  240. "room_id": room_id,
  241. "sender": OTHER_USER,
  242. "depth": most_recent_prev_event_depth,
  243. "prev_events": prev_event_ids.copy(),
  244. "origin_server_ts": self.clock.time_msec(),
  245. "signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
  246. }
  247. builder = self.hs.get_event_builder_factory().for_room_version(
  248. room_version, message_event_dict
  249. )
  250. message_event = self.get_success(
  251. builder.build(
  252. prev_event_ids=message_event_dict["prev_events"],
  253. auth_event_ids=self._event_auth_handler.compute_auth_events(
  254. builder,
  255. prev_state_map,
  256. for_verification=False,
  257. ),
  258. depth=message_event_dict["depth"],
  259. )
  260. )
  261. # Override the signature added from "test" homeserver that we created the event with
  262. message_event.signatures = message_event_dict["signatures"]
  263. # Stub the /event_auth response from the OTHER_SERVER
  264. async def get_event_auth(
  265. destination: str, room_id: str, event_id: str
  266. ) -> List[EventBase]:
  267. return auth_events
  268. self.handler.federation_client.get_event_auth = get_event_auth
  269. with LoggingContext("receive_pdu"):
  270. # Fake the OTHER_SERVER federating the message event over to our local homeserver
  271. d = run_in_background(
  272. self.handler.on_receive_pdu, OTHER_SERVER, message_event
  273. )
  274. self.get_success(d)
  275. # Now try and get the events on our local homeserver
  276. stored_event = self.get_success(
  277. self.store.get_event(message_event.event_id, allow_none=True)
  278. )
  279. self.assertTrue(stored_event is not None)
  280. @unittest.override_config(
  281. {"rc_invites": {"per_user": {"per_second": 0.5, "burst_count": 3}}}
  282. )
  283. def test_invite_by_user_ratelimit(self):
  284. """Tests that invites from federation to a particular user are
  285. actually rate-limited.
  286. """
  287. other_server = "otherserver"
  288. other_user = "@otheruser:" + other_server
  289. # create the room
  290. user_id = self.register_user("kermit", "test")
  291. tok = self.login("kermit", "test")
  292. def create_invite():
  293. room_id = self.helper.create_room_as(room_creator=user_id, tok=tok)
  294. room_version = self.get_success(self.store.get_room_version(room_id))
  295. return event_from_pdu_json(
  296. {
  297. "type": EventTypes.Member,
  298. "content": {"membership": "invite"},
  299. "room_id": room_id,
  300. "sender": other_user,
  301. "state_key": "@user:test",
  302. "depth": 32,
  303. "prev_events": [],
  304. "auth_events": [],
  305. "origin_server_ts": self.clock.time_msec(),
  306. },
  307. room_version,
  308. )
  309. for _ in range(3):
  310. event = create_invite()
  311. self.get_success(
  312. self.handler.on_invite_request(
  313. other_server,
  314. event,
  315. event.room_version,
  316. )
  317. )
  318. event = create_invite()
  319. self.get_failure(
  320. self.handler.on_invite_request(
  321. other_server,
  322. event,
  323. event.room_version,
  324. ),
  325. exc=LimitExceededError,
  326. )
  327. def _build_and_send_join_event(self, other_server, other_user, room_id):
  328. join_event = self.get_success(
  329. self.handler.on_make_join_request(other_server, room_id, other_user)
  330. )
  331. # the auth code requires that a signature exists, but doesn't check that
  332. # signature... go figure.
  333. join_event.signatures[other_server] = {"x": "y"}
  334. with LoggingContext("send_join"):
  335. d = run_in_background(
  336. self.handler.on_send_membership_event, other_server, join_event
  337. )
  338. self.get_success(d)
  339. # sanity-check: the room should show that the new user is a member
  340. r = self.get_success(self.store.get_current_state_ids(room_id))
  341. self.assertEqual(r[(EventTypes.Member, other_user)], join_event.event_id)
  342. return join_event
  343. class EventFromPduTestCase(TestCase):
  344. def test_valid_json(self):
  345. """Valid JSON should be turned into an event."""
  346. ev = event_from_pdu_json(
  347. {
  348. "type": EventTypes.Message,
  349. "content": {"bool": True, "null": None, "int": 1, "str": "foobar"},
  350. "room_id": "!room:test",
  351. "sender": "@user:test",
  352. "depth": 1,
  353. "prev_events": [],
  354. "auth_events": [],
  355. "origin_server_ts": 1234,
  356. },
  357. RoomVersions.V6,
  358. )
  359. self.assertIsInstance(ev, EventBase)
  360. def test_invalid_numbers(self):
  361. """Invalid values for an integer should be rejected, all floats should be rejected."""
  362. for value in [
  363. -(2 ** 53),
  364. 2 ** 53,
  365. 1.0,
  366. float("inf"),
  367. float("-inf"),
  368. float("nan"),
  369. ]:
  370. with self.assertRaises(SynapseError):
  371. event_from_pdu_json(
  372. {
  373. "type": EventTypes.Message,
  374. "content": {"foo": value},
  375. "room_id": "!room:test",
  376. "sender": "@user:test",
  377. "depth": 1,
  378. "prev_events": [],
  379. "auth_events": [],
  380. "origin_server_ts": 1234,
  381. },
  382. RoomVersions.V6,
  383. )
  384. def test_invalid_nested(self):
  385. """List and dictionaries are recursively searched."""
  386. with self.assertRaises(SynapseError):
  387. event_from_pdu_json(
  388. {
  389. "type": EventTypes.Message,
  390. "content": {"foo": [{"bar": 2 ** 56}]},
  391. "room_id": "!room:test",
  392. "sender": "@user:test",
  393. "depth": 1,
  394. "prev_events": [],
  395. "auth_events": [],
  396. "origin_server_ts": 1234,
  397. },
  398. RoomVersions.V6,
  399. )