test_room_member.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. from http import HTTPStatus
  2. from unittest.mock import Mock, patch
  3. from twisted.test.proto_helpers import MemoryReactor
  4. import synapse.rest.admin
  5. import synapse.rest.client.login
  6. import synapse.rest.client.room
  7. from synapse.api.constants import EventTypes, Membership
  8. from synapse.api.errors import LimitExceededError
  9. from synapse.crypto.event_signing import add_hashes_and_signatures
  10. from synapse.events import FrozenEventV3
  11. from synapse.federation.federation_client import SendJoinResult
  12. from synapse.server import HomeServer
  13. from synapse.types import UserID, create_requester
  14. from synapse.util import Clock
  15. from tests.replication._base import RedisMultiWorkerStreamTestCase
  16. from tests.server import make_request
  17. from tests.test_utils import make_awaitable
  18. from tests.unittest import FederatingHomeserverTestCase, override_config
  19. class TestJoinsLimitedByPerRoomRateLimiter(FederatingHomeserverTestCase):
  20. servlets = [
  21. synapse.rest.admin.register_servlets,
  22. synapse.rest.client.login.register_servlets,
  23. synapse.rest.client.room.register_servlets,
  24. ]
  25. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  26. self.handler = hs.get_room_member_handler()
  27. # Create three users.
  28. self.alice = self.register_user("alice", "pass")
  29. self.alice_token = self.login("alice", "pass")
  30. self.bob = self.register_user("bob", "pass")
  31. self.bob_token = self.login("bob", "pass")
  32. self.chris = self.register_user("chris", "pass")
  33. self.chris_token = self.login("chris", "pass")
  34. # Create a room on this homeserver. Note that this counts as a join: it
  35. # contributes to the rate limter's count of actions
  36. self.room_id = self.helper.create_room_as(self.alice, tok=self.alice_token)
  37. self.intially_unjoined_room_id = f"!example:{self.OTHER_SERVER_NAME}"
  38. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  39. def test_local_user_local_joins_contribute_to_limit_and_are_limited(self) -> None:
  40. # The rate limiter has accumulated one token from Alice's join after the create
  41. # event.
  42. # Try joining the room as Bob.
  43. self.get_success(
  44. self.handler.update_membership(
  45. requester=create_requester(self.bob),
  46. target=UserID.from_string(self.bob),
  47. room_id=self.room_id,
  48. action=Membership.JOIN,
  49. )
  50. )
  51. # The rate limiter bucket is full. A second join should be denied.
  52. self.get_failure(
  53. self.handler.update_membership(
  54. requester=create_requester(self.chris),
  55. target=UserID.from_string(self.chris),
  56. room_id=self.room_id,
  57. action=Membership.JOIN,
  58. ),
  59. LimitExceededError,
  60. )
  61. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  62. def test_local_user_profile_edits_dont_contribute_to_limit(self) -> None:
  63. # The rate limiter has accumulated one token from Alice's join after the create
  64. # event. Alice should still be able to change her displayname.
  65. self.get_success(
  66. self.handler.update_membership(
  67. requester=create_requester(self.alice),
  68. target=UserID.from_string(self.alice),
  69. room_id=self.room_id,
  70. action=Membership.JOIN,
  71. content={"displayname": "Alice Cooper"},
  72. )
  73. )
  74. # Still room in the limiter bucket. Chris's join should be accepted.
  75. self.get_success(
  76. self.handler.update_membership(
  77. requester=create_requester(self.chris),
  78. target=UserID.from_string(self.chris),
  79. room_id=self.room_id,
  80. action=Membership.JOIN,
  81. )
  82. )
  83. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 1}})
  84. def test_remote_joins_contribute_to_rate_limit(self) -> None:
  85. # Join once, to fill the rate limiter bucket.
  86. #
  87. # To do this we have to mock the responses from the remote homeserver.
  88. # We also patch out a bunch of event checks on our end. All we're really
  89. # trying to check here is that remote joins will bump the rate limter when
  90. # they are persisted.
  91. create_event_source = {
  92. "auth_events": [],
  93. "content": {
  94. "creator": f"@creator:{self.OTHER_SERVER_NAME}",
  95. "room_version": self.hs.config.server.default_room_version.identifier,
  96. },
  97. "depth": 0,
  98. "origin_server_ts": 0,
  99. "prev_events": [],
  100. "room_id": self.intially_unjoined_room_id,
  101. "sender": f"@creator:{self.OTHER_SERVER_NAME}",
  102. "state_key": "",
  103. "type": EventTypes.Create,
  104. }
  105. self.add_hashes_and_signatures_from_other_server(
  106. create_event_source,
  107. self.hs.config.server.default_room_version,
  108. )
  109. create_event = FrozenEventV3(
  110. create_event_source,
  111. self.hs.config.server.default_room_version,
  112. {},
  113. None,
  114. )
  115. join_event_source = {
  116. "auth_events": [create_event.event_id],
  117. "content": {"membership": "join"},
  118. "depth": 1,
  119. "origin_server_ts": 100,
  120. "prev_events": [create_event.event_id],
  121. "sender": self.bob,
  122. "state_key": self.bob,
  123. "room_id": self.intially_unjoined_room_id,
  124. "type": EventTypes.Member,
  125. }
  126. add_hashes_and_signatures(
  127. self.hs.config.server.default_room_version,
  128. join_event_source,
  129. self.hs.hostname,
  130. self.hs.signing_key,
  131. )
  132. join_event = FrozenEventV3(
  133. join_event_source,
  134. self.hs.config.server.default_room_version,
  135. {},
  136. None,
  137. )
  138. mock_make_membership_event = Mock(
  139. return_value=make_awaitable(
  140. (
  141. self.OTHER_SERVER_NAME,
  142. join_event,
  143. self.hs.config.server.default_room_version,
  144. )
  145. )
  146. )
  147. mock_send_join = Mock(
  148. return_value=make_awaitable(
  149. SendJoinResult(
  150. join_event,
  151. self.OTHER_SERVER_NAME,
  152. state=[create_event],
  153. auth_chain=[create_event],
  154. partial_state=False,
  155. servers_in_room=[],
  156. )
  157. )
  158. )
  159. with patch.object(
  160. self.handler.federation_handler.federation_client,
  161. "make_membership_event",
  162. mock_make_membership_event,
  163. ), patch.object(
  164. self.handler.federation_handler.federation_client,
  165. "send_join",
  166. mock_send_join,
  167. ), patch(
  168. "synapse.event_auth._is_membership_change_allowed",
  169. return_value=None,
  170. ), patch(
  171. "synapse.handlers.federation_event.check_state_dependent_auth_rules",
  172. return_value=None,
  173. ):
  174. self.get_success(
  175. self.handler.update_membership(
  176. requester=create_requester(self.bob),
  177. target=UserID.from_string(self.bob),
  178. room_id=self.intially_unjoined_room_id,
  179. action=Membership.JOIN,
  180. remote_room_hosts=[self.OTHER_SERVER_NAME],
  181. )
  182. )
  183. # Try to join as Chris. Should get denied.
  184. self.get_failure(
  185. self.handler.update_membership(
  186. requester=create_requester(self.chris),
  187. target=UserID.from_string(self.chris),
  188. room_id=self.intially_unjoined_room_id,
  189. action=Membership.JOIN,
  190. remote_room_hosts=[self.OTHER_SERVER_NAME],
  191. ),
  192. LimitExceededError,
  193. )
  194. # TODO: test that remote joins to a room are rate limited.
  195. # Could do this by setting the burst count to 1, then:
  196. # - remote-joining a room
  197. # - immediately leaving
  198. # - trying to remote-join again.
  199. class TestReplicatedJoinsLimitedByPerRoomRateLimiter(RedisMultiWorkerStreamTestCase):
  200. servlets = [
  201. synapse.rest.admin.register_servlets,
  202. synapse.rest.client.login.register_servlets,
  203. synapse.rest.client.room.register_servlets,
  204. ]
  205. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  206. self.handler = hs.get_room_member_handler()
  207. # Create three users.
  208. self.alice = self.register_user("alice", "pass")
  209. self.alice_token = self.login("alice", "pass")
  210. self.bob = self.register_user("bob", "pass")
  211. self.bob_token = self.login("bob", "pass")
  212. self.chris = self.register_user("chris", "pass")
  213. self.chris_token = self.login("chris", "pass")
  214. # Create a room on this homeserver.
  215. # Note that this counts as a
  216. self.room_id = self.helper.create_room_as(self.alice, tok=self.alice_token)
  217. self.intially_unjoined_room_id = "!example:otherhs"
  218. @override_config({"rc_joins_per_room": {"per_second": 0, "burst_count": 2}})
  219. def test_local_users_joining_on_another_worker_contribute_to_rate_limit(
  220. self,
  221. ) -> None:
  222. # The rate limiter has accumulated one token from Alice's join after the create
  223. # event.
  224. self.replicate()
  225. # Spawn another worker and have bob join via it.
  226. worker_app = self.make_worker_hs(
  227. "synapse.app.generic_worker", extra_config={"worker_name": "other worker"}
  228. )
  229. worker_site = self._hs_to_site[worker_app]
  230. channel = make_request(
  231. self.reactor,
  232. worker_site,
  233. "POST",
  234. f"/_matrix/client/v3/rooms/{self.room_id}/join",
  235. access_token=self.bob_token,
  236. )
  237. self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
  238. # wait for join to arrive over replication
  239. self.replicate()
  240. # Try to join as Chris on the worker. Should get denied because Alice
  241. # and Bob have both joined the room.
  242. self.get_failure(
  243. worker_app.get_room_member_handler().update_membership(
  244. requester=create_requester(self.chris),
  245. target=UserID.from_string(self.chris),
  246. room_id=self.room_id,
  247. action=Membership.JOIN,
  248. ),
  249. LimitExceededError,
  250. )
  251. # Try to join as Chris on the original worker. Should get denied because Alice
  252. # and Bob have both joined the room.
  253. self.get_failure(
  254. self.handler.update_membership(
  255. requester=create_requester(self.chris),
  256. target=UserID.from_string(self.chris),
  257. room_id=self.room_id,
  258. action=Membership.JOIN,
  259. ),
  260. LimitExceededError,
  261. )