test_server_notice.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. # Copyright 2021 Dirk Klimpel
  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. from typing import List
  15. import synapse.rest.admin
  16. from synapse.api.errors import Codes
  17. from synapse.rest.client import login, room, sync
  18. from synapse.storage.roommember import RoomsForUser
  19. from synapse.types import JsonDict
  20. from tests import unittest
  21. from tests.unittest import override_config
  22. class ServerNoticeTestCase(unittest.HomeserverTestCase):
  23. servlets = [
  24. synapse.rest.admin.register_servlets,
  25. login.register_servlets,
  26. room.register_servlets,
  27. sync.register_servlets,
  28. ]
  29. def prepare(self, reactor, clock, hs):
  30. self.store = hs.get_datastore()
  31. self.room_shutdown_handler = hs.get_room_shutdown_handler()
  32. self.pagination_handler = hs.get_pagination_handler()
  33. self.server_notices_manager = self.hs.get_server_notices_manager()
  34. # Create user
  35. self.admin_user = self.register_user("admin", "pass", admin=True)
  36. self.admin_user_tok = self.login("admin", "pass")
  37. self.other_user = self.register_user("user", "pass")
  38. self.other_user_token = self.login("user", "pass")
  39. self.url = "/_synapse/admin/v1/send_server_notice"
  40. def test_no_auth(self):
  41. """Try to send a server notice without authentication."""
  42. channel = self.make_request("POST", self.url)
  43. self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"])
  44. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  45. def test_requester_is_no_admin(self):
  46. """If the user is not a server admin, an error is returned."""
  47. channel = self.make_request(
  48. "POST",
  49. self.url,
  50. access_token=self.other_user_token,
  51. )
  52. self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"])
  53. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  54. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  55. def test_user_does_not_exist(self):
  56. """Tests that a lookup for a user that does not exist returns a 404"""
  57. channel = self.make_request(
  58. "POST",
  59. self.url,
  60. access_token=self.admin_user_tok,
  61. content={"user_id": "@unknown_person:test", "content": ""},
  62. )
  63. self.assertEqual(404, channel.code, msg=channel.json_body)
  64. self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
  65. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  66. def test_user_is_not_local(self):
  67. """
  68. Tests that a lookup for a user that is not a local returns a 400
  69. """
  70. channel = self.make_request(
  71. "POST",
  72. self.url,
  73. access_token=self.admin_user_tok,
  74. content={
  75. "user_id": "@unknown_person:unknown_domain",
  76. "content": "",
  77. },
  78. )
  79. self.assertEqual(400, channel.code, msg=channel.json_body)
  80. self.assertEqual(
  81. "Server notices can only be sent to local users", channel.json_body["error"]
  82. )
  83. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  84. def test_invalid_parameter(self):
  85. """If parameters are invalid, an error is returned."""
  86. # no content, no user
  87. channel = self.make_request(
  88. "POST",
  89. self.url,
  90. access_token=self.admin_user_tok,
  91. )
  92. self.assertEqual(400, channel.code, msg=channel.json_body)
  93. self.assertEqual(Codes.NOT_JSON, channel.json_body["errcode"])
  94. # no content
  95. channel = self.make_request(
  96. "POST",
  97. self.url,
  98. access_token=self.admin_user_tok,
  99. content={"user_id": self.other_user},
  100. )
  101. self.assertEqual(400, channel.code, msg=channel.json_body)
  102. self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
  103. # no body
  104. channel = self.make_request(
  105. "POST",
  106. self.url,
  107. access_token=self.admin_user_tok,
  108. content={"user_id": self.other_user, "content": ""},
  109. )
  110. self.assertEqual(400, channel.code, msg=channel.json_body)
  111. self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
  112. self.assertEqual("'body' not in content", channel.json_body["error"])
  113. # no msgtype
  114. channel = self.make_request(
  115. "POST",
  116. self.url,
  117. access_token=self.admin_user_tok,
  118. content={"user_id": self.other_user, "content": {"body": ""}},
  119. )
  120. self.assertEqual(400, channel.code, msg=channel.json_body)
  121. self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
  122. self.assertEqual("'msgtype' not in content", channel.json_body["error"])
  123. def test_server_notice_disabled(self):
  124. """Tests that server returns error if server notice is disabled"""
  125. channel = self.make_request(
  126. "POST",
  127. self.url,
  128. access_token=self.admin_user_tok,
  129. content={
  130. "user_id": self.other_user,
  131. "content": "",
  132. },
  133. )
  134. self.assertEqual(400, channel.code, msg=channel.json_body)
  135. self.assertEqual(Codes.UNKNOWN, channel.json_body["errcode"])
  136. self.assertEqual(
  137. "Server notices are not enabled on this server", channel.json_body["error"]
  138. )
  139. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  140. def test_send_server_notice(self):
  141. """
  142. Tests that sending two server notices is successfully,
  143. the server uses the same room and do not send messages twice.
  144. """
  145. # user has no room memberships
  146. self._check_invite_and_join_status(self.other_user, 0, 0)
  147. # send first message
  148. channel = self.make_request(
  149. "POST",
  150. self.url,
  151. access_token=self.admin_user_tok,
  152. content={
  153. "user_id": self.other_user,
  154. "content": {"msgtype": "m.text", "body": "test msg one"},
  155. },
  156. )
  157. self.assertEqual(200, channel.code, msg=channel.json_body)
  158. # user has one invite
  159. invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
  160. room_id = invited_rooms[0].room_id
  161. # user joins the room and is member now
  162. self.helper.join(room=room_id, user=self.other_user, tok=self.other_user_token)
  163. self._check_invite_and_join_status(self.other_user, 0, 1)
  164. # get messages
  165. messages = self._sync_and_get_messages(room_id, self.other_user_token)
  166. self.assertEqual(len(messages), 1)
  167. self.assertEqual(messages[0]["content"]["body"], "test msg one")
  168. self.assertEqual(messages[0]["sender"], "@notices:test")
  169. # invalidate cache of server notices room_ids
  170. self.get_success(
  171. self.server_notices_manager.get_or_create_notice_room_for_user.invalidate_all()
  172. )
  173. # send second message
  174. channel = self.make_request(
  175. "POST",
  176. self.url,
  177. access_token=self.admin_user_tok,
  178. content={
  179. "user_id": self.other_user,
  180. "content": {"msgtype": "m.text", "body": "test msg two"},
  181. },
  182. )
  183. self.assertEqual(200, channel.code, msg=channel.json_body)
  184. # user has no new invites or memberships
  185. self._check_invite_and_join_status(self.other_user, 0, 1)
  186. # get messages
  187. messages = self._sync_and_get_messages(room_id, self.other_user_token)
  188. self.assertEqual(len(messages), 2)
  189. self.assertEqual(messages[0]["content"]["body"], "test msg one")
  190. self.assertEqual(messages[0]["sender"], "@notices:test")
  191. self.assertEqual(messages[1]["content"]["body"], "test msg two")
  192. self.assertEqual(messages[1]["sender"], "@notices:test")
  193. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  194. def test_send_server_notice_leave_room(self):
  195. """
  196. Tests that sending a server notices is successfully.
  197. The user leaves the room and the second message appears
  198. in a new room.
  199. """
  200. # user has no room memberships
  201. self._check_invite_and_join_status(self.other_user, 0, 0)
  202. # send first message
  203. channel = self.make_request(
  204. "POST",
  205. self.url,
  206. access_token=self.admin_user_tok,
  207. content={
  208. "user_id": self.other_user,
  209. "content": {"msgtype": "m.text", "body": "test msg one"},
  210. },
  211. )
  212. self.assertEqual(200, channel.code, msg=channel.json_body)
  213. # user has one invite
  214. invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
  215. first_room_id = invited_rooms[0].room_id
  216. # user joins the room and is member now
  217. self.helper.join(
  218. room=first_room_id, user=self.other_user, tok=self.other_user_token
  219. )
  220. self._check_invite_and_join_status(self.other_user, 0, 1)
  221. # get messages
  222. messages = self._sync_and_get_messages(first_room_id, self.other_user_token)
  223. self.assertEqual(len(messages), 1)
  224. self.assertEqual(messages[0]["content"]["body"], "test msg one")
  225. self.assertEqual(messages[0]["sender"], "@notices:test")
  226. # user leaves the romm
  227. self.helper.leave(
  228. room=first_room_id, user=self.other_user, tok=self.other_user_token
  229. )
  230. # user is not member anymore
  231. self._check_invite_and_join_status(self.other_user, 0, 0)
  232. # invalidate cache of server notices room_ids
  233. # if server tries to send to a cached room_id the user gets the message
  234. # in old room
  235. self.get_success(
  236. self.server_notices_manager.get_or_create_notice_room_for_user.invalidate_all()
  237. )
  238. # send second message
  239. channel = self.make_request(
  240. "POST",
  241. self.url,
  242. access_token=self.admin_user_tok,
  243. content={
  244. "user_id": self.other_user,
  245. "content": {"msgtype": "m.text", "body": "test msg two"},
  246. },
  247. )
  248. self.assertEqual(200, channel.code, msg=channel.json_body)
  249. # user has one invite
  250. invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
  251. second_room_id = invited_rooms[0].room_id
  252. # user joins the room and is member now
  253. self.helper.join(
  254. room=second_room_id, user=self.other_user, tok=self.other_user_token
  255. )
  256. self._check_invite_and_join_status(self.other_user, 0, 1)
  257. # get messages
  258. messages = self._sync_and_get_messages(second_room_id, self.other_user_token)
  259. self.assertEqual(len(messages), 1)
  260. self.assertEqual(messages[0]["content"]["body"], "test msg two")
  261. self.assertEqual(messages[0]["sender"], "@notices:test")
  262. # room has the same id
  263. self.assertNotEqual(first_room_id, second_room_id)
  264. @override_config({"server_notices": {"system_mxid_localpart": "notices"}})
  265. def test_send_server_notice_delete_room(self):
  266. """
  267. Tests that the user get server notice in a new room
  268. after the first server notice room was deleted.
  269. """
  270. # user has no room memberships
  271. self._check_invite_and_join_status(self.other_user, 0, 0)
  272. # send first message
  273. channel = self.make_request(
  274. "POST",
  275. self.url,
  276. access_token=self.admin_user_tok,
  277. content={
  278. "user_id": self.other_user,
  279. "content": {"msgtype": "m.text", "body": "test msg one"},
  280. },
  281. )
  282. self.assertEqual(200, channel.code, msg=channel.json_body)
  283. # user has one invite
  284. invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
  285. first_room_id = invited_rooms[0].room_id
  286. # user joins the room and is member now
  287. self.helper.join(
  288. room=first_room_id, user=self.other_user, tok=self.other_user_token
  289. )
  290. self._check_invite_and_join_status(self.other_user, 0, 1)
  291. # get messages
  292. messages = self._sync_and_get_messages(first_room_id, self.other_user_token)
  293. self.assertEqual(len(messages), 1)
  294. self.assertEqual(messages[0]["content"]["body"], "test msg one")
  295. self.assertEqual(messages[0]["sender"], "@notices:test")
  296. # shut down and purge room
  297. self.get_success(
  298. self.room_shutdown_handler.shutdown_room(first_room_id, self.admin_user)
  299. )
  300. self.get_success(self.pagination_handler.purge_room(first_room_id))
  301. # user is not member anymore
  302. self._check_invite_and_join_status(self.other_user, 0, 0)
  303. # It doesn't really matter what API we use here, we just want to assert
  304. # that the room doesn't exist.
  305. summary = self.get_success(self.store.get_room_summary(first_room_id))
  306. # The summary should be empty since the room doesn't exist.
  307. self.assertEqual(summary, {})
  308. # invalidate cache of server notices room_ids
  309. # if server tries to send to a cached room_id it gives an error
  310. self.get_success(
  311. self.server_notices_manager.get_or_create_notice_room_for_user.invalidate_all()
  312. )
  313. # send second message
  314. channel = self.make_request(
  315. "POST",
  316. self.url,
  317. access_token=self.admin_user_tok,
  318. content={
  319. "user_id": self.other_user,
  320. "content": {"msgtype": "m.text", "body": "test msg two"},
  321. },
  322. )
  323. self.assertEqual(200, channel.code, msg=channel.json_body)
  324. # user has one invite
  325. invited_rooms = self._check_invite_and_join_status(self.other_user, 1, 0)
  326. second_room_id = invited_rooms[0].room_id
  327. # user joins the room and is member now
  328. self.helper.join(
  329. room=second_room_id, user=self.other_user, tok=self.other_user_token
  330. )
  331. self._check_invite_and_join_status(self.other_user, 0, 1)
  332. # get message
  333. messages = self._sync_and_get_messages(second_room_id, self.other_user_token)
  334. self.assertEqual(len(messages), 1)
  335. self.assertEqual(messages[0]["content"]["body"], "test msg two")
  336. self.assertEqual(messages[0]["sender"], "@notices:test")
  337. # second room has new ID
  338. self.assertNotEqual(first_room_id, second_room_id)
  339. def _check_invite_and_join_status(
  340. self, user_id: str, expected_invites: int, expected_memberships: int
  341. ) -> RoomsForUser:
  342. """Check invite and room membership status of a user.
  343. Args
  344. user_id: user to check
  345. expected_invites: number of expected invites of this user
  346. expected_memberships: number of expected room memberships of this user
  347. Returns
  348. room_ids from the rooms that the user is invited
  349. """
  350. invited_rooms = self.get_success(
  351. self.store.get_invited_rooms_for_local_user(user_id)
  352. )
  353. self.assertEqual(expected_invites, len(invited_rooms))
  354. room_ids = self.get_success(self.store.get_rooms_for_user(user_id))
  355. self.assertEqual(expected_memberships, len(room_ids))
  356. return invited_rooms
  357. def _sync_and_get_messages(self, room_id: str, token: str) -> List[JsonDict]:
  358. """
  359. Do a sync and get messages of a room.
  360. Args
  361. room_id: room that contains the messages
  362. token: access token of user
  363. Returns
  364. list of messages contained in the room
  365. """
  366. channel = self.make_request(
  367. "GET", "/_matrix/client/r0/sync", access_token=token
  368. )
  369. self.assertEqual(channel.code, 200)
  370. # Get the messages
  371. room = channel.json_body["rooms"]["join"][room_id]
  372. messages = [
  373. x for x in room["timeline"]["events"] if x["type"] == "m.room.message"
  374. ]
  375. return messages