register.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. ):
  110. """Registers a new client on the server.
  111. Args:
  112. localpart : The local part of the user ID to register. If None,
  113. one will be generated.
  114. password (unicode) : The password to assign to this user so they can
  115. login again. This can be None which means they cannot login again
  116. via a password (e.g. the user is an application service user).
  117. generate_token (bool): Whether a new access token should be
  118. generated. Having this be True should be considered deprecated,
  119. since it offers no means of associating a device_id with the
  120. access_token. Instead you should call auth_handler.issue_access_token
  121. after registration.
  122. Returns:
  123. A tuple of (user_id, access_token).
  124. Raises:
  125. RegistrationError if there was a problem registering.
  126. """
  127. yield self.auth.check_auth_blocking(threepid=threepid)
  128. password_hash = None
  129. if password:
  130. password_hash = yield self.auth_handler().hash(password)
  131. if localpart:
  132. yield self.check_username(localpart, guest_access_token=guest_access_token)
  133. was_guest = guest_access_token is not None
  134. if not was_guest:
  135. try:
  136. int(localpart)
  137. raise RegistrationError(
  138. 400,
  139. "Numeric user IDs are reserved for guest users."
  140. )
  141. except ValueError:
  142. pass
  143. user = UserID(localpart, self.hs.hostname)
  144. user_id = user.to_string()
  145. token = None
  146. if generate_token:
  147. token = self.macaroon_gen.generate_access_token(user_id)
  148. yield self.store.register(
  149. user_id=user_id,
  150. token=token,
  151. password_hash=password_hash,
  152. was_guest=was_guest,
  153. make_guest=make_guest,
  154. create_profile_with_localpart=(
  155. # If the user was a guest then they already have a profile
  156. None if was_guest else user.localpart
  157. ),
  158. admin=admin,
  159. )
  160. if self.hs.config.user_directory_search_all_users:
  161. profile = yield self.store.get_profileinfo(localpart)
  162. yield self.user_directory_handler.handle_local_profile_change(
  163. user_id, profile
  164. )
  165. else:
  166. # autogen a sequential user ID
  167. attempts = 0
  168. token = None
  169. user = None
  170. while not user:
  171. localpart = yield self._generate_user_id(attempts > 0)
  172. user = UserID(localpart, self.hs.hostname)
  173. user_id = user.to_string()
  174. yield self.check_user_id_not_appservice_exclusive(user_id)
  175. if generate_token:
  176. token = self.macaroon_gen.generate_access_token(user_id)
  177. try:
  178. yield self.store.register(
  179. user_id=user_id,
  180. token=token,
  181. password_hash=password_hash,
  182. make_guest=make_guest,
  183. create_profile_with_localpart=user.localpart,
  184. )
  185. except SynapseError:
  186. # if user id is taken, just generate another
  187. user = None
  188. user_id = None
  189. token = None
  190. attempts += 1
  191. # auto-join the user to any rooms we're supposed to dump them into
  192. fake_requester = create_requester(user_id)
  193. # try to create the room if we're the first user on the server
  194. if self.hs.config.autocreate_auto_join_rooms:
  195. count = yield self.store.count_all_users()
  196. auto_create_rooms = count == 1
  197. for r in self.hs.config.auto_join_rooms:
  198. try:
  199. if auto_create_rooms and RoomAlias.is_valid(r):
  200. room_creation_handler = self.hs.get_room_creation_handler()
  201. # create room expects the localpart of the room alias
  202. room_alias_localpart = RoomAlias.from_string(r).localpart
  203. yield room_creation_handler.create_room(
  204. fake_requester,
  205. config={
  206. "preset": "public_chat",
  207. "room_alias_name": room_alias_localpart
  208. },
  209. ratelimit=False,
  210. )
  211. yield self._join_user_to_room(fake_requester, r)
  212. except Exception as e:
  213. logger.error("Failed to join new user to %r: %r", r, e)
  214. # We used to generate default identicons here, but nowadays
  215. # we want clients to generate their own as part of their branding
  216. # rather than there being consistent matrix-wide ones, so we don't.
  217. defer.returnValue((user_id, token))
  218. @defer.inlineCallbacks
  219. def appservice_register(self, user_localpart, as_token):
  220. user = UserID(user_localpart, self.hs.hostname)
  221. user_id = user.to_string()
  222. service = self.store.get_app_service_by_token(as_token)
  223. if not service:
  224. raise AuthError(403, "Invalid application service token.")
  225. if not service.is_interested_in_user(user_id):
  226. raise SynapseError(
  227. 400, "Invalid user localpart for this application service.",
  228. errcode=Codes.EXCLUSIVE
  229. )
  230. service_id = service.id if service.is_exclusive_user(user_id) else None
  231. yield self.check_user_id_not_appservice_exclusive(
  232. user_id, allowed_appservice=service
  233. )
  234. yield self.store.register(
  235. user_id=user_id,
  236. password_hash="",
  237. appservice_id=service_id,
  238. create_profile_with_localpart=user.localpart,
  239. )
  240. defer.returnValue(user_id)
  241. @defer.inlineCallbacks
  242. def check_recaptcha(self, ip, private_key, challenge, response):
  243. """
  244. Checks a recaptcha is correct.
  245. Used only by c/s api v1
  246. """
  247. captcha_response = yield self._validate_captcha(
  248. ip,
  249. private_key,
  250. challenge,
  251. response
  252. )
  253. if not captcha_response["valid"]:
  254. logger.info("Invalid captcha entered from %s. Error: %s",
  255. ip, captcha_response["error_url"])
  256. raise InvalidCaptchaError(
  257. error_url=captcha_response["error_url"]
  258. )
  259. else:
  260. logger.info("Valid captcha entered from %s", ip)
  261. @defer.inlineCallbacks
  262. def register_saml2(self, localpart):
  263. """
  264. Registers email_id as SAML2 Based Auth.
  265. """
  266. if types.contains_invalid_mxid_characters(localpart):
  267. raise SynapseError(
  268. 400,
  269. "User ID can only contain characters a-z, 0-9, or '=_-./'",
  270. )
  271. yield self.auth.check_auth_blocking()
  272. user = UserID(localpart, self.hs.hostname)
  273. user_id = user.to_string()
  274. yield self.check_user_id_not_appservice_exclusive(user_id)
  275. token = self.macaroon_gen.generate_access_token(user_id)
  276. try:
  277. yield self.store.register(
  278. user_id=user_id,
  279. token=token,
  280. password_hash=None,
  281. create_profile_with_localpart=user.localpart,
  282. )
  283. except Exception as e:
  284. yield self.store.add_access_token_to_user(user_id, token)
  285. # Ignore Registration errors
  286. logger.exception(e)
  287. defer.returnValue((user_id, token))
  288. @defer.inlineCallbacks
  289. def register_email(self, threepidCreds):
  290. """
  291. Registers emails with an identity server.
  292. Used only by c/s api v1
  293. """
  294. for c in threepidCreds:
  295. logger.info("validating threepidcred sid %s on id server %s",
  296. c['sid'], c['idServer'])
  297. try:
  298. identity_handler = self.hs.get_handlers().identity_handler
  299. threepid = yield identity_handler.threepid_from_creds(c)
  300. except Exception:
  301. logger.exception("Couldn't validate 3pid")
  302. raise RegistrationError(400, "Couldn't validate 3pid")
  303. if not threepid:
  304. raise RegistrationError(400, "Couldn't validate 3pid")
  305. logger.info("got threepid with medium '%s' and address '%s'",
  306. threepid['medium'], threepid['address'])
  307. if not check_3pid_allowed(self.hs, threepid['medium'], threepid['address']):
  308. raise RegistrationError(
  309. 403, "Third party identifier is not allowed"
  310. )
  311. @defer.inlineCallbacks
  312. def bind_emails(self, user_id, threepidCreds):
  313. """Links emails with a user ID and informs an identity server.
  314. Used only by c/s api v1
  315. """
  316. # Now we have a matrix ID, bind it to the threepids we were given
  317. for c in threepidCreds:
  318. identity_handler = self.hs.get_handlers().identity_handler
  319. # XXX: This should be a deferred list, shouldn't it?
  320. yield identity_handler.bind_threepid(c, user_id)
  321. def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
  322. # don't allow people to register the server notices mxid
  323. if self._server_notices_mxid is not None:
  324. if user_id == self._server_notices_mxid:
  325. raise SynapseError(
  326. 400, "This user ID is reserved.",
  327. errcode=Codes.EXCLUSIVE
  328. )
  329. # valid user IDs must not clash with any user ID namespaces claimed by
  330. # application services.
  331. services = self.store.get_app_services()
  332. interested_services = [
  333. s for s in services
  334. if s.is_interested_in_user(user_id)
  335. and s != allowed_appservice
  336. ]
  337. for service in interested_services:
  338. if service.is_exclusive_user(user_id):
  339. raise SynapseError(
  340. 400, "This user ID is reserved by an application service.",
  341. errcode=Codes.EXCLUSIVE
  342. )
  343. @defer.inlineCallbacks
  344. def _generate_user_id(self, reseed=False):
  345. if reseed or self._next_generated_user_id is None:
  346. with (yield self._generate_user_id_linearizer.queue(())):
  347. if reseed or self._next_generated_user_id is None:
  348. self._next_generated_user_id = (
  349. yield self.store.find_next_generated_user_id_localpart()
  350. )
  351. id = self._next_generated_user_id
  352. self._next_generated_user_id += 1
  353. defer.returnValue(str(id))
  354. @defer.inlineCallbacks
  355. def _validate_captcha(self, ip_addr, private_key, challenge, response):
  356. """Validates the captcha provided.
  357. Used only by c/s api v1
  358. Returns:
  359. dict: Containing 'valid'(bool) and 'error_url'(str) if invalid.
  360. """
  361. response = yield self._submit_captcha(ip_addr, private_key, challenge,
  362. response)
  363. # parse Google's response. Lovely format..
  364. lines = response.split('\n')
  365. json = {
  366. "valid": lines[0] == 'true',
  367. "error_url": "http://www.google.com/recaptcha/api/challenge?" +
  368. "error=%s" % lines[1]
  369. }
  370. defer.returnValue(json)
  371. @defer.inlineCallbacks
  372. def _submit_captcha(self, ip_addr, private_key, challenge, response):
  373. """
  374. Used only by c/s api v1
  375. """
  376. data = yield self.captcha_client.post_urlencoded_get_raw(
  377. "http://www.google.com:80/recaptcha/api/verify",
  378. args={
  379. 'privatekey': private_key,
  380. 'remoteip': ip_addr,
  381. 'challenge': challenge,
  382. 'response': response
  383. }
  384. )
  385. defer.returnValue(data)
  386. @defer.inlineCallbacks
  387. def get_or_create_user(self, requester, localpart, displayname,
  388. password_hash=None):
  389. """Creates a new user if the user does not exist,
  390. else revokes all previous access tokens and generates a new one.
  391. Args:
  392. localpart : The local part of the user ID to register. If None,
  393. one will be randomly generated.
  394. Returns:
  395. A tuple of (user_id, access_token).
  396. Raises:
  397. RegistrationError if there was a problem registering.
  398. """
  399. if localpart is None:
  400. raise SynapseError(400, "Request must include user id")
  401. yield self.auth.check_auth_blocking()
  402. need_register = True
  403. try:
  404. yield self.check_username(localpart)
  405. except SynapseError as e:
  406. if e.errcode == Codes.USER_IN_USE:
  407. need_register = False
  408. else:
  409. raise
  410. user = UserID(localpart, self.hs.hostname)
  411. user_id = user.to_string()
  412. token = self.macaroon_gen.generate_access_token(user_id)
  413. if need_register:
  414. yield self.store.register(
  415. user_id=user_id,
  416. token=token,
  417. password_hash=password_hash,
  418. create_profile_with_localpart=user.localpart,
  419. )
  420. else:
  421. yield self._auth_handler.delete_access_tokens_for_user(user_id)
  422. yield self.store.add_access_token_to_user(user_id=user_id, token=token)
  423. if displayname is not None:
  424. logger.info("setting user display name: %s -> %s", user_id, displayname)
  425. yield self.profile_handler.set_displayname(
  426. user, requester, displayname, by_admin=True,
  427. )
  428. defer.returnValue((user_id, token))
  429. def auth_handler(self):
  430. return self.hs.get_auth_handler()
  431. @defer.inlineCallbacks
  432. def get_or_register_3pid_guest(self, medium, address, inviter_user_id):
  433. """Get a guest access token for a 3PID, creating a guest account if
  434. one doesn't already exist.
  435. Args:
  436. medium (str)
  437. address (str)
  438. inviter_user_id (str): The user ID who is trying to invite the
  439. 3PID
  440. Returns:
  441. Deferred[(str, str)]: A 2-tuple of `(user_id, access_token)` of the
  442. 3PID guest account.
  443. """
  444. access_token = yield self.store.get_3pid_guest_access_token(medium, address)
  445. if access_token:
  446. user_info = yield self.auth.get_user_by_access_token(
  447. access_token
  448. )
  449. defer.returnValue((user_info["user"].to_string(), access_token))
  450. user_id, access_token = yield self.register(
  451. generate_token=True,
  452. make_guest=True
  453. )
  454. access_token = yield self.store.save_or_get_3pid_guest_access_token(
  455. medium, address, access_token, inviter_user_id
  456. )
  457. defer.returnValue((user_id, access_token))
  458. @defer.inlineCallbacks
  459. def _join_user_to_room(self, requester, room_identifier):
  460. room_id = None
  461. room_member_handler = self.hs.get_room_member_handler()
  462. if RoomID.is_valid(room_identifier):
  463. room_id = room_identifier
  464. elif RoomAlias.is_valid(room_identifier):
  465. room_alias = RoomAlias.from_string(room_identifier)
  466. room_id, remote_room_hosts = (
  467. yield room_member_handler.lookup_room_alias(room_alias)
  468. )
  469. room_id = room_id.to_string()
  470. else:
  471. raise SynapseError(400, "%s was not legal room ID or room alias" % (
  472. room_identifier,
  473. ))
  474. yield room_member_handler.update_membership(
  475. requester=requester,
  476. target=requester.user,
  477. room_id=room_id,
  478. remote_room_hosts=remote_room_hosts,
  479. action="join",
  480. ratelimit=False,
  481. )