test_account.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  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.api.errors import Codes
  25. from synapse.rest.client.v1 import login, room
  26. from synapse.rest.client.v2_alpha import account, register
  27. from tests import unittest
  28. class PasswordResetTestCase(unittest.HomeserverTestCase):
  29. servlets = [
  30. account.register_servlets,
  31. synapse.rest.admin.register_servlets_for_client_rest_resource,
  32. register.register_servlets,
  33. login.register_servlets,
  34. ]
  35. def make_homeserver(self, reactor, clock):
  36. config = self.default_config()
  37. # Email config.
  38. self.email_attempts = []
  39. async def sendmail(smtphost, from_addr, to_addrs, msg, **kwargs):
  40. self.email_attempts.append(msg)
  41. return
  42. config["email"] = {
  43. "enable_notifs": False,
  44. "template_dir": os.path.abspath(
  45. pkg_resources.resource_filename("synapse", "res/templates")
  46. ),
  47. "smtp_host": "127.0.0.1",
  48. "smtp_port": 20,
  49. "require_transport_security": False,
  50. "smtp_user": None,
  51. "smtp_pass": None,
  52. "notif_from": "test@example.com",
  53. }
  54. config["public_baseurl"] = "https://example.com"
  55. hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
  56. return hs
  57. def prepare(self, reactor, clock, hs):
  58. self.store = hs.get_datastore()
  59. def test_basic_password_reset(self):
  60. """Test basic password reset flow
  61. """
  62. old_password = "monkey"
  63. new_password = "kangeroo"
  64. user_id = self.register_user("kermit", old_password)
  65. self.login("kermit", old_password)
  66. email = "test@example.com"
  67. # Add a threepid
  68. self.get_success(
  69. self.store.user_add_threepid(
  70. user_id=user_id,
  71. medium="email",
  72. address=email,
  73. validated_at=0,
  74. added_at=0,
  75. )
  76. )
  77. client_secret = "foobar"
  78. session_id = self._request_token(email, client_secret)
  79. self.assertEquals(len(self.email_attempts), 1)
  80. link = self._get_link_from_email()
  81. self._validate_token(link)
  82. self._reset_password(new_password, session_id, client_secret)
  83. # Assert we can log in with the new password
  84. self.login("kermit", new_password)
  85. # Assert we can't log in with the old password
  86. self.attempt_wrong_password_login("kermit", old_password)
  87. def test_basic_password_reset_canonicalise_email(self):
  88. """Test basic password reset flow
  89. Request password reset with different spelling
  90. """
  91. old_password = "monkey"
  92. new_password = "kangeroo"
  93. user_id = self.register_user("kermit", old_password)
  94. self.login("kermit", old_password)
  95. email_profile = "test@example.com"
  96. email_passwort_reset = "TEST@EXAMPLE.COM"
  97. # Add a threepid
  98. self.get_success(
  99. self.store.user_add_threepid(
  100. user_id=user_id,
  101. medium="email",
  102. address=email_profile,
  103. validated_at=0,
  104. added_at=0,
  105. )
  106. )
  107. client_secret = "foobar"
  108. session_id = self._request_token(email_passwort_reset, client_secret)
  109. self.assertEquals(len(self.email_attempts), 1)
  110. link = self._get_link_from_email()
  111. self._validate_token(link)
  112. self._reset_password(new_password, session_id, client_secret)
  113. # Assert we can log in with the new password
  114. self.login("kermit", new_password)
  115. # Assert we can't log in with the old password
  116. self.attempt_wrong_password_login("kermit", old_password)
  117. def test_cant_reset_password_without_clicking_link(self):
  118. """Test that we do actually need to click the link in the email
  119. """
  120. old_password = "monkey"
  121. new_password = "kangeroo"
  122. user_id = self.register_user("kermit", old_password)
  123. self.login("kermit", old_password)
  124. email = "test@example.com"
  125. # Add a threepid
  126. self.get_success(
  127. self.store.user_add_threepid(
  128. user_id=user_id,
  129. medium="email",
  130. address=email,
  131. validated_at=0,
  132. added_at=0,
  133. )
  134. )
  135. client_secret = "foobar"
  136. session_id = self._request_token(email, client_secret)
  137. self.assertEquals(len(self.email_attempts), 1)
  138. # Attempt to reset password without clicking the link
  139. self._reset_password(new_password, session_id, client_secret, expected_code=401)
  140. # Assert we can log in with the old password
  141. self.login("kermit", old_password)
  142. # Assert we can't log in with the new password
  143. self.attempt_wrong_password_login("kermit", new_password)
  144. def test_no_valid_token(self):
  145. """Test that we do actually need to request a token and can't just
  146. make a session up.
  147. """
  148. old_password = "monkey"
  149. new_password = "kangeroo"
  150. user_id = self.register_user("kermit", old_password)
  151. self.login("kermit", old_password)
  152. email = "test@example.com"
  153. # Add a threepid
  154. self.get_success(
  155. self.store.user_add_threepid(
  156. user_id=user_id,
  157. medium="email",
  158. address=email,
  159. validated_at=0,
  160. added_at=0,
  161. )
  162. )
  163. client_secret = "foobar"
  164. session_id = "weasle"
  165. # Attempt to reset password without even requesting an email
  166. self._reset_password(new_password, session_id, client_secret, expected_code=401)
  167. # Assert we can log in with the old password
  168. self.login("kermit", old_password)
  169. # Assert we can't log in with the new password
  170. self.attempt_wrong_password_login("kermit", new_password)
  171. @unittest.override_config({"request_token_inhibit_3pid_errors": True})
  172. def test_password_reset_bad_email_inhibit_error(self):
  173. """Test that triggering a password reset with an email address that isn't bound
  174. to an account doesn't leak the lack of binding for that address if configured
  175. that way.
  176. """
  177. self.register_user("kermit", "monkey")
  178. self.login("kermit", "monkey")
  179. email = "test@example.com"
  180. client_secret = "foobar"
  181. session_id = self._request_token(email, client_secret)
  182. self.assertIsNotNone(session_id)
  183. def _request_token(self, email, client_secret):
  184. request, channel = self.make_request(
  185. "POST",
  186. b"account/password/email/requestToken",
  187. {"client_secret": client_secret, "email": email, "send_attempt": 1},
  188. )
  189. self.render(request)
  190. self.assertEquals(200, channel.code, channel.result)
  191. return channel.json_body["sid"]
  192. def _validate_token(self, link):
  193. # Remove the host
  194. path = link.replace("https://example.com", "")
  195. request, channel = self.make_request("GET", path, shorthand=False)
  196. self.render(request)
  197. self.assertEquals(200, channel.code, channel.result)
  198. def _get_link_from_email(self):
  199. assert self.email_attempts, "No emails have been sent"
  200. raw_msg = self.email_attempts[-1].decode("UTF-8")
  201. mail = Parser().parsestr(raw_msg)
  202. text = None
  203. for part in mail.walk():
  204. if part.get_content_type() == "text/plain":
  205. text = part.get_payload(decode=True).decode("UTF-8")
  206. break
  207. if not text:
  208. self.fail("Could not find text portion of email to parse")
  209. match = re.search(r"https://example.com\S+", text)
  210. assert match, "Could not find link in email"
  211. return match.group(0)
  212. def _reset_password(
  213. self, new_password, session_id, client_secret, expected_code=200
  214. ):
  215. request, channel = self.make_request(
  216. "POST",
  217. b"account/password",
  218. {
  219. "new_password": new_password,
  220. "auth": {
  221. "type": LoginType.EMAIL_IDENTITY,
  222. "threepid_creds": {
  223. "client_secret": client_secret,
  224. "sid": session_id,
  225. },
  226. },
  227. },
  228. )
  229. self.render(request)
  230. self.assertEquals(expected_code, channel.code, channel.result)
  231. class DeactivateTestCase(unittest.HomeserverTestCase):
  232. servlets = [
  233. synapse.rest.admin.register_servlets_for_client_rest_resource,
  234. login.register_servlets,
  235. account.register_servlets,
  236. room.register_servlets,
  237. ]
  238. def make_homeserver(self, reactor, clock):
  239. self.hs = self.setup_test_homeserver()
  240. return self.hs
  241. def test_deactivate_account(self):
  242. user_id = self.register_user("kermit", "test")
  243. tok = self.login("kermit", "test")
  244. self.deactivate(user_id, tok)
  245. store = self.hs.get_datastore()
  246. # Check that the user has been marked as deactivated.
  247. self.assertTrue(self.get_success(store.get_user_deactivated_status(user_id)))
  248. # Check that this access token has been invalidated.
  249. request, channel = self.make_request("GET", "account/whoami")
  250. self.render(request)
  251. self.assertEqual(request.code, 401)
  252. @unittest.INFO
  253. def test_pending_invites(self):
  254. """Tests that deactivating a user rejects every pending invite for them."""
  255. store = self.hs.get_datastore()
  256. inviter_id = self.register_user("inviter", "test")
  257. inviter_tok = self.login("inviter", "test")
  258. invitee_id = self.register_user("invitee", "test")
  259. invitee_tok = self.login("invitee", "test")
  260. # Make @inviter:test invite @invitee:test in a new room.
  261. room_id = self.helper.create_room_as(inviter_id, tok=inviter_tok)
  262. self.helper.invite(
  263. room=room_id, src=inviter_id, targ=invitee_id, tok=inviter_tok
  264. )
  265. # Make sure the invite is here.
  266. pending_invites = self.get_success(
  267. store.get_invited_rooms_for_local_user(invitee_id)
  268. )
  269. self.assertEqual(len(pending_invites), 1, pending_invites)
  270. self.assertEqual(pending_invites[0].room_id, room_id, pending_invites)
  271. # Deactivate @invitee:test.
  272. self.deactivate(invitee_id, invitee_tok)
  273. # Check that the invite isn't there anymore.
  274. pending_invites = self.get_success(
  275. store.get_invited_rooms_for_local_user(invitee_id)
  276. )
  277. self.assertEqual(len(pending_invites), 0, pending_invites)
  278. # Check that the membership of @invitee:test in the room is now "leave".
  279. memberships = self.get_success(
  280. store.get_rooms_for_local_user_where_membership_is(
  281. invitee_id, [Membership.LEAVE]
  282. )
  283. )
  284. self.assertEqual(len(memberships), 1, memberships)
  285. self.assertEqual(memberships[0].room_id, room_id, memberships)
  286. def deactivate(self, user_id, tok):
  287. request_data = json.dumps(
  288. {
  289. "auth": {
  290. "type": "m.login.password",
  291. "user": user_id,
  292. "password": "test",
  293. },
  294. "erase": False,
  295. }
  296. )
  297. request, channel = self.make_request(
  298. "POST", "account/deactivate", request_data, access_token=tok
  299. )
  300. self.render(request)
  301. self.assertEqual(request.code, 200)
  302. class ThreepidEmailRestTestCase(unittest.HomeserverTestCase):
  303. servlets = [
  304. account.register_servlets,
  305. login.register_servlets,
  306. synapse.rest.admin.register_servlets_for_client_rest_resource,
  307. ]
  308. def make_homeserver(self, reactor, clock):
  309. config = self.default_config()
  310. # Email config.
  311. self.email_attempts = []
  312. async def sendmail(smtphost, from_addr, to_addrs, msg, **kwargs):
  313. self.email_attempts.append(msg)
  314. config["email"] = {
  315. "enable_notifs": False,
  316. "template_dir": os.path.abspath(
  317. pkg_resources.resource_filename("synapse", "res/templates")
  318. ),
  319. "smtp_host": "127.0.0.1",
  320. "smtp_port": 20,
  321. "require_transport_security": False,
  322. "smtp_user": None,
  323. "smtp_pass": None,
  324. "notif_from": "test@example.com",
  325. }
  326. config["public_baseurl"] = "https://example.com"
  327. self.hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
  328. return self.hs
  329. def prepare(self, reactor, clock, hs):
  330. self.store = hs.get_datastore()
  331. self.user_id = self.register_user("kermit", "test")
  332. self.user_id_tok = self.login("kermit", "test")
  333. self.email = "test@example.com"
  334. self.url_3pid = b"account/3pid"
  335. def test_add_valid_email(self):
  336. self.get_success(self._add_email(self.email, self.email))
  337. def test_add_valid_email_second_time(self):
  338. self.get_success(self._add_email(self.email, self.email))
  339. self.get_success(
  340. self._request_token_invalid_email(
  341. self.email,
  342. expected_errcode=Codes.THREEPID_IN_USE,
  343. expected_error="Email is already in use",
  344. )
  345. )
  346. def test_add_valid_email_second_time_canonicalise(self):
  347. self.get_success(self._add_email(self.email, self.email))
  348. self.get_success(
  349. self._request_token_invalid_email(
  350. "TEST@EXAMPLE.COM",
  351. expected_errcode=Codes.THREEPID_IN_USE,
  352. expected_error="Email is already in use",
  353. )
  354. )
  355. def test_add_email_no_at(self):
  356. self.get_success(
  357. self._request_token_invalid_email(
  358. "address-without-at.bar",
  359. expected_errcode=Codes.UNKNOWN,
  360. expected_error="Unable to parse email address",
  361. )
  362. )
  363. def test_add_email_two_at(self):
  364. self.get_success(
  365. self._request_token_invalid_email(
  366. "foo@foo@test.bar",
  367. expected_errcode=Codes.UNKNOWN,
  368. expected_error="Unable to parse email address",
  369. )
  370. )
  371. def test_add_email_bad_format(self):
  372. self.get_success(
  373. self._request_token_invalid_email(
  374. "user@bad.example.net@good.example.com",
  375. expected_errcode=Codes.UNKNOWN,
  376. expected_error="Unable to parse email address",
  377. )
  378. )
  379. def test_add_email_domain_to_lower(self):
  380. self.get_success(self._add_email("foo@TEST.BAR", "foo@test.bar"))
  381. def test_add_email_domain_with_umlaut(self):
  382. self.get_success(self._add_email("foo@Öumlaut.com", "foo@öumlaut.com"))
  383. def test_add_email_address_casefold(self):
  384. self.get_success(self._add_email("Strauß@Example.com", "strauss@example.com"))
  385. def test_address_trim(self):
  386. self.get_success(self._add_email(" foo@test.bar ", "foo@test.bar"))
  387. def test_add_email_if_disabled(self):
  388. """Test adding email to profile when doing so is disallowed
  389. """
  390. self.hs.config.enable_3pid_changes = False
  391. client_secret = "foobar"
  392. session_id = self._request_token(self.email, client_secret)
  393. self.assertEquals(len(self.email_attempts), 1)
  394. link = self._get_link_from_email()
  395. self._validate_token(link)
  396. request, channel = self.make_request(
  397. "POST",
  398. b"/_matrix/client/unstable/account/3pid/add",
  399. {
  400. "client_secret": client_secret,
  401. "sid": session_id,
  402. "auth": {
  403. "type": "m.login.password",
  404. "user": self.user_id,
  405. "password": "test",
  406. },
  407. },
  408. access_token=self.user_id_tok,
  409. )
  410. self.render(request)
  411. self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
  412. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  413. # Get user
  414. request, channel = self.make_request(
  415. "GET", self.url_3pid, access_token=self.user_id_tok,
  416. )
  417. self.render(request)
  418. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  419. self.assertFalse(channel.json_body["threepids"])
  420. def test_delete_email(self):
  421. """Test deleting an email from profile
  422. """
  423. # Add a threepid
  424. self.get_success(
  425. self.store.user_add_threepid(
  426. user_id=self.user_id,
  427. medium="email",
  428. address=self.email,
  429. validated_at=0,
  430. added_at=0,
  431. )
  432. )
  433. request, channel = self.make_request(
  434. "POST",
  435. b"account/3pid/delete",
  436. {"medium": "email", "address": self.email},
  437. access_token=self.user_id_tok,
  438. )
  439. self.render(request)
  440. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  441. # Get user
  442. request, channel = self.make_request(
  443. "GET", self.url_3pid, access_token=self.user_id_tok,
  444. )
  445. self.render(request)
  446. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  447. self.assertFalse(channel.json_body["threepids"])
  448. def test_delete_email_if_disabled(self):
  449. """Test deleting an email from profile when disallowed
  450. """
  451. self.hs.config.enable_3pid_changes = False
  452. # Add a threepid
  453. self.get_success(
  454. self.store.user_add_threepid(
  455. user_id=self.user_id,
  456. medium="email",
  457. address=self.email,
  458. validated_at=0,
  459. added_at=0,
  460. )
  461. )
  462. request, channel = self.make_request(
  463. "POST",
  464. b"account/3pid/delete",
  465. {"medium": "email", "address": self.email},
  466. access_token=self.user_id_tok,
  467. )
  468. self.render(request)
  469. self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
  470. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  471. # Get user
  472. request, channel = self.make_request(
  473. "GET", self.url_3pid, access_token=self.user_id_tok,
  474. )
  475. self.render(request)
  476. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  477. self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
  478. self.assertEqual(self.email, channel.json_body["threepids"][0]["address"])
  479. def test_cant_add_email_without_clicking_link(self):
  480. """Test that we do actually need to click the link in the email
  481. """
  482. client_secret = "foobar"
  483. session_id = self._request_token(self.email, client_secret)
  484. self.assertEquals(len(self.email_attempts), 1)
  485. # Attempt to add email without clicking the link
  486. request, channel = self.make_request(
  487. "POST",
  488. b"/_matrix/client/unstable/account/3pid/add",
  489. {
  490. "client_secret": client_secret,
  491. "sid": session_id,
  492. "auth": {
  493. "type": "m.login.password",
  494. "user": self.user_id,
  495. "password": "test",
  496. },
  497. },
  498. access_token=self.user_id_tok,
  499. )
  500. self.render(request)
  501. self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
  502. self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
  503. # Get user
  504. request, channel = self.make_request(
  505. "GET", self.url_3pid, access_token=self.user_id_tok,
  506. )
  507. self.render(request)
  508. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  509. self.assertFalse(channel.json_body["threepids"])
  510. def test_no_valid_token(self):
  511. """Test that we do actually need to request a token and can't just
  512. make a session up.
  513. """
  514. client_secret = "foobar"
  515. session_id = "weasle"
  516. # Attempt to add email without even requesting an email
  517. request, channel = self.make_request(
  518. "POST",
  519. b"/_matrix/client/unstable/account/3pid/add",
  520. {
  521. "client_secret": client_secret,
  522. "sid": session_id,
  523. "auth": {
  524. "type": "m.login.password",
  525. "user": self.user_id,
  526. "password": "test",
  527. },
  528. },
  529. access_token=self.user_id_tok,
  530. )
  531. self.render(request)
  532. self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
  533. self.assertEqual(Codes.THREEPID_AUTH_FAILED, channel.json_body["errcode"])
  534. # Get user
  535. request, channel = self.make_request(
  536. "GET", self.url_3pid, access_token=self.user_id_tok,
  537. )
  538. self.render(request)
  539. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  540. self.assertFalse(channel.json_body["threepids"])
  541. def _request_token(self, email, client_secret):
  542. request, channel = self.make_request(
  543. "POST",
  544. b"account/3pid/email/requestToken",
  545. {"client_secret": client_secret, "email": email, "send_attempt": 1},
  546. )
  547. self.render(request)
  548. self.assertEquals(200, channel.code, channel.result)
  549. return channel.json_body["sid"]
  550. def _request_token_invalid_email(
  551. self, email, expected_errcode, expected_error, client_secret="foobar",
  552. ):
  553. request, channel = self.make_request(
  554. "POST",
  555. b"account/3pid/email/requestToken",
  556. {"client_secret": client_secret, "email": email, "send_attempt": 1},
  557. )
  558. self.render(request)
  559. self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"])
  560. self.assertEqual(expected_errcode, channel.json_body["errcode"])
  561. self.assertEqual(expected_error, channel.json_body["error"])
  562. def _validate_token(self, link):
  563. # Remove the host
  564. path = link.replace("https://example.com", "")
  565. request, channel = self.make_request("GET", path, shorthand=False)
  566. self.render(request)
  567. self.assertEquals(200, channel.code, channel.result)
  568. def _get_link_from_email(self):
  569. assert self.email_attempts, "No emails have been sent"
  570. raw_msg = self.email_attempts[-1].decode("UTF-8")
  571. mail = Parser().parsestr(raw_msg)
  572. text = None
  573. for part in mail.walk():
  574. if part.get_content_type() == "text/plain":
  575. text = part.get_payload(decode=True).decode("UTF-8")
  576. break
  577. if not text:
  578. self.fail("Could not find text portion of email to parse")
  579. match = re.search(r"https://example.com\S+", text)
  580. assert match, "Could not find link in email"
  581. return match.group(0)
  582. def _add_email(self, request_email, expected_email):
  583. """Test adding an email to profile
  584. """
  585. client_secret = "foobar"
  586. session_id = self._request_token(request_email, client_secret)
  587. self.assertEquals(len(self.email_attempts), 1)
  588. link = self._get_link_from_email()
  589. self._validate_token(link)
  590. request, channel = self.make_request(
  591. "POST",
  592. b"/_matrix/client/unstable/account/3pid/add",
  593. {
  594. "client_secret": client_secret,
  595. "sid": session_id,
  596. "auth": {
  597. "type": "m.login.password",
  598. "user": self.user_id,
  599. "password": "test",
  600. },
  601. },
  602. access_token=self.user_id_tok,
  603. )
  604. self.render(request)
  605. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  606. # Get user
  607. request, channel = self.make_request(
  608. "GET", self.url_3pid, access_token=self.user_id_tok,
  609. )
  610. self.render(request)
  611. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  612. self.assertEqual("email", channel.json_body["threepids"][0]["medium"])
  613. self.assertEqual(expected_email, channel.json_body["threepids"][0]["address"])