test_server_notice.py 17 KB

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