test_room_member.py 15 KB

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