test_email.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. # Copyright 2018 New Vector
  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. import os
  15. import attr
  16. import pkg_resources
  17. from twisted.internet.defer import Deferred
  18. import synapse.rest.admin
  19. from synapse.api.errors import Codes, SynapseError
  20. from synapse.rest.client import login, room
  21. from tests.unittest import HomeserverTestCase
  22. @attr.s
  23. class _User:
  24. "Helper wrapper for user ID and access token"
  25. id = attr.ib()
  26. token = attr.ib()
  27. class EmailPusherTests(HomeserverTestCase):
  28. servlets = [
  29. synapse.rest.admin.register_servlets_for_client_rest_resource,
  30. room.register_servlets,
  31. login.register_servlets,
  32. ]
  33. user_id = True
  34. hijack_auth = False
  35. def make_homeserver(self, reactor, clock):
  36. config = self.default_config()
  37. config["email"] = {
  38. "enable_notifs": True,
  39. "template_dir": os.path.abspath(
  40. pkg_resources.resource_filename("synapse", "res/templates")
  41. ),
  42. "expiry_template_html": "notice_expiry.html",
  43. "expiry_template_text": "notice_expiry.txt",
  44. "notif_template_html": "notif_mail.html",
  45. "notif_template_text": "notif_mail.txt",
  46. "smtp_host": "127.0.0.1",
  47. "smtp_port": 20,
  48. "require_transport_security": False,
  49. "smtp_user": None,
  50. "smtp_pass": None,
  51. "app_name": "Matrix",
  52. "notif_from": "test@example.com",
  53. "riot_base_url": None,
  54. }
  55. config["public_baseurl"] = "aaa"
  56. config["start_pushers"] = True
  57. hs = self.setup_test_homeserver(config=config)
  58. # List[Tuple[Deferred, args, kwargs]]
  59. self.email_attempts = []
  60. def sendmail(*args, **kwargs):
  61. d = Deferred()
  62. self.email_attempts.append((d, args, kwargs))
  63. return d
  64. hs.get_send_email_handler()._sendmail = sendmail
  65. return hs
  66. def prepare(self, reactor, clock, hs):
  67. # Register the user who gets notified
  68. self.user_id = self.register_user("user", "pass")
  69. self.access_token = self.login("user", "pass")
  70. # Register other users
  71. self.others = [
  72. _User(
  73. id=self.register_user("otheruser1", "pass"),
  74. token=self.login("otheruser1", "pass"),
  75. ),
  76. _User(
  77. id=self.register_user("otheruser2", "pass"),
  78. token=self.login("otheruser2", "pass"),
  79. ),
  80. ]
  81. # Register the pusher
  82. user_tuple = self.get_success(
  83. self.hs.get_datastore().get_user_by_access_token(self.access_token)
  84. )
  85. self.token_id = user_tuple.token_id
  86. # We need to add email to account before we can create a pusher.
  87. self.get_success(
  88. hs.get_datastore().user_add_threepid(
  89. self.user_id, "email", "a@example.com", 0, 0
  90. )
  91. )
  92. self.pusher = self.get_success(
  93. self.hs.get_pusherpool().add_pusher(
  94. user_id=self.user_id,
  95. access_token=self.token_id,
  96. kind="email",
  97. app_id="m.email",
  98. app_display_name="Email Notifications",
  99. device_display_name="a@example.com",
  100. pushkey="a@example.com",
  101. lang=None,
  102. data={},
  103. )
  104. )
  105. self.auth_handler = hs.get_auth_handler()
  106. def test_need_validated_email(self):
  107. """Test that we can only add an email pusher if the user has validated
  108. their email.
  109. """
  110. with self.assertRaises(SynapseError) as cm:
  111. self.get_success_or_raise(
  112. self.hs.get_pusherpool().add_pusher(
  113. user_id=self.user_id,
  114. access_token=self.token_id,
  115. kind="email",
  116. app_id="m.email",
  117. app_display_name="Email Notifications",
  118. device_display_name="b@example.com",
  119. pushkey="b@example.com",
  120. lang=None,
  121. data={},
  122. )
  123. )
  124. self.assertEqual(400, cm.exception.code)
  125. self.assertEqual(Codes.THREEPID_NOT_FOUND, cm.exception.errcode)
  126. def test_simple_sends_email(self):
  127. # Create a simple room with two users
  128. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  129. self.helper.invite(
  130. room=room, src=self.user_id, tok=self.access_token, targ=self.others[0].id
  131. )
  132. self.helper.join(room=room, user=self.others[0].id, tok=self.others[0].token)
  133. # The other user sends a single message.
  134. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  135. # We should get emailed about that message
  136. self._check_for_mail()
  137. # The other user sends multiple messages.
  138. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  139. self.helper.send(room, body="There!", tok=self.others[0].token)
  140. self._check_for_mail()
  141. def test_invite_sends_email(self):
  142. # Create a room and invite the user to it
  143. room = self.helper.create_room_as(self.others[0].id, tok=self.others[0].token)
  144. self.helper.invite(
  145. room=room,
  146. src=self.others[0].id,
  147. tok=self.others[0].token,
  148. targ=self.user_id,
  149. )
  150. # We should get emailed about the invite
  151. self._check_for_mail()
  152. def test_invite_to_empty_room_sends_email(self):
  153. # Create a room and invite the user to it
  154. room = self.helper.create_room_as(self.others[0].id, tok=self.others[0].token)
  155. self.helper.invite(
  156. room=room,
  157. src=self.others[0].id,
  158. tok=self.others[0].token,
  159. targ=self.user_id,
  160. )
  161. # Then have the original user leave
  162. self.helper.leave(room, self.others[0].id, tok=self.others[0].token)
  163. # We should get emailed about the invite
  164. self._check_for_mail()
  165. def test_multiple_members_email(self):
  166. # We want to test multiple notifications, so we pause processing of push
  167. # while we send messages.
  168. self.pusher._pause_processing()
  169. # Create a simple room with multiple other users
  170. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  171. for other in self.others:
  172. self.helper.invite(
  173. room=room, src=self.user_id, tok=self.access_token, targ=other.id
  174. )
  175. self.helper.join(room=room, user=other.id, tok=other.token)
  176. # The other users send some messages
  177. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  178. self.helper.send(room, body="There!", tok=self.others[1].token)
  179. self.helper.send(room, body="There!", tok=self.others[1].token)
  180. # Nothing should have happened yet, as we're paused.
  181. assert not self.email_attempts
  182. self.pusher._resume_processing()
  183. # We should get emailed about those messages
  184. self._check_for_mail()
  185. def test_multiple_rooms(self):
  186. # We want to test multiple notifications from multiple rooms, so we pause
  187. # processing of push while we send messages.
  188. self.pusher._pause_processing()
  189. # Create a simple room with multiple other users
  190. rooms = [
  191. self.helper.create_room_as(self.user_id, tok=self.access_token),
  192. self.helper.create_room_as(self.user_id, tok=self.access_token),
  193. ]
  194. for r, other in zip(rooms, self.others):
  195. self.helper.invite(
  196. room=r, src=self.user_id, tok=self.access_token, targ=other.id
  197. )
  198. self.helper.join(room=r, user=other.id, tok=other.token)
  199. # The other users send some messages
  200. self.helper.send(rooms[0], body="Hi!", tok=self.others[0].token)
  201. self.helper.send(rooms[1], body="There!", tok=self.others[1].token)
  202. self.helper.send(rooms[1], body="There!", tok=self.others[1].token)
  203. # Nothing should have happened yet, as we're paused.
  204. assert not self.email_attempts
  205. self.pusher._resume_processing()
  206. # We should get emailed about those messages
  207. self._check_for_mail()
  208. def test_empty_room(self):
  209. """All users leaving a room shouldn't cause the pusher to break."""
  210. # Create a simple room with two users
  211. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  212. self.helper.invite(
  213. room=room, src=self.user_id, tok=self.access_token, targ=self.others[0].id
  214. )
  215. self.helper.join(room=room, user=self.others[0].id, tok=self.others[0].token)
  216. # The other user sends a single message.
  217. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  218. # Leave the room before the message is processed.
  219. self.helper.leave(room, self.user_id, tok=self.access_token)
  220. self.helper.leave(room, self.others[0].id, tok=self.others[0].token)
  221. # We should get emailed about that message
  222. self._check_for_mail()
  223. def test_empty_room_multiple_messages(self):
  224. """All users leaving a room shouldn't cause the pusher to break."""
  225. # Create a simple room with two users
  226. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  227. self.helper.invite(
  228. room=room, src=self.user_id, tok=self.access_token, targ=self.others[0].id
  229. )
  230. self.helper.join(room=room, user=self.others[0].id, tok=self.others[0].token)
  231. # The other user sends a single message.
  232. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  233. self.helper.send(room, body="There!", tok=self.others[0].token)
  234. # Leave the room before the message is processed.
  235. self.helper.leave(room, self.user_id, tok=self.access_token)
  236. self.helper.leave(room, self.others[0].id, tok=self.others[0].token)
  237. # We should get emailed about that message
  238. self._check_for_mail()
  239. def test_encrypted_message(self):
  240. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  241. self.helper.invite(
  242. room=room, src=self.user_id, tok=self.access_token, targ=self.others[0].id
  243. )
  244. self.helper.join(room=room, user=self.others[0].id, tok=self.others[0].token)
  245. # The other user sends some messages
  246. self.helper.send_event(room, "m.room.encrypted", {}, tok=self.others[0].token)
  247. # We should get emailed about that message
  248. self._check_for_mail()
  249. def test_no_email_sent_after_removed(self):
  250. # Create a simple room with two users
  251. room = self.helper.create_room_as(self.user_id, tok=self.access_token)
  252. self.helper.invite(
  253. room=room,
  254. src=self.user_id,
  255. tok=self.access_token,
  256. targ=self.others[0].id,
  257. )
  258. self.helper.join(
  259. room=room,
  260. user=self.others[0].id,
  261. tok=self.others[0].token,
  262. )
  263. # The other user sends a single message.
  264. self.helper.send(room, body="Hi!", tok=self.others[0].token)
  265. # We should get emailed about that message
  266. self._check_for_mail()
  267. # disassociate the user's email address
  268. self.get_success(
  269. self.auth_handler.delete_threepid(
  270. user_id=self.user_id,
  271. medium="email",
  272. address="a@example.com",
  273. )
  274. )
  275. # check that the pusher for that email address has been deleted
  276. pushers = self.get_success(
  277. self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
  278. )
  279. pushers = list(pushers)
  280. self.assertEqual(len(pushers), 0)
  281. def test_remove_unlinked_pushers_background_job(self):
  282. """Checks that all existing pushers associated with unlinked email addresses are removed
  283. upon running the remove_deleted_email_pushers background update.
  284. """
  285. # disassociate the user's email address manually (without deleting the pusher).
  286. # This resembles the old behaviour, which the background update below is intended
  287. # to clean up.
  288. self.get_success(
  289. self.hs.get_datastore().user_delete_threepid(
  290. self.user_id, "email", "a@example.com"
  291. )
  292. )
  293. # Run the "remove_deleted_email_pushers" background job
  294. self.get_success(
  295. self.hs.get_datastore().db_pool.simple_insert(
  296. table="background_updates",
  297. values={
  298. "update_name": "remove_deleted_email_pushers",
  299. "progress_json": "{}",
  300. "depends_on": None,
  301. },
  302. )
  303. )
  304. # ... and tell the DataStore that it hasn't finished all updates yet
  305. self.hs.get_datastore().db_pool.updates._all_done = False
  306. # Now let's actually drive the updates to completion
  307. while not self.get_success(
  308. self.hs.get_datastore().db_pool.updates.has_completed_background_updates()
  309. ):
  310. self.get_success(
  311. self.hs.get_datastore().db_pool.updates.do_next_background_update(100),
  312. by=0.1,
  313. )
  314. # Check that all pushers with unlinked addresses were deleted
  315. pushers = self.get_success(
  316. self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
  317. )
  318. pushers = list(pushers)
  319. self.assertEqual(len(pushers), 0)
  320. def _check_for_mail(self):
  321. """Check that the user receives an email notification"""
  322. # Get the stream ordering before it gets sent
  323. pushers = self.get_success(
  324. self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
  325. )
  326. pushers = list(pushers)
  327. self.assertEqual(len(pushers), 1)
  328. last_stream_ordering = pushers[0].last_stream_ordering
  329. # Advance time a bit, so the pusher will register something has happened
  330. self.pump(10)
  331. # It hasn't succeeded yet, so the stream ordering shouldn't have moved
  332. pushers = self.get_success(
  333. self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
  334. )
  335. pushers = list(pushers)
  336. self.assertEqual(len(pushers), 1)
  337. self.assertEqual(last_stream_ordering, pushers[0].last_stream_ordering)
  338. # One email was attempted to be sent
  339. self.assertEqual(len(self.email_attempts), 1)
  340. # Make the email succeed
  341. self.email_attempts[0][0].callback(True)
  342. self.pump()
  343. # One email was attempted to be sent
  344. self.assertEqual(len(self.email_attempts), 1)
  345. # The stream ordering has increased
  346. pushers = self.get_success(
  347. self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
  348. )
  349. pushers = list(pushers)
  350. self.assertEqual(len(pushers), 1)
  351. self.assertTrue(pushers[0].last_stream_ordering > last_stream_ordering)
  352. # Reset the attempts.
  353. self.email_attempts = []