test_account.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015-2016 OpenMarket Ltd
  3. # Copyright 2017-2018 New Vector Ltd
  4. # Copyright 2019 The Matrix.org Foundation C.I.C.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import json
  18. import os
  19. import re
  20. from email.parser import Parser
  21. import pkg_resources
  22. import synapse.rest.admin
  23. from synapse.api.constants import LoginType, Membership
  24. from synapse.rest.client.v1 import login, room
  25. from synapse.rest.client.v2_alpha import account, register
  26. from tests import unittest
  27. class PasswordResetTestCase(unittest.HomeserverTestCase):
  28. servlets = [
  29. account.register_servlets,
  30. synapse.rest.admin.register_servlets_for_client_rest_resource,
  31. register.register_servlets,
  32. login.register_servlets,
  33. ]
  34. def make_homeserver(self, reactor, clock):
  35. config = self.default_config()
  36. # Email config.
  37. self.email_attempts = []
  38. def sendmail(smtphost, from_addr, to_addrs, msg, **kwargs):
  39. self.email_attempts.append(msg)
  40. return
  41. config["email"] = {
  42. "enable_notifs": False,
  43. "template_dir": os.path.abspath(
  44. pkg_resources.resource_filename("synapse", "res/templates")
  45. ),
  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. "notif_from": "test@example.com",
  52. }
  53. config["public_baseurl"] = "https://example.com"
  54. hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
  55. return hs
  56. def prepare(self, reactor, clock, hs):
  57. self.store = hs.get_datastore()
  58. def test_basic_password_reset(self):
  59. """Test basic password reset flow
  60. """
  61. old_password = "monkey"
  62. new_password = "kangeroo"
  63. user_id = self.register_user("kermit", old_password)
  64. self.login("kermit", old_password)
  65. email = "test@example.com"
  66. # Add a threepid
  67. self.get_success(
  68. self.store.user_add_threepid(
  69. user_id=user_id,
  70. medium="email",
  71. address=email,
  72. validated_at=0,
  73. added_at=0,
  74. )
  75. )
  76. client_secret = "foobar"
  77. session_id = self._request_token(email, client_secret)
  78. self.assertEquals(len(self.email_attempts), 1)
  79. link = self._get_link_from_email()
  80. self._validate_token(link)
  81. self._reset_password(new_password, session_id, client_secret)
  82. # Assert we can log in with the new password
  83. self.login("kermit", new_password)
  84. # Assert we can't log in with the old password
  85. self.attempt_wrong_password_login("kermit", old_password)
  86. def test_cant_reset_password_without_clicking_link(self):
  87. """Test that we do actually need to click the link in the email
  88. """
  89. old_password = "monkey"
  90. new_password = "kangeroo"
  91. user_id = self.register_user("kermit", old_password)
  92. self.login("kermit", old_password)
  93. email = "test@example.com"
  94. # Add a threepid
  95. self.get_success(
  96. self.store.user_add_threepid(
  97. user_id=user_id,
  98. medium="email",
  99. address=email,
  100. validated_at=0,
  101. added_at=0,
  102. )
  103. )
  104. client_secret = "foobar"
  105. session_id = self._request_token(email, client_secret)
  106. self.assertEquals(len(self.email_attempts), 1)
  107. # Attempt to reset password without clicking the link
  108. self._reset_password(new_password, session_id, client_secret, expected_code=401)
  109. # Assert we can log in with the old password
  110. self.login("kermit", old_password)
  111. # Assert we can't log in with the new password
  112. self.attempt_wrong_password_login("kermit", new_password)
  113. def test_no_valid_token(self):
  114. """Test that we do actually need to request a token and can't just
  115. make a session up.
  116. """
  117. old_password = "monkey"
  118. new_password = "kangeroo"
  119. user_id = self.register_user("kermit", old_password)
  120. self.login("kermit", old_password)
  121. email = "test@example.com"
  122. # Add a threepid
  123. self.get_success(
  124. self.store.user_add_threepid(
  125. user_id=user_id,
  126. medium="email",
  127. address=email,
  128. validated_at=0,
  129. added_at=0,
  130. )
  131. )
  132. client_secret = "foobar"
  133. session_id = "weasle"
  134. # Attempt to reset password without even requesting an email
  135. self._reset_password(new_password, session_id, client_secret, expected_code=401)
  136. # Assert we can log in with the old password
  137. self.login("kermit", old_password)
  138. # Assert we can't log in with the new password
  139. self.attempt_wrong_password_login("kermit", new_password)
  140. def _request_token(self, email, client_secret):
  141. request, channel = self.make_request(
  142. "POST",
  143. b"account/password/email/requestToken",
  144. {"client_secret": client_secret, "email": email, "send_attempt": 1},
  145. )
  146. self.render(request)
  147. self.assertEquals(200, channel.code, channel.result)
  148. return channel.json_body["sid"]
  149. def _validate_token(self, link):
  150. # Remove the host
  151. path = link.replace("https://example.com", "")
  152. request, channel = self.make_request("GET", path, shorthand=False)
  153. self.render(request)
  154. self.assertEquals(200, channel.code, channel.result)
  155. def _get_link_from_email(self):
  156. assert self.email_attempts, "No emails have been sent"
  157. raw_msg = self.email_attempts[-1].decode("UTF-8")
  158. mail = Parser().parsestr(raw_msg)
  159. text = None
  160. for part in mail.walk():
  161. if part.get_content_type() == "text/plain":
  162. text = part.get_payload(decode=True).decode("UTF-8")
  163. break
  164. if not text:
  165. self.fail("Could not find text portion of email to parse")
  166. match = re.search(r"https://example.com\S+", text)
  167. assert match, "Could not find link in email"
  168. return match.group(0)
  169. def _reset_password(
  170. self, new_password, session_id, client_secret, expected_code=200
  171. ):
  172. request, channel = self.make_request(
  173. "POST",
  174. b"account/password",
  175. {
  176. "new_password": new_password,
  177. "auth": {
  178. "type": LoginType.EMAIL_IDENTITY,
  179. "threepid_creds": {
  180. "client_secret": client_secret,
  181. "sid": session_id,
  182. },
  183. },
  184. },
  185. )
  186. self.render(request)
  187. self.assertEquals(expected_code, channel.code, channel.result)
  188. class DeactivateTestCase(unittest.HomeserverTestCase):
  189. servlets = [
  190. synapse.rest.admin.register_servlets_for_client_rest_resource,
  191. login.register_servlets,
  192. account.register_servlets,
  193. room.register_servlets,
  194. ]
  195. def make_homeserver(self, reactor, clock):
  196. self.hs = self.setup_test_homeserver()
  197. return self.hs
  198. def test_deactivate_account(self):
  199. user_id = self.register_user("kermit", "test")
  200. tok = self.login("kermit", "test")
  201. self.deactivate(user_id, tok)
  202. store = self.hs.get_datastore()
  203. # Check that the user has been marked as deactivated.
  204. self.assertTrue(self.get_success(store.get_user_deactivated_status(user_id)))
  205. # Check that this access token has been invalidated.
  206. request, channel = self.make_request("GET", "account/whoami")
  207. self.render(request)
  208. self.assertEqual(request.code, 401)
  209. @unittest.INFO
  210. def test_pending_invites(self):
  211. """Tests that deactivating a user rejects every pending invite for them."""
  212. store = self.hs.get_datastore()
  213. inviter_id = self.register_user("inviter", "test")
  214. inviter_tok = self.login("inviter", "test")
  215. invitee_id = self.register_user("invitee", "test")
  216. invitee_tok = self.login("invitee", "test")
  217. # Make @inviter:test invite @invitee:test in a new room.
  218. room_id = self.helper.create_room_as(inviter_id, tok=inviter_tok)
  219. self.helper.invite(
  220. room=room_id, src=inviter_id, targ=invitee_id, tok=inviter_tok
  221. )
  222. # Make sure the invite is here.
  223. pending_invites = self.get_success(
  224. store.get_invited_rooms_for_local_user(invitee_id)
  225. )
  226. self.assertEqual(len(pending_invites), 1, pending_invites)
  227. self.assertEqual(pending_invites[0].room_id, room_id, pending_invites)
  228. # Deactivate @invitee:test.
  229. self.deactivate(invitee_id, invitee_tok)
  230. # Check that the invite isn't there anymore.
  231. pending_invites = self.get_success(
  232. store.get_invited_rooms_for_local_user(invitee_id)
  233. )
  234. self.assertEqual(len(pending_invites), 0, pending_invites)
  235. # Check that the membership of @invitee:test in the room is now "leave".
  236. memberships = self.get_success(
  237. store.get_rooms_for_local_user_where_membership_is(
  238. invitee_id, [Membership.LEAVE]
  239. )
  240. )
  241. self.assertEqual(len(memberships), 1, memberships)
  242. self.assertEqual(memberships[0].room_id, room_id, memberships)
  243. def deactivate(self, user_id, tok):
  244. request_data = json.dumps(
  245. {
  246. "auth": {
  247. "type": "m.login.password",
  248. "user": user_id,
  249. "password": "test",
  250. },
  251. "erase": False,
  252. }
  253. )
  254. request, channel = self.make_request(
  255. "POST", "account/deactivate", request_data, access_token=tok
  256. )
  257. self.render(request)
  258. self.assertEqual(request.code, 200)