test_room_member.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. from unittest.mock import AsyncMock, patch
  2. from twisted.test.proto_helpers import MemoryReactor
  3. import synapse.rest.admin
  4. import synapse.rest.client.login
  5. import synapse.rest.client.room
  6. from synapse.api.constants import EventTypes, Membership
  7. from synapse.api.errors import LimitExceededError, SynapseError
  8. from synapse.crypto.event_signing import add_hashes_and_signatures
  9. from synapse.events import FrozenEventV3
  10. from synapse.federation.federation_client import SendJoinResult
  11. from synapse.server import HomeServer
  12. from synapse.types import UserID, create_requester
  13. from synapse.util import Clock
  14. from tests.replication._base import BaseMultiWorkerStreamTestCase
  15. from tests.server import make_request
  16. from tests.unittest import (
  17. FederatingHomeserverTestCase,
  18. HomeserverTestCase,
  19. override_config,
  20. )
  21. class TestJoinsLimitedByPerRoomRateLimiter(FederatingHomeserverTestCase):
  22. servlets = [
  23. synapse.rest.admin.register_servlets,
  24. synapse.rest.client.login.register_servlets,
  25. synapse.rest.client.room.register_servlets,
  26. ]
  27. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  28. self.handler = hs.get_room_member_handler()
  29. # Create three users.
  30. self.alice = self.register_user("alice", "pass")
  31. self.alice_token = self.login("alice", "pass")
  32. self.bob = self.register_user("bob", "pass")
  33. self.bob_token = self.login("bob", "pass")
  34. self.chris = self.register_user("chris", "pass")
  35. self.chris_token = self.login("chris", "pass")
  36. # Create a room on this homeserver. Note that this counts as a join: it
  37. # contributes to the rate limter's count of actions
  38. self.room_id = self.helper.create_room_as(self.alice, tok=self.alice_token)
  39. self.intially_unjoined_room_id = f"!example:{self.OTHER_SERVER_NAME}"
  40. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  41. def test_local_user_local_joins_contribute_to_limit_and_are_limited(self) -> None:
  42. # The rate limiter has accumulated one token from Alice's join after the create
  43. # event.
  44. # Try joining the room as Bob.
  45. self.get_success(
  46. self.handler.update_membership(
  47. requester=create_requester(self.bob),
  48. target=UserID.from_string(self.bob),
  49. room_id=self.room_id,
  50. action=Membership.JOIN,
  51. )
  52. )
  53. # The rate limiter bucket is full. A second join should be denied.
  54. self.get_failure(
  55. self.handler.update_membership(
  56. requester=create_requester(self.chris),
  57. target=UserID.from_string(self.chris),
  58. room_id=self.room_id,
  59. action=Membership.JOIN,
  60. ),
  61. LimitExceededError,
  62. )
  63. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  64. def test_local_user_profile_edits_dont_contribute_to_limit(self) -> None:
  65. # The rate limiter has accumulated one token from Alice's join after the create
  66. # event. Alice should still be able to change her displayname.
  67. self.get_success(
  68. self.handler.update_membership(
  69. requester=create_requester(self.alice),
  70. target=UserID.from_string(self.alice),
  71. room_id=self.room_id,
  72. action=Membership.JOIN,
  73. content={"displayname": "Alice Cooper"},
  74. )
  75. )
  76. # Still room in the limiter bucket. Chris's join should be accepted.
  77. self.get_success(
  78. self.handler.update_membership(
  79. requester=create_requester(self.chris),
  80. target=UserID.from_string(self.chris),
  81. room_id=self.room_id,
  82. action=Membership.JOIN,
  83. )
  84. )
  85. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 1}})
  86. def test_remote_joins_contribute_to_rate_limit(self) -> None:
  87. # Join once, to fill the rate limiter bucket.
  88. #
  89. # To do this we have to mock the responses from the remote homeserver.
  90. # We also patch out a bunch of event checks on our end. All we're really
  91. # trying to check here is that remote joins will bump the rate limter when
  92. # they are persisted.
  93. create_event_source = {
  94. "auth_events": [],
  95. "content": {
  96. "creator": f"@creator:{self.OTHER_SERVER_NAME}",
  97. "room_version": self.hs.config.server.default_room_version.identifier,
  98. },
  99. "depth": 0,
  100. "origin_server_ts": 0,
  101. "prev_events": [],
  102. "room_id": self.intially_unjoined_room_id,
  103. "sender": f"@creator:{self.OTHER_SERVER_NAME}",
  104. "state_key": "",
  105. "type": EventTypes.Create,
  106. }
  107. self.add_hashes_and_signatures_from_other_server(
  108. create_event_source,
  109. self.hs.config.server.default_room_version,
  110. )
  111. create_event = FrozenEventV3(
  112. create_event_source,
  113. self.hs.config.server.default_room_version,
  114. {},
  115. None,
  116. )
  117. join_event_source = {
  118. "auth_events": [create_event.event_id],
  119. "content": {"membership": "join"},
  120. "depth": 1,
  121. "origin_server_ts": 100,
  122. "prev_events": [create_event.event_id],
  123. "sender": self.bob,
  124. "state_key": self.bob,
  125. "room_id": self.intially_unjoined_room_id,
  126. "type": EventTypes.Member,
  127. }
  128. add_hashes_and_signatures(
  129. self.hs.config.server.default_room_version,
  130. join_event_source,
  131. self.hs.hostname,
  132. self.hs.signing_key,
  133. )
  134. join_event = FrozenEventV3(
  135. join_event_source,
  136. self.hs.config.server.default_room_version,
  137. {},
  138. None,
  139. )
  140. mock_make_membership_event = AsyncMock(
  141. return_value=(
  142. self.OTHER_SERVER_NAME,
  143. join_event,
  144. self.hs.config.server.default_room_version,
  145. )
  146. )
  147. mock_send_join = AsyncMock(
  148. return_value=SendJoinResult(
  149. join_event,
  150. self.OTHER_SERVER_NAME,
  151. state=[create_event],
  152. auth_chain=[create_event],
  153. partial_state=False,
  154. servers_in_room=frozenset(),
  155. )
  156. )
  157. with patch.object(
  158. self.handler.federation_handler.federation_client,
  159. "make_membership_event",
  160. mock_make_membership_event,
  161. ), patch.object(
  162. self.handler.federation_handler.federation_client,
  163. "send_join",
  164. mock_send_join,
  165. ), patch(
  166. "synapse.event_auth._is_membership_change_allowed",
  167. return_value=None,
  168. ), patch(
  169. "synapse.handlers.federation_event.check_state_dependent_auth_rules",
  170. return_value=None,
  171. ):
  172. self.get_success(
  173. self.handler.update_membership(
  174. requester=create_requester(self.bob),
  175. target=UserID.from_string(self.bob),
  176. room_id=self.intially_unjoined_room_id,
  177. action=Membership.JOIN,
  178. remote_room_hosts=[self.OTHER_SERVER_NAME],
  179. )
  180. )
  181. # Try to join as Chris. Should get denied.
  182. self.get_failure(
  183. self.handler.update_membership(
  184. requester=create_requester(self.chris),
  185. target=UserID.from_string(self.chris),
  186. room_id=self.intially_unjoined_room_id,
  187. action=Membership.JOIN,
  188. remote_room_hosts=[self.OTHER_SERVER_NAME],
  189. ),
  190. LimitExceededError,
  191. )
  192. # TODO: test that remote joins to a room are rate limited.
  193. # Could do this by setting the burst count to 1, then:
  194. # - remote-joining a room
  195. # - immediately leaving
  196. # - trying to remote-join again.
  197. class TestReplicatedJoinsLimitedByPerRoomRateLimiter(BaseMultiWorkerStreamTestCase):
  198. servlets = [
  199. synapse.rest.admin.register_servlets,
  200. synapse.rest.client.login.register_servlets,
  201. synapse.rest.client.room.register_servlets,
  202. ]
  203. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  204. self.handler = hs.get_room_member_handler()
  205. # Create three users.
  206. self.alice = self.register_user("alice", "pass")
  207. self.alice_token = self.login("alice", "pass")
  208. self.bob = self.register_user("bob", "pass")
  209. self.bob_token = self.login("bob", "pass")
  210. self.chris = self.register_user("chris", "pass")
  211. self.chris_token = self.login("chris", "pass")
  212. # Create a room on this homeserver.
  213. # Note that this counts as a
  214. self.room_id = self.helper.create_room_as(self.alice, tok=self.alice_token)
  215. self.intially_unjoined_room_id = "!example:otherhs"
  216. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  217. def test_local_users_joining_on_another_worker_contribute_to_rate_limit(
  218. self,
  219. ) -> None:
  220. # The rate limiter has accumulated one token from Alice's join after the create
  221. # event.
  222. self.replicate()
  223. # Spawn another worker and have bob join via it.
  224. worker_app = self.make_worker_hs(
  225. "synapse.app.generic_worker", extra_config={"worker_name": "other worker"}
  226. )
  227. worker_site = self._hs_to_site[worker_app]
  228. channel = make_request(
  229. self.reactor,
  230. worker_site,
  231. "POST",
  232. f"/_matrix/client/v3/rooms/{self.room_id}/join",
  233. access_token=self.bob_token,
  234. )
  235. self.assertEqual(channel.code, 200, channel.json_body)
  236. # wait for join to arrive over replication
  237. self.replicate()
  238. # Try to join as Chris on the worker. Should get denied because Alice
  239. # and Bob have both joined the room.
  240. self.get_failure(
  241. worker_app.get_room_member_handler().update_membership(
  242. requester=create_requester(self.chris),
  243. target=UserID.from_string(self.chris),
  244. room_id=self.room_id,
  245. action=Membership.JOIN,
  246. ),
  247. LimitExceededError,
  248. )
  249. # Try to join as Chris on the original worker. Should get denied because Alice
  250. # and Bob have both joined the room.
  251. self.get_failure(
  252. self.handler.update_membership(
  253. requester=create_requester(self.chris),
  254. target=UserID.from_string(self.chris),
  255. room_id=self.room_id,
  256. action=Membership.JOIN,
  257. ),
  258. LimitExceededError,
  259. )
  260. class RoomMemberMasterHandlerTestCase(HomeserverTestCase):
  261. servlets = [
  262. synapse.rest.admin.register_servlets,
  263. synapse.rest.client.login.register_servlets,
  264. synapse.rest.client.room.register_servlets,
  265. ]
  266. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  267. self.handler = hs.get_room_member_handler()
  268. self.store = hs.get_datastores().main
  269. # Create two users.
  270. self.alice = self.register_user("alice", "pass")
  271. self.alice_ID = UserID.from_string(self.alice)
  272. self.alice_token = self.login("alice", "pass")
  273. self.bob = self.register_user("bob", "pass")
  274. self.bob_ID = UserID.from_string(self.bob)
  275. self.bob_token = self.login("bob", "pass")
  276. # Create a room on this homeserver.
  277. self.room_id = self.helper.create_room_as(self.alice, tok=self.alice_token)
  278. def test_leave_and_forget(self) -> None:
  279. """Tests that forget a room is successfully. The test is performed with two users,
  280. as forgetting by the last user respectively after all users had left the
  281. is a special edge case."""
  282. self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
  283. # alice is not the last room member that leaves and forgets the room
  284. self.helper.leave(self.room_id, user=self.alice, tok=self.alice_token)
  285. self.get_success(self.handler.forget(self.alice_ID, self.room_id))
  286. self.assertTrue(
  287. self.get_success(self.store.did_forget(self.alice, self.room_id))
  288. )
  289. # the server has not forgotten the room
  290. self.assertFalse(
  291. self.get_success(self.store.is_locally_forgotten_room(self.room_id))
  292. )
  293. def test_leave_and_unforget(self) -> None:
  294. """Tests if rejoining a room unforgets the room, so that it shows up in sync again."""
  295. self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
  296. # alice is not the last room member that leaves and forgets the room
  297. self.helper.leave(self.room_id, user=self.alice, tok=self.alice_token)
  298. self.get_success(self.handler.forget(self.alice_ID, self.room_id))
  299. self.assertTrue(
  300. self.get_success(self.store.did_forget(self.alice, self.room_id))
  301. )
  302. self.helper.join(self.room_id, user=self.alice, tok=self.alice_token)
  303. self.assertFalse(
  304. self.get_success(self.store.did_forget(self.alice, self.room_id))
  305. )
  306. # the server has not forgotten the room
  307. self.assertFalse(
  308. self.get_success(self.store.is_locally_forgotten_room(self.room_id))
  309. )
  310. @override_config({"forget_rooms_on_leave": True})
  311. def test_leave_and_auto_forget(self) -> None:
  312. """Tests the `forget_rooms_on_leave` config option."""
  313. self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
  314. # alice is not the last room member that leaves and forgets the room
  315. self.helper.leave(self.room_id, user=self.alice, tok=self.alice_token)
  316. self.assertTrue(
  317. self.get_success(self.store.did_forget(self.alice, self.room_id))
  318. )
  319. def test_leave_and_forget_last_user(self) -> None:
  320. """Tests that forget a room is successfully when the last user has left the room."""
  321. # alice is the last room member that leaves and forgets the room
  322. self.helper.leave(self.room_id, user=self.alice, tok=self.alice_token)
  323. self.get_success(self.handler.forget(self.alice_ID, self.room_id))
  324. self.assertTrue(
  325. self.get_success(self.store.did_forget(self.alice, self.room_id))
  326. )
  327. # the server has forgotten the room
  328. self.assertTrue(
  329. self.get_success(self.store.is_locally_forgotten_room(self.room_id))
  330. )
  331. def test_forget_when_not_left(self) -> None:
  332. """Tests that a user cannot not forgets a room that has not left."""
  333. self.get_failure(self.handler.forget(self.alice_ID, self.room_id), SynapseError)
  334. def test_rejoin_forgotten_by_user(self) -> None:
  335. """Test that a user that has forgotten a room can do a re-join.
  336. The room was not forgotten from the local server.
  337. One local user is still member of the room."""
  338. self.helper.join(self.room_id, user=self.bob, tok=self.bob_token)
  339. self.helper.leave(self.room_id, user=self.alice, tok=self.alice_token)
  340. self.get_success(self.handler.forget(self.alice_ID, self.room_id))
  341. self.assertTrue(
  342. self.get_success(self.store.did_forget(self.alice, self.room_id))
  343. )
  344. # the server has not forgotten the room
  345. self.assertFalse(
  346. self.get_success(self.store.is_locally_forgotten_room(self.room_id))
  347. )
  348. self.helper.join(self.room_id, user=self.alice, tok=self.alice_token)
  349. # TODO: A join to a room does not invalidate the forgotten cache
  350. # see https://github.com/matrix-org/synapse/issues/13262
  351. self.store.did_forget.invalidate_all()
  352. self.assertFalse(
  353. self.get_success(self.store.did_forget(self.alice, self.room_id))
  354. )