test_account.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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
  24. from synapse.rest.client.v1 import login
  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. ]
  194. def make_homeserver(self, reactor, clock):
  195. hs = self.setup_test_homeserver()
  196. return hs
  197. def test_deactivate_account(self):
  198. user_id = self.register_user("kermit", "test")
  199. tok = self.login("kermit", "test")
  200. request_data = json.dumps(
  201. {
  202. "auth": {
  203. "type": "m.login.password",
  204. "user": user_id,
  205. "password": "test",
  206. },
  207. "erase": False,
  208. }
  209. )
  210. request, channel = self.make_request(
  211. "POST", "account/deactivate", request_data, access_token=tok
  212. )
  213. self.render(request)
  214. self.assertEqual(request.code, 200)
  215. store = self.hs.get_datastore()
  216. # Check that the user has been marked as deactivated.
  217. self.assertTrue(self.get_success(store.get_user_deactivated_status(user_id)))
  218. # Check that this access token has been invalidated.
  219. request, channel = self.make_request("GET", "account/whoami")
  220. self.render(request)
  221. self.assertEqual(request.code, 401)