register.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 - 2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Contains functions for registering clients."""
  16. import logging
  17. from twisted.internet import defer
  18. from synapse import types
  19. from synapse.api.errors import (
  20. AuthError,
  21. Codes,
  22. InvalidCaptchaError,
  23. RegistrationError,
  24. SynapseError,
  25. )
  26. from synapse.http.client import CaptchaServerHttpClient
  27. from synapse.types import RoomAlias, RoomID, UserID, create_requester
  28. from synapse.util.async_helpers import Linearizer
  29. from synapse.util.threepids import check_3pid_allowed
  30. from ._base import BaseHandler
  31. logger = logging.getLogger(__name__)
  32. class RegistrationHandler(BaseHandler):
  33. def __init__(self, hs):
  34. """
  35. Args:
  36. hs (synapse.server.HomeServer):
  37. """
  38. super(RegistrationHandler, self).__init__(hs)
  39. self.hs = hs
  40. self.auth = hs.get_auth()
  41. self._auth_handler = hs.get_auth_handler()
  42. self.profile_handler = hs.get_profile_handler()
  43. self.user_directory_handler = hs.get_user_directory_handler()
  44. self.captcha_client = CaptchaServerHttpClient(hs)
  45. self._next_generated_user_id = None
  46. self.macaroon_gen = hs.get_macaroon_generator()
  47. self._generate_user_id_linearizer = Linearizer(
  48. name="_generate_user_id_linearizer",
  49. )
  50. self._server_notices_mxid = hs.config.server_notices_mxid
  51. @defer.inlineCallbacks
  52. def check_username(self, localpart, guest_access_token=None,
  53. assigned_user_id=None):
  54. if types.contains_invalid_mxid_characters(localpart):
  55. raise SynapseError(
  56. 400,
  57. "User ID can only contain characters a-z, 0-9, or '=_-./'",
  58. Codes.INVALID_USERNAME
  59. )
  60. if not localpart:
  61. raise SynapseError(
  62. 400,
  63. "User ID cannot be empty",
  64. Codes.INVALID_USERNAME
  65. )
  66. if localpart[0] == '_':
  67. raise SynapseError(
  68. 400,
  69. "User ID may not begin with _",
  70. Codes.INVALID_USERNAME
  71. )
  72. user = UserID(localpart, self.hs.hostname)
  73. user_id = user.to_string()
  74. if assigned_user_id:
  75. if user_id == assigned_user_id:
  76. return
  77. else:
  78. raise SynapseError(
  79. 400,
  80. "A different user ID has already been registered for this session",
  81. )
  82. self.check_user_id_not_appservice_exclusive(user_id)
  83. users = yield self.store.get_users_by_id_case_insensitive(user_id)
  84. if users:
  85. if not guest_access_token:
  86. raise SynapseError(
  87. 400,
  88. "User ID already taken.",
  89. errcode=Codes.USER_IN_USE,
  90. )
  91. user_data = yield self.auth.get_user_by_access_token(guest_access_token)
  92. if not user_data["is_guest"] or user_data["user"].localpart != localpart:
  93. raise AuthError(
  94. 403,
  95. "Cannot register taken user ID without valid guest "
  96. "credentials for that user.",
  97. errcode=Codes.FORBIDDEN,
  98. )
  99. @defer.inlineCallbacks
  100. def register(
  101. self,
  102. localpart=None,
  103. password=None,
  104. generate_token=True,
  105. guest_access_token=None,
  106. make_guest=False,
  107. admin=False,
  108. threepid=None,
  109. user_type=None,
  110. default_display_name=None,
  111. ):
  112. """Registers a new client on the server.
  113. Args:
  114. localpart : The local part of the user ID to register. If None,
  115. one will be generated.
  116. password (unicode) : The password to assign to this user so they can
  117. login again. This can be None which means they cannot login again
  118. via a password (e.g. the user is an application service user).
  119. generate_token (bool): Whether a new access token should be
  120. generated. Having this be True should be considered deprecated,
  121. since it offers no means of associating a device_id with the
  122. access_token. Instead you should call auth_handler.issue_access_token
  123. after registration.
  124. user_type (str|None): type of user. One of the values from
  125. api.constants.UserTypes, or None for a normal user.
  126. default_display_name (unicode|None): if set, the new user's displayname
  127. will be set to this. Defaults to 'localpart'.
  128. Returns:
  129. A tuple of (user_id, access_token).
  130. Raises:
  131. RegistrationError if there was a problem registering.
  132. """
  133. yield self.auth.check_auth_blocking(threepid=threepid)
  134. password_hash = None
  135. if password:
  136. password_hash = yield self.auth_handler().hash(password)
  137. if localpart:
  138. yield self.check_username(localpart, guest_access_token=guest_access_token)
  139. was_guest = guest_access_token is not None
  140. if not was_guest:
  141. try:
  142. int(localpart)
  143. raise RegistrationError(
  144. 400,
  145. "Numeric user IDs are reserved for guest users."
  146. )
  147. except ValueError:
  148. pass
  149. user = UserID(localpart, self.hs.hostname)
  150. user_id = user.to_string()
  151. if was_guest:
  152. # If the user was a guest then they already have a profile
  153. default_display_name = None
  154. elif default_display_name is None:
  155. default_display_name = localpart
  156. token = None
  157. if generate_token:
  158. token = self.macaroon_gen.generate_access_token(user_id)
  159. yield self.store.register(
  160. user_id=user_id,
  161. token=token,
  162. password_hash=password_hash,
  163. was_guest=was_guest,
  164. make_guest=make_guest,
  165. create_profile_with_displayname=default_display_name,
  166. admin=admin,
  167. user_type=user_type,
  168. )
  169. if self.hs.config.user_directory_search_all_users:
  170. profile = yield self.store.get_profileinfo(localpart)
  171. yield self.user_directory_handler.handle_local_profile_change(
  172. user_id, profile
  173. )
  174. else:
  175. # autogen a sequential user ID
  176. attempts = 0
  177. token = None
  178. user = None
  179. while not user:
  180. localpart = yield self._generate_user_id(attempts > 0)
  181. user = UserID(localpart, self.hs.hostname)
  182. user_id = user.to_string()
  183. yield self.check_user_id_not_appservice_exclusive(user_id)
  184. if generate_token:
  185. token = self.macaroon_gen.generate_access_token(user_id)
  186. if default_display_name is None:
  187. default_display_name = localpart
  188. try:
  189. yield self.store.register(
  190. user_id=user_id,
  191. token=token,
  192. password_hash=password_hash,
  193. make_guest=make_guest,
  194. create_profile_with_displayname=default_display_name,
  195. )
  196. except SynapseError:
  197. # if user id is taken, just generate another
  198. user = None
  199. user_id = None
  200. token = None
  201. attempts += 1
  202. if not self.hs.config.user_consent_at_registration:
  203. yield self._auto_join_rooms(user_id)
  204. defer.returnValue((user_id, token))
  205. @defer.inlineCallbacks
  206. def _auto_join_rooms(self, user_id):
  207. """Automatically joins users to auto join rooms - creating the room in the first place
  208. if the user is the first to be created.
  209. Args:
  210. user_id(str): The user to join
  211. """
  212. # auto-join the user to any rooms we're supposed to dump them into
  213. fake_requester = create_requester(user_id)
  214. # try to create the room if we're the first real user on the server. Note
  215. # that an auto-generated support user is not a real user and will never be
  216. # the user to create the room
  217. should_auto_create_rooms = False
  218. is_support = yield self.store.is_support_user(user_id)
  219. # There is an edge case where the first user is the support user, then
  220. # the room is never created, though this seems unlikely and
  221. # recoverable from given the support user being involved in the first
  222. # place.
  223. if self.hs.config.autocreate_auto_join_rooms and not is_support:
  224. count = yield self.store.count_all_users()
  225. should_auto_create_rooms = count == 1
  226. for r in self.hs.config.auto_join_rooms:
  227. try:
  228. if should_auto_create_rooms:
  229. room_alias = RoomAlias.from_string(r)
  230. if self.hs.hostname != room_alias.domain:
  231. logger.warning(
  232. 'Cannot create room alias %s, '
  233. 'it does not match server domain',
  234. r,
  235. )
  236. else:
  237. # create room expects the localpart of the room alias
  238. room_alias_localpart = room_alias.localpart
  239. # getting the RoomCreationHandler during init gives a dependency
  240. # loop
  241. yield self.hs.get_room_creation_handler().create_room(
  242. fake_requester,
  243. config={
  244. "preset": "public_chat",
  245. "room_alias_name": room_alias_localpart
  246. },
  247. ratelimit=False,
  248. )
  249. else:
  250. yield self._join_user_to_room(fake_requester, r)
  251. except Exception as e:
  252. logger.error("Failed to join new user to %r: %r", r, e)
  253. @defer.inlineCallbacks
  254. def post_consent_actions(self, user_id):
  255. """A series of registration actions that can only be carried out once consent
  256. has been granted
  257. Args:
  258. user_id (str): The user to join
  259. """
  260. yield self._auto_join_rooms(user_id)
  261. @defer.inlineCallbacks
  262. def appservice_register(self, user_localpart, as_token):
  263. user = UserID(user_localpart, self.hs.hostname)
  264. user_id = user.to_string()
  265. service = self.store.get_app_service_by_token(as_token)
  266. if not service:
  267. raise AuthError(403, "Invalid application service token.")
  268. if not service.is_interested_in_user(user_id):
  269. raise SynapseError(
  270. 400, "Invalid user localpart for this application service.",
  271. errcode=Codes.EXCLUSIVE
  272. )
  273. service_id = service.id if service.is_exclusive_user(user_id) else None
  274. yield self.check_user_id_not_appservice_exclusive(
  275. user_id, allowed_appservice=service
  276. )
  277. yield self.store.register(
  278. user_id=user_id,
  279. password_hash="",
  280. appservice_id=service_id,
  281. create_profile_with_displayname=user.localpart,
  282. )
  283. defer.returnValue(user_id)
  284. @defer.inlineCallbacks
  285. def check_recaptcha(self, ip, private_key, challenge, response):
  286. """
  287. Checks a recaptcha is correct.
  288. Used only by c/s api v1
  289. """
  290. captcha_response = yield self._validate_captcha(
  291. ip,
  292. private_key,
  293. challenge,
  294. response
  295. )
  296. if not captcha_response["valid"]:
  297. logger.info("Invalid captcha entered from %s. Error: %s",
  298. ip, captcha_response["error_url"])
  299. raise InvalidCaptchaError(
  300. error_url=captcha_response["error_url"]
  301. )
  302. else:
  303. logger.info("Valid captcha entered from %s", ip)
  304. @defer.inlineCallbacks
  305. def register_email(self, threepidCreds):
  306. """
  307. Registers emails with an identity server.
  308. Used only by c/s api v1
  309. """
  310. for c in threepidCreds:
  311. logger.info("validating threepidcred sid %s on id server %s",
  312. c['sid'], c['idServer'])
  313. try:
  314. identity_handler = self.hs.get_handlers().identity_handler
  315. threepid = yield identity_handler.threepid_from_creds(c)
  316. except Exception:
  317. logger.exception("Couldn't validate 3pid")
  318. raise RegistrationError(400, "Couldn't validate 3pid")
  319. if not threepid:
  320. raise RegistrationError(400, "Couldn't validate 3pid")
  321. logger.info("got threepid with medium '%s' and address '%s'",
  322. threepid['medium'], threepid['address'])
  323. if not check_3pid_allowed(self.hs, threepid['medium'], threepid['address']):
  324. raise RegistrationError(
  325. 403, "Third party identifier is not allowed"
  326. )
  327. @defer.inlineCallbacks
  328. def bind_emails(self, user_id, threepidCreds):
  329. """Links emails with a user ID and informs an identity server.
  330. Used only by c/s api v1
  331. """
  332. # Now we have a matrix ID, bind it to the threepids we were given
  333. for c in threepidCreds:
  334. identity_handler = self.hs.get_handlers().identity_handler
  335. # XXX: This should be a deferred list, shouldn't it?
  336. yield identity_handler.bind_threepid(c, user_id)
  337. def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
  338. # don't allow people to register the server notices mxid
  339. if self._server_notices_mxid is not None:
  340. if user_id == self._server_notices_mxid:
  341. raise SynapseError(
  342. 400, "This user ID is reserved.",
  343. errcode=Codes.EXCLUSIVE
  344. )
  345. # valid user IDs must not clash with any user ID namespaces claimed by
  346. # application services.
  347. services = self.store.get_app_services()
  348. interested_services = [
  349. s for s in services
  350. if s.is_interested_in_user(user_id)
  351. and s != allowed_appservice
  352. ]
  353. for service in interested_services:
  354. if service.is_exclusive_user(user_id):
  355. raise SynapseError(
  356. 400, "This user ID is reserved by an application service.",
  357. errcode=Codes.EXCLUSIVE
  358. )
  359. @defer.inlineCallbacks
  360. def _generate_user_id(self, reseed=False):
  361. if reseed or self._next_generated_user_id is None:
  362. with (yield self._generate_user_id_linearizer.queue(())):
  363. if reseed or self._next_generated_user_id is None:
  364. self._next_generated_user_id = (
  365. yield self.store.find_next_generated_user_id_localpart()
  366. )
  367. id = self._next_generated_user_id
  368. self._next_generated_user_id += 1
  369. defer.returnValue(str(id))
  370. @defer.inlineCallbacks
  371. def _validate_captcha(self, ip_addr, private_key, challenge, response):
  372. """Validates the captcha provided.
  373. Used only by c/s api v1
  374. Returns:
  375. dict: Containing 'valid'(bool) and 'error_url'(str) if invalid.
  376. """
  377. response = yield self._submit_captcha(ip_addr, private_key, challenge,
  378. response)
  379. # parse Google's response. Lovely format..
  380. lines = response.split('\n')
  381. json = {
  382. "valid": lines[0] == 'true',
  383. "error_url": "http://www.google.com/recaptcha/api/challenge?" +
  384. "error=%s" % lines[1]
  385. }
  386. defer.returnValue(json)
  387. @defer.inlineCallbacks
  388. def _submit_captcha(self, ip_addr, private_key, challenge, response):
  389. """
  390. Used only by c/s api v1
  391. """
  392. data = yield self.captcha_client.post_urlencoded_get_raw(
  393. "http://www.google.com:80/recaptcha/api/verify",
  394. args={
  395. 'privatekey': private_key,
  396. 'remoteip': ip_addr,
  397. 'challenge': challenge,
  398. 'response': response
  399. }
  400. )
  401. defer.returnValue(data)
  402. @defer.inlineCallbacks
  403. def get_or_create_user(self, requester, localpart, displayname,
  404. password_hash=None):
  405. """Creates a new user if the user does not exist,
  406. else revokes all previous access tokens and generates a new one.
  407. Args:
  408. localpart : The local part of the user ID to register. If None,
  409. one will be randomly generated.
  410. Returns:
  411. A tuple of (user_id, access_token).
  412. Raises:
  413. RegistrationError if there was a problem registering.
  414. """
  415. if localpart is None:
  416. raise SynapseError(400, "Request must include user id")
  417. yield self.auth.check_auth_blocking()
  418. need_register = True
  419. try:
  420. yield self.check_username(localpart)
  421. except SynapseError as e:
  422. if e.errcode == Codes.USER_IN_USE:
  423. need_register = False
  424. else:
  425. raise
  426. user = UserID(localpart, self.hs.hostname)
  427. user_id = user.to_string()
  428. token = self.macaroon_gen.generate_access_token(user_id)
  429. if need_register:
  430. yield self.store.register(
  431. user_id=user_id,
  432. token=token,
  433. password_hash=password_hash,
  434. create_profile_with_displayname=user.localpart,
  435. )
  436. else:
  437. yield self._auth_handler.delete_access_tokens_for_user(user_id)
  438. yield self.store.add_access_token_to_user(user_id=user_id, token=token)
  439. if displayname is not None:
  440. logger.info("setting user display name: %s -> %s", user_id, displayname)
  441. yield self.profile_handler.set_displayname(
  442. user, requester, displayname, by_admin=True,
  443. )
  444. defer.returnValue((user_id, token))
  445. def auth_handler(self):
  446. return self.hs.get_auth_handler()
  447. @defer.inlineCallbacks
  448. def get_or_register_3pid_guest(self, medium, address, inviter_user_id):
  449. """Get a guest access token for a 3PID, creating a guest account if
  450. one doesn't already exist.
  451. Args:
  452. medium (str)
  453. address (str)
  454. inviter_user_id (str): The user ID who is trying to invite the
  455. 3PID
  456. Returns:
  457. Deferred[(str, str)]: A 2-tuple of `(user_id, access_token)` of the
  458. 3PID guest account.
  459. """
  460. access_token = yield self.store.get_3pid_guest_access_token(medium, address)
  461. if access_token:
  462. user_info = yield self.auth.get_user_by_access_token(
  463. access_token
  464. )
  465. defer.returnValue((user_info["user"].to_string(), access_token))
  466. user_id, access_token = yield self.register(
  467. generate_token=True,
  468. make_guest=True
  469. )
  470. access_token = yield self.store.save_or_get_3pid_guest_access_token(
  471. medium, address, access_token, inviter_user_id
  472. )
  473. defer.returnValue((user_id, access_token))
  474. @defer.inlineCallbacks
  475. def _join_user_to_room(self, requester, room_identifier):
  476. room_id = None
  477. room_member_handler = self.hs.get_room_member_handler()
  478. if RoomID.is_valid(room_identifier):
  479. room_id = room_identifier
  480. elif RoomAlias.is_valid(room_identifier):
  481. room_alias = RoomAlias.from_string(room_identifier)
  482. room_id, remote_room_hosts = (
  483. yield room_member_handler.lookup_room_alias(room_alias)
  484. )
  485. room_id = room_id.to_string()
  486. else:
  487. raise SynapseError(400, "%s was not legal room ID or room alias" % (
  488. room_identifier,
  489. ))
  490. yield room_member_handler.update_membership(
  491. requester=requester,
  492. target=requester.user,
  493. room_id=room_id,
  494. remote_room_hosts=remote_room_hosts,
  495. action="join",
  496. ratelimit=False,
  497. )