account.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2017 Vector Creations Ltd
  3. # Copyright 2018 New Vector Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import logging
  17. import random
  18. from http import HTTPStatus
  19. from typing import TYPE_CHECKING, Optional, Tuple
  20. from urllib.parse import urlparse
  21. from twisted.web.server import Request
  22. from synapse.api.constants import LoginType
  23. from synapse.api.errors import (
  24. Codes,
  25. InteractiveAuthIncompleteError,
  26. SynapseError,
  27. ThreepidValidationError,
  28. )
  29. from synapse.handlers.ui_auth import UIAuthSessionDataConstants
  30. from synapse.http.server import HttpServer, finish_request, respond_with_html
  31. from synapse.http.servlet import (
  32. RestServlet,
  33. assert_params_in_dict,
  34. parse_json_object_from_request,
  35. parse_string,
  36. )
  37. from synapse.http.site import SynapseRequest
  38. from synapse.metrics import threepid_send_requests
  39. from synapse.push.mailer import Mailer
  40. from synapse.types import JsonDict
  41. from synapse.util.msisdn import phone_number_to_msisdn
  42. from synapse.util.stringutils import assert_valid_client_secret, random_string
  43. from synapse.util.threepids import check_3pid_allowed, validate_email
  44. from ._base import client_patterns, interactive_auth_handler
  45. if TYPE_CHECKING:
  46. from synapse.server import HomeServer
  47. logger = logging.getLogger(__name__)
  48. class EmailPasswordRequestTokenRestServlet(RestServlet):
  49. PATTERNS = client_patterns("/account/password/email/requestToken$")
  50. def __init__(self, hs: "HomeServer"):
  51. super().__init__()
  52. self.hs = hs
  53. self.datastore = hs.get_datastores().main
  54. self.config = hs.config
  55. self.identity_handler = hs.get_identity_handler()
  56. if self.config.email.can_verify_email:
  57. self.mailer = Mailer(
  58. hs=self.hs,
  59. app_name=self.config.email.email_app_name,
  60. template_html=self.config.email.email_password_reset_template_html,
  61. template_text=self.config.email.email_password_reset_template_text,
  62. )
  63. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  64. if not self.config.email.can_verify_email:
  65. logger.warning(
  66. "User password resets have been disabled due to lack of email config"
  67. )
  68. raise SynapseError(
  69. 400, "Email-based password resets have been disabled on this server"
  70. )
  71. body = parse_json_object_from_request(request)
  72. assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
  73. # Extract params from body
  74. client_secret = body["client_secret"]
  75. assert_valid_client_secret(client_secret)
  76. # Canonicalise the email address. The addresses are all stored canonicalised
  77. # in the database. This allows the user to reset his password without having to
  78. # know the exact spelling (eg. upper and lower case) of address in the database.
  79. # Stored in the database "foo@bar.com"
  80. # User requests with "FOO@bar.com" would raise a Not Found error
  81. try:
  82. email = validate_email(body["email"])
  83. except ValueError as e:
  84. raise SynapseError(400, str(e))
  85. send_attempt = body["send_attempt"]
  86. next_link = body.get("next_link") # Optional param
  87. if next_link:
  88. # Raise if the provided next_link value isn't valid
  89. assert_valid_next_link(self.hs, next_link)
  90. await self.identity_handler.ratelimit_request_token_requests(
  91. request, "email", email
  92. )
  93. # The email will be sent to the stored address.
  94. # This avoids a potential account hijack by requesting a password reset to
  95. # an email address which is controlled by the attacker but which, after
  96. # canonicalisation, matches the one in our database.
  97. existing_user_id = await self.hs.get_datastores().main.get_user_id_by_threepid(
  98. "email", email
  99. )
  100. if existing_user_id is None:
  101. if self.config.server.request_token_inhibit_3pid_errors:
  102. # Make the client think the operation succeeded. See the rationale in the
  103. # comments for request_token_inhibit_3pid_errors.
  104. # Also wait for some random amount of time between 100ms and 1s to make it
  105. # look like we did something.
  106. await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
  107. return 200, {"sid": random_string(16)}
  108. raise SynapseError(400, "Email not found", Codes.THREEPID_NOT_FOUND)
  109. # Send password reset emails from Synapse
  110. sid = await self.identity_handler.send_threepid_validation(
  111. email,
  112. client_secret,
  113. send_attempt,
  114. self.mailer.send_password_reset_mail,
  115. next_link,
  116. )
  117. threepid_send_requests.labels(type="email", reason="password_reset").observe(
  118. send_attempt
  119. )
  120. # Wrap the session id in a JSON object
  121. return 200, {"sid": sid}
  122. class PasswordRestServlet(RestServlet):
  123. PATTERNS = client_patterns("/account/password$")
  124. def __init__(self, hs: "HomeServer"):
  125. super().__init__()
  126. self.hs = hs
  127. self.auth = hs.get_auth()
  128. self.auth_handler = hs.get_auth_handler()
  129. self.datastore = self.hs.get_datastores().main
  130. self.password_policy_handler = hs.get_password_policy_handler()
  131. self._set_password_handler = hs.get_set_password_handler()
  132. @interactive_auth_handler
  133. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  134. body = parse_json_object_from_request(request)
  135. # we do basic sanity checks here because the auth layer will store these
  136. # in sessions. Pull out the new password provided to us.
  137. new_password = body.pop("new_password", None)
  138. if new_password is not None:
  139. if not isinstance(new_password, str) or len(new_password) > 512:
  140. raise SynapseError(400, "Invalid password")
  141. self.password_policy_handler.validate_password(new_password)
  142. # there are two possibilities here. Either the user does not have an
  143. # access token, and needs to do a password reset; or they have one and
  144. # need to validate their identity.
  145. #
  146. # In the first case, we offer a couple of means of identifying
  147. # themselves (email and msisdn, though it's unclear if msisdn actually
  148. # works).
  149. #
  150. # In the second case, we require a password to confirm their identity.
  151. requester = None
  152. if self.auth.has_access_token(request):
  153. requester = await self.auth.get_user_by_req(request)
  154. try:
  155. params, session_id = await self.auth_handler.validate_user_via_ui_auth(
  156. requester,
  157. request,
  158. body,
  159. "modify your account password",
  160. )
  161. except InteractiveAuthIncompleteError as e:
  162. # The user needs to provide more steps to complete auth, but
  163. # they're not required to provide the password again.
  164. #
  165. # If a password is available now, hash the provided password and
  166. # store it for later.
  167. if new_password:
  168. new_password_hash = await self.auth_handler.hash(new_password)
  169. await self.auth_handler.set_session_data(
  170. e.session_id,
  171. UIAuthSessionDataConstants.PASSWORD_HASH,
  172. new_password_hash,
  173. )
  174. raise
  175. user_id = requester.user.to_string()
  176. else:
  177. try:
  178. result, params, session_id = await self.auth_handler.check_ui_auth(
  179. [[LoginType.EMAIL_IDENTITY]],
  180. request,
  181. body,
  182. "modify your account password",
  183. )
  184. except InteractiveAuthIncompleteError as e:
  185. # The user needs to provide more steps to complete auth, but
  186. # they're not required to provide the password again.
  187. #
  188. # If a password is available now, hash the provided password and
  189. # store it for later.
  190. if new_password:
  191. new_password_hash = await self.auth_handler.hash(new_password)
  192. await self.auth_handler.set_session_data(
  193. e.session_id,
  194. UIAuthSessionDataConstants.PASSWORD_HASH,
  195. new_password_hash,
  196. )
  197. raise
  198. if LoginType.EMAIL_IDENTITY in result:
  199. threepid = result[LoginType.EMAIL_IDENTITY]
  200. if "medium" not in threepid or "address" not in threepid:
  201. raise SynapseError(500, "Malformed threepid")
  202. if threepid["medium"] == "email":
  203. # For emails, canonicalise the address.
  204. # We store all email addresses canonicalised in the DB.
  205. # (See add_threepid in synapse/handlers/auth.py)
  206. try:
  207. threepid["address"] = validate_email(threepid["address"])
  208. except ValueError as e:
  209. raise SynapseError(400, str(e))
  210. # if using email, we must know about the email they're authing with!
  211. threepid_user_id = await self.datastore.get_user_id_by_threepid(
  212. threepid["medium"], threepid["address"]
  213. )
  214. if not threepid_user_id:
  215. raise SynapseError(404, "Email address not found", Codes.NOT_FOUND)
  216. user_id = threepid_user_id
  217. else:
  218. logger.error("Auth succeeded but no known type! %r", result.keys())
  219. raise SynapseError(500, "", Codes.UNKNOWN)
  220. # If we have a password in this request, prefer it. Otherwise, use the
  221. # password hash from an earlier request.
  222. if new_password:
  223. password_hash: Optional[str] = await self.auth_handler.hash(new_password)
  224. elif session_id is not None:
  225. password_hash = await self.auth_handler.get_session_data(
  226. session_id, UIAuthSessionDataConstants.PASSWORD_HASH, None
  227. )
  228. else:
  229. # UI validation was skipped, but the request did not include a new
  230. # password.
  231. password_hash = None
  232. if not password_hash:
  233. raise SynapseError(400, "Missing params: password", Codes.MISSING_PARAM)
  234. logout_devices = params.get("logout_devices", True)
  235. await self._set_password_handler.set_password(
  236. user_id, password_hash, logout_devices, requester
  237. )
  238. return 200, {}
  239. class DeactivateAccountRestServlet(RestServlet):
  240. PATTERNS = client_patterns("/account/deactivate$")
  241. def __init__(self, hs: "HomeServer"):
  242. super().__init__()
  243. self.hs = hs
  244. self.auth = hs.get_auth()
  245. self.auth_handler = hs.get_auth_handler()
  246. self._deactivate_account_handler = hs.get_deactivate_account_handler()
  247. @interactive_auth_handler
  248. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  249. body = parse_json_object_from_request(request)
  250. erase = body.get("erase", False)
  251. if not isinstance(erase, bool):
  252. raise SynapseError(
  253. HTTPStatus.BAD_REQUEST,
  254. "Param 'erase' must be a boolean, if given",
  255. Codes.BAD_JSON,
  256. )
  257. requester = await self.auth.get_user_by_req(request)
  258. # allow ASes to deactivate their own users
  259. if requester.app_service:
  260. await self._deactivate_account_handler.deactivate_account(
  261. requester.user.to_string(), erase, requester
  262. )
  263. return 200, {}
  264. await self.auth_handler.validate_user_via_ui_auth(
  265. requester,
  266. request,
  267. body,
  268. "deactivate your account",
  269. )
  270. result = await self._deactivate_account_handler.deactivate_account(
  271. requester.user.to_string(),
  272. erase,
  273. requester,
  274. id_server=body.get("id_server"),
  275. )
  276. if result:
  277. id_server_unbind_result = "success"
  278. else:
  279. id_server_unbind_result = "no-support"
  280. return 200, {"id_server_unbind_result": id_server_unbind_result}
  281. class EmailThreepidRequestTokenRestServlet(RestServlet):
  282. PATTERNS = client_patterns("/account/3pid/email/requestToken$")
  283. def __init__(self, hs: "HomeServer"):
  284. super().__init__()
  285. self.hs = hs
  286. self.config = hs.config
  287. self.identity_handler = hs.get_identity_handler()
  288. self.store = self.hs.get_datastores().main
  289. if self.config.email.can_verify_email:
  290. self.mailer = Mailer(
  291. hs=self.hs,
  292. app_name=self.config.email.email_app_name,
  293. template_html=self.config.email.email_add_threepid_template_html,
  294. template_text=self.config.email.email_add_threepid_template_text,
  295. )
  296. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  297. if not self.config.email.can_verify_email:
  298. logger.warning(
  299. "Adding emails have been disabled due to lack of an email config"
  300. )
  301. raise SynapseError(
  302. 400, "Adding an email to your account is disabled on this server"
  303. )
  304. body = parse_json_object_from_request(request)
  305. assert_params_in_dict(body, ["client_secret", "email", "send_attempt"])
  306. client_secret = body["client_secret"]
  307. assert_valid_client_secret(client_secret)
  308. # Canonicalise the email address. The addresses are all stored canonicalised
  309. # in the database.
  310. # This ensures that the validation email is sent to the canonicalised address
  311. # as it will later be entered into the database.
  312. # Otherwise the email will be sent to "FOO@bar.com" and stored as
  313. # "foo@bar.com" in database.
  314. try:
  315. email = validate_email(body["email"])
  316. except ValueError as e:
  317. raise SynapseError(400, str(e))
  318. send_attempt = body["send_attempt"]
  319. next_link = body.get("next_link") # Optional param
  320. if not await check_3pid_allowed(self.hs, "email", email):
  321. raise SynapseError(
  322. 403,
  323. "Your email domain is not authorized on this server",
  324. Codes.THREEPID_DENIED,
  325. )
  326. await self.identity_handler.ratelimit_request_token_requests(
  327. request, "email", email
  328. )
  329. if next_link:
  330. # Raise if the provided next_link value isn't valid
  331. assert_valid_next_link(self.hs, next_link)
  332. existing_user_id = await self.store.get_user_id_by_threepid("email", email)
  333. if existing_user_id is not None:
  334. if self.config.server.request_token_inhibit_3pid_errors:
  335. # Make the client think the operation succeeded. See the rationale in the
  336. # comments for request_token_inhibit_3pid_errors.
  337. # Also wait for some random amount of time between 100ms and 1s to make it
  338. # look like we did something.
  339. await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
  340. return 200, {"sid": random_string(16)}
  341. raise SynapseError(400, "Email is already in use", Codes.THREEPID_IN_USE)
  342. sid = await self.identity_handler.send_threepid_validation(
  343. email,
  344. client_secret,
  345. send_attempt,
  346. self.mailer.send_add_threepid_mail,
  347. next_link,
  348. )
  349. threepid_send_requests.labels(type="email", reason="add_threepid").observe(
  350. send_attempt
  351. )
  352. # Wrap the session id in a JSON object
  353. return 200, {"sid": sid}
  354. class MsisdnThreepidRequestTokenRestServlet(RestServlet):
  355. PATTERNS = client_patterns("/account/3pid/msisdn/requestToken$")
  356. def __init__(self, hs: "HomeServer"):
  357. self.hs = hs
  358. super().__init__()
  359. self.store = self.hs.get_datastores().main
  360. self.identity_handler = hs.get_identity_handler()
  361. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  362. body = parse_json_object_from_request(request)
  363. assert_params_in_dict(
  364. body, ["client_secret", "country", "phone_number", "send_attempt"]
  365. )
  366. client_secret = body["client_secret"]
  367. assert_valid_client_secret(client_secret)
  368. country = body["country"]
  369. phone_number = body["phone_number"]
  370. send_attempt = body["send_attempt"]
  371. next_link = body.get("next_link") # Optional param
  372. msisdn = phone_number_to_msisdn(country, phone_number)
  373. if not await check_3pid_allowed(self.hs, "msisdn", msisdn):
  374. raise SynapseError(
  375. 403,
  376. "Account phone numbers are not authorized on this server",
  377. Codes.THREEPID_DENIED,
  378. )
  379. await self.identity_handler.ratelimit_request_token_requests(
  380. request, "msisdn", msisdn
  381. )
  382. if next_link:
  383. # Raise if the provided next_link value isn't valid
  384. assert_valid_next_link(self.hs, next_link)
  385. existing_user_id = await self.store.get_user_id_by_threepid("msisdn", msisdn)
  386. if existing_user_id is not None:
  387. if self.hs.config.server.request_token_inhibit_3pid_errors:
  388. # Make the client think the operation succeeded. See the rationale in the
  389. # comments for request_token_inhibit_3pid_errors.
  390. # Also wait for some random amount of time between 100ms and 1s to make it
  391. # look like we did something.
  392. await self.hs.get_clock().sleep(random.randint(1, 10) / 10)
  393. return 200, {"sid": random_string(16)}
  394. raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE)
  395. if not self.hs.config.registration.account_threepid_delegate_msisdn:
  396. logger.warning(
  397. "No upstream msisdn account_threepid_delegate configured on the server to "
  398. "handle this request"
  399. )
  400. raise SynapseError(
  401. 400,
  402. "Adding phone numbers to user account is not supported by this homeserver",
  403. )
  404. ret = await self.identity_handler.requestMsisdnToken(
  405. self.hs.config.registration.account_threepid_delegate_msisdn,
  406. country,
  407. phone_number,
  408. client_secret,
  409. send_attempt,
  410. next_link,
  411. )
  412. threepid_send_requests.labels(type="msisdn", reason="add_threepid").observe(
  413. send_attempt
  414. )
  415. return 200, ret
  416. class AddThreepidEmailSubmitTokenServlet(RestServlet):
  417. """Handles 3PID validation token submission for adding an email to a user's account"""
  418. PATTERNS = client_patterns(
  419. "/add_threepid/email/submit_token$", releases=(), unstable=True
  420. )
  421. def __init__(self, hs: "HomeServer"):
  422. super().__init__()
  423. self.config = hs.config
  424. self.clock = hs.get_clock()
  425. self.store = hs.get_datastores().main
  426. if self.config.email.can_verify_email:
  427. self._failure_email_template = (
  428. self.config.email.email_add_threepid_template_failure_html
  429. )
  430. async def on_GET(self, request: Request) -> None:
  431. if not self.config.email.can_verify_email:
  432. logger.warning(
  433. "Adding emails have been disabled due to lack of an email config"
  434. )
  435. raise SynapseError(
  436. 400, "Adding an email to your account is disabled on this server"
  437. )
  438. sid = parse_string(request, "sid", required=True)
  439. token = parse_string(request, "token", required=True)
  440. client_secret = parse_string(request, "client_secret", required=True)
  441. assert_valid_client_secret(client_secret)
  442. # Attempt to validate a 3PID session
  443. try:
  444. # Mark the session as valid
  445. next_link = await self.store.validate_threepid_session(
  446. sid, client_secret, token, self.clock.time_msec()
  447. )
  448. # Perform a 302 redirect if next_link is set
  449. if next_link:
  450. request.setResponseCode(302)
  451. request.setHeader("Location", next_link)
  452. finish_request(request)
  453. return None
  454. # Otherwise show the success template
  455. html = self.config.email.email_add_threepid_template_success_html_content
  456. status_code = 200
  457. except ThreepidValidationError as e:
  458. status_code = e.code
  459. # Show a failure page with a reason
  460. template_vars = {"failure_reason": e.msg}
  461. html = self._failure_email_template.render(**template_vars)
  462. respond_with_html(request, status_code, html)
  463. class AddThreepidMsisdnSubmitTokenServlet(RestServlet):
  464. """Handles 3PID validation token submission for adding a phone number to a user's
  465. account
  466. """
  467. PATTERNS = client_patterns(
  468. "/add_threepid/msisdn/submit_token$", releases=(), unstable=True
  469. )
  470. def __init__(self, hs: "HomeServer"):
  471. super().__init__()
  472. self.config = hs.config
  473. self.clock = hs.get_clock()
  474. self.store = hs.get_datastores().main
  475. self.identity_handler = hs.get_identity_handler()
  476. async def on_POST(self, request: Request) -> Tuple[int, JsonDict]:
  477. if not self.config.registration.account_threepid_delegate_msisdn:
  478. raise SynapseError(
  479. 400,
  480. "This homeserver is not validating phone numbers. Use an identity server "
  481. "instead.",
  482. )
  483. body = parse_json_object_from_request(request)
  484. assert_params_in_dict(body, ["client_secret", "sid", "token"])
  485. assert_valid_client_secret(body["client_secret"])
  486. # Proxy submit_token request to msisdn threepid delegate
  487. response = await self.identity_handler.proxy_msisdn_submit_token(
  488. self.config.registration.account_threepid_delegate_msisdn,
  489. body["client_secret"],
  490. body["sid"],
  491. body["token"],
  492. )
  493. return 200, response
  494. class ThreepidRestServlet(RestServlet):
  495. PATTERNS = client_patterns("/account/3pid$")
  496. def __init__(self, hs: "HomeServer"):
  497. super().__init__()
  498. self.hs = hs
  499. self.identity_handler = hs.get_identity_handler()
  500. self.auth = hs.get_auth()
  501. self.auth_handler = hs.get_auth_handler()
  502. self.datastore = self.hs.get_datastores().main
  503. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  504. requester = await self.auth.get_user_by_req(request)
  505. threepids = await self.datastore.user_get_threepids(requester.user.to_string())
  506. return 200, {"threepids": threepids}
  507. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  508. if not self.hs.config.registration.enable_3pid_changes:
  509. raise SynapseError(
  510. 400, "3PID changes are disabled on this server", Codes.FORBIDDEN
  511. )
  512. requester = await self.auth.get_user_by_req(request)
  513. user_id = requester.user.to_string()
  514. body = parse_json_object_from_request(request)
  515. threepid_creds = body.get("threePidCreds") or body.get("three_pid_creds")
  516. if threepid_creds is None:
  517. raise SynapseError(
  518. 400, "Missing param three_pid_creds", Codes.MISSING_PARAM
  519. )
  520. assert_params_in_dict(threepid_creds, ["client_secret", "sid"])
  521. sid = threepid_creds["sid"]
  522. client_secret = threepid_creds["client_secret"]
  523. assert_valid_client_secret(client_secret)
  524. validation_session = await self.identity_handler.validate_threepid_session(
  525. client_secret, sid
  526. )
  527. if validation_session:
  528. await self.auth_handler.add_threepid(
  529. user_id,
  530. validation_session["medium"],
  531. validation_session["address"],
  532. validation_session["validated_at"],
  533. )
  534. return 200, {}
  535. raise SynapseError(
  536. 400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED
  537. )
  538. class ThreepidAddRestServlet(RestServlet):
  539. PATTERNS = client_patterns("/account/3pid/add$")
  540. def __init__(self, hs: "HomeServer"):
  541. super().__init__()
  542. self.hs = hs
  543. self.identity_handler = hs.get_identity_handler()
  544. self.auth = hs.get_auth()
  545. self.auth_handler = hs.get_auth_handler()
  546. @interactive_auth_handler
  547. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  548. if not self.hs.config.registration.enable_3pid_changes:
  549. raise SynapseError(
  550. 400, "3PID changes are disabled on this server", Codes.FORBIDDEN
  551. )
  552. requester = await self.auth.get_user_by_req(request)
  553. user_id = requester.user.to_string()
  554. body = parse_json_object_from_request(request)
  555. assert_params_in_dict(body, ["client_secret", "sid"])
  556. sid = body["sid"]
  557. client_secret = body["client_secret"]
  558. assert_valid_client_secret(client_secret)
  559. await self.auth_handler.validate_user_via_ui_auth(
  560. requester,
  561. request,
  562. body,
  563. "add a third-party identifier to your account",
  564. )
  565. validation_session = await self.identity_handler.validate_threepid_session(
  566. client_secret, sid
  567. )
  568. if validation_session:
  569. await self.auth_handler.add_threepid(
  570. user_id,
  571. validation_session["medium"],
  572. validation_session["address"],
  573. validation_session["validated_at"],
  574. )
  575. return 200, {}
  576. raise SynapseError(
  577. 400, "No validated 3pid session found", Codes.THREEPID_AUTH_FAILED
  578. )
  579. class ThreepidBindRestServlet(RestServlet):
  580. PATTERNS = client_patterns("/account/3pid/bind$")
  581. def __init__(self, hs: "HomeServer"):
  582. super().__init__()
  583. self.hs = hs
  584. self.identity_handler = hs.get_identity_handler()
  585. self.auth = hs.get_auth()
  586. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  587. body = parse_json_object_from_request(request)
  588. assert_params_in_dict(body, ["id_server", "sid", "client_secret"])
  589. id_server = body["id_server"]
  590. sid = body["sid"]
  591. id_access_token = body.get("id_access_token") # optional
  592. client_secret = body["client_secret"]
  593. assert_valid_client_secret(client_secret)
  594. requester = await self.auth.get_user_by_req(request)
  595. user_id = requester.user.to_string()
  596. await self.identity_handler.bind_threepid(
  597. client_secret, sid, user_id, id_server, id_access_token
  598. )
  599. return 200, {}
  600. class ThreepidUnbindRestServlet(RestServlet):
  601. PATTERNS = client_patterns("/account/3pid/unbind$")
  602. def __init__(self, hs: "HomeServer"):
  603. super().__init__()
  604. self.hs = hs
  605. self.identity_handler = hs.get_identity_handler()
  606. self.auth = hs.get_auth()
  607. self.datastore = self.hs.get_datastores().main
  608. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  609. """Unbind the given 3pid from a specific identity server, or identity servers that are
  610. known to have this 3pid bound
  611. """
  612. requester = await self.auth.get_user_by_req(request)
  613. body = parse_json_object_from_request(request)
  614. assert_params_in_dict(body, ["medium", "address"])
  615. medium = body.get("medium")
  616. address = body.get("address")
  617. id_server = body.get("id_server")
  618. # Attempt to unbind the threepid from an identity server. If id_server is None, try to
  619. # unbind from all identity servers this threepid has been added to in the past
  620. result = await self.identity_handler.try_unbind_threepid(
  621. requester.user.to_string(),
  622. {"address": address, "medium": medium, "id_server": id_server},
  623. )
  624. return 200, {"id_server_unbind_result": "success" if result else "no-support"}
  625. class ThreepidDeleteRestServlet(RestServlet):
  626. PATTERNS = client_patterns("/account/3pid/delete$")
  627. def __init__(self, hs: "HomeServer"):
  628. super().__init__()
  629. self.hs = hs
  630. self.auth = hs.get_auth()
  631. self.auth_handler = hs.get_auth_handler()
  632. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  633. if not self.hs.config.registration.enable_3pid_changes:
  634. raise SynapseError(
  635. 400, "3PID changes are disabled on this server", Codes.FORBIDDEN
  636. )
  637. body = parse_json_object_from_request(request)
  638. assert_params_in_dict(body, ["medium", "address"])
  639. requester = await self.auth.get_user_by_req(request)
  640. user_id = requester.user.to_string()
  641. try:
  642. ret = await self.auth_handler.delete_threepid(
  643. user_id, body["medium"], body["address"], body.get("id_server")
  644. )
  645. except Exception:
  646. # NB. This endpoint should succeed if there is nothing to
  647. # delete, so it should only throw if something is wrong
  648. # that we ought to care about.
  649. logger.exception("Failed to remove threepid")
  650. raise SynapseError(500, "Failed to remove threepid")
  651. if ret:
  652. id_server_unbind_result = "success"
  653. else:
  654. id_server_unbind_result = "no-support"
  655. return 200, {"id_server_unbind_result": id_server_unbind_result}
  656. def assert_valid_next_link(hs: "HomeServer", next_link: str) -> None:
  657. """
  658. Raises a SynapseError if a given next_link value is invalid
  659. next_link is valid if the scheme is http(s) and the next_link.domain_whitelist config
  660. option is either empty or contains a domain that matches the one in the given next_link
  661. Args:
  662. hs: The homeserver object
  663. next_link: The next_link value given by the client
  664. Raises:
  665. SynapseError: If the next_link is invalid
  666. """
  667. valid = True
  668. # Parse the contents of the URL
  669. next_link_parsed = urlparse(next_link)
  670. # Scheme must not point to the local drive
  671. if next_link_parsed.scheme == "file":
  672. valid = False
  673. # If the domain whitelist is set, the domain must be in it
  674. if (
  675. valid
  676. and hs.config.server.next_link_domain_whitelist is not None
  677. and next_link_parsed.hostname not in hs.config.server.next_link_domain_whitelist
  678. ):
  679. valid = False
  680. if not valid:
  681. raise SynapseError(
  682. 400,
  683. "'next_link' domain not included in whitelist, or not http(s)",
  684. errcode=Codes.INVALID_PARAM,
  685. )
  686. class WhoamiRestServlet(RestServlet):
  687. PATTERNS = client_patterns("/account/whoami$")
  688. def __init__(self, hs: "HomeServer"):
  689. super().__init__()
  690. self.auth = hs.get_auth()
  691. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  692. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  693. response = {
  694. "user_id": requester.user.to_string(),
  695. # Entered spec in Matrix 1.2
  696. "is_guest": bool(requester.is_guest),
  697. }
  698. # Appservices and similar accounts do not have device IDs
  699. # that we can report on, so exclude them for compliance.
  700. if requester.device_id is not None:
  701. response["device_id"] = requester.device_id
  702. return 200, response
  703. class AccountStatusRestServlet(RestServlet):
  704. PATTERNS = client_patterns(
  705. "/org.matrix.msc3720/account_status$", unstable=True, releases=()
  706. )
  707. def __init__(self, hs: "HomeServer"):
  708. super().__init__()
  709. self._auth = hs.get_auth()
  710. self._account_handler = hs.get_account_handler()
  711. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  712. await self._auth.get_user_by_req(request)
  713. body = parse_json_object_from_request(request)
  714. if "user_ids" not in body:
  715. raise SynapseError(
  716. 400, "Required parameter 'user_ids' is missing", Codes.MISSING_PARAM
  717. )
  718. statuses, failures = await self._account_handler.get_account_statuses(
  719. body["user_ids"],
  720. allow_remote=True,
  721. )
  722. return 200, {"account_statuses": statuses, "failures": failures}
  723. def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
  724. EmailPasswordRequestTokenRestServlet(hs).register(http_server)
  725. PasswordRestServlet(hs).register(http_server)
  726. DeactivateAccountRestServlet(hs).register(http_server)
  727. EmailThreepidRequestTokenRestServlet(hs).register(http_server)
  728. MsisdnThreepidRequestTokenRestServlet(hs).register(http_server)
  729. AddThreepidEmailSubmitTokenServlet(hs).register(http_server)
  730. AddThreepidMsisdnSubmitTokenServlet(hs).register(http_server)
  731. ThreepidRestServlet(hs).register(http_server)
  732. ThreepidAddRestServlet(hs).register(http_server)
  733. ThreepidBindRestServlet(hs).register(http_server)
  734. ThreepidUnbindRestServlet(hs).register(http_server)
  735. ThreepidDeleteRestServlet(hs).register(http_server)
  736. WhoamiRestServlet(hs).register(http_server)
  737. if hs.config.experimental.msc3720_enabled:
  738. AccountStatusRestServlet(hs).register(http_server)