register.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  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. from twisted.internet import defer
  17. from synapse.types import UserID
  18. from synapse.api.errors import (
  19. AuthError, Codes, SynapseError, RegistrationError, InvalidCaptchaError
  20. )
  21. from ._base import BaseHandler
  22. from synapse.util.async import run_on_reactor
  23. from synapse.http.client import CaptchaServerHttpClient
  24. from synapse.util.distributor import registered_user
  25. import logging
  26. import urllib
  27. logger = logging.getLogger(__name__)
  28. class RegistrationHandler(BaseHandler):
  29. def __init__(self, hs):
  30. super(RegistrationHandler, self).__init__(hs)
  31. self.auth = hs.get_auth()
  32. self.distributor = hs.get_distributor()
  33. self.distributor.declare("registered_user")
  34. self.captcha_client = CaptchaServerHttpClient(hs)
  35. self._next_generated_user_id = None
  36. @defer.inlineCallbacks
  37. def check_username(self, localpart, guest_access_token=None,
  38. assigned_user_id=None):
  39. yield run_on_reactor()
  40. if urllib.quote(localpart.encode('utf-8')) != localpart:
  41. raise SynapseError(
  42. 400,
  43. "User ID can only contain characters a-z, 0-9, or '_-./'",
  44. Codes.INVALID_USERNAME
  45. )
  46. user = UserID(localpart, self.hs.hostname)
  47. user_id = user.to_string()
  48. if assigned_user_id:
  49. if user_id == assigned_user_id:
  50. return
  51. else:
  52. raise SynapseError(
  53. 400,
  54. "A different user ID has already been registered for this session",
  55. )
  56. yield self.check_user_id_not_appservice_exclusive(user_id)
  57. users = yield self.store.get_users_by_id_case_insensitive(user_id)
  58. if users:
  59. if not guest_access_token:
  60. raise SynapseError(
  61. 400,
  62. "User ID already taken.",
  63. errcode=Codes.USER_IN_USE,
  64. )
  65. user_data = yield self.auth.get_user_from_macaroon(guest_access_token)
  66. if not user_data["is_guest"] or user_data["user"].localpart != localpart:
  67. raise AuthError(
  68. 403,
  69. "Cannot register taken user ID without valid guest "
  70. "credentials for that user.",
  71. errcode=Codes.FORBIDDEN,
  72. )
  73. @defer.inlineCallbacks
  74. def register(
  75. self,
  76. localpart=None,
  77. password=None,
  78. generate_token=True,
  79. guest_access_token=None,
  80. make_guest=False
  81. ):
  82. """Registers a new client on the server.
  83. Args:
  84. localpart : The local part of the user ID to register. If None,
  85. one will be generated.
  86. password (str) : The password to assign to this user so they can
  87. login again. This can be None which means they cannot login again
  88. via a password (e.g. the user is an application service user).
  89. Returns:
  90. A tuple of (user_id, access_token).
  91. Raises:
  92. RegistrationError if there was a problem registering.
  93. """
  94. yield run_on_reactor()
  95. password_hash = None
  96. if password:
  97. password_hash = self.auth_handler().hash(password)
  98. if localpart:
  99. yield self.check_username(localpart, guest_access_token=guest_access_token)
  100. was_guest = guest_access_token is not None
  101. if not was_guest:
  102. try:
  103. int(localpart)
  104. raise RegistrationError(
  105. 400,
  106. "Numeric user IDs are reserved for guest users."
  107. )
  108. except ValueError:
  109. pass
  110. user = UserID(localpart, self.hs.hostname)
  111. user_id = user.to_string()
  112. token = None
  113. if generate_token:
  114. token = self.auth_handler().generate_access_token(user_id)
  115. yield self.store.register(
  116. user_id=user_id,
  117. token=token,
  118. password_hash=password_hash,
  119. was_guest=was_guest,
  120. make_guest=make_guest,
  121. )
  122. yield registered_user(self.distributor, user)
  123. else:
  124. # autogen a sequential user ID
  125. attempts = 0
  126. token = None
  127. user = None
  128. while not user:
  129. localpart = yield self._generate_user_id(attempts > 0)
  130. user = UserID(localpart, self.hs.hostname)
  131. user_id = user.to_string()
  132. yield self.check_user_id_not_appservice_exclusive(user_id)
  133. if generate_token:
  134. token = self.auth_handler().generate_access_token(user_id)
  135. try:
  136. yield self.store.register(
  137. user_id=user_id,
  138. token=token,
  139. password_hash=password_hash,
  140. make_guest=make_guest
  141. )
  142. except SynapseError:
  143. # if user id is taken, just generate another
  144. user = None
  145. user_id = None
  146. token = None
  147. attempts += 1
  148. yield registered_user(self.distributor, user)
  149. # We used to generate default identicons here, but nowadays
  150. # we want clients to generate their own as part of their branding
  151. # rather than there being consistent matrix-wide ones, so we don't.
  152. defer.returnValue((user_id, token))
  153. @defer.inlineCallbacks
  154. def appservice_register(self, user_localpart, as_token):
  155. user = UserID(user_localpart, self.hs.hostname)
  156. user_id = user.to_string()
  157. service = yield self.store.get_app_service_by_token(as_token)
  158. if not service:
  159. raise AuthError(403, "Invalid application service token.")
  160. if not service.is_interested_in_user(user_id):
  161. raise SynapseError(
  162. 400, "Invalid user localpart for this application service.",
  163. errcode=Codes.EXCLUSIVE
  164. )
  165. service_id = service.id if service.is_exclusive_user(user_id) else None
  166. yield self.check_user_id_not_appservice_exclusive(
  167. user_id, allowed_appservice=service
  168. )
  169. token = self.auth_handler().generate_access_token(user_id)
  170. yield self.store.register(
  171. user_id=user_id,
  172. token=token,
  173. password_hash="",
  174. appservice_id=service_id,
  175. )
  176. yield registered_user(self.distributor, user)
  177. defer.returnValue((user_id, token))
  178. @defer.inlineCallbacks
  179. def check_recaptcha(self, ip, private_key, challenge, response):
  180. """
  181. Checks a recaptcha is correct.
  182. Used only by c/s api v1
  183. """
  184. captcha_response = yield self._validate_captcha(
  185. ip,
  186. private_key,
  187. challenge,
  188. response
  189. )
  190. if not captcha_response["valid"]:
  191. logger.info("Invalid captcha entered from %s. Error: %s",
  192. ip, captcha_response["error_url"])
  193. raise InvalidCaptchaError(
  194. error_url=captcha_response["error_url"]
  195. )
  196. else:
  197. logger.info("Valid captcha entered from %s", ip)
  198. @defer.inlineCallbacks
  199. def register_saml2(self, localpart):
  200. """
  201. Registers email_id as SAML2 Based Auth.
  202. """
  203. if urllib.quote(localpart) != localpart:
  204. raise SynapseError(
  205. 400,
  206. "User ID must only contain characters which do not"
  207. " require URL encoding."
  208. )
  209. user = UserID(localpart, self.hs.hostname)
  210. user_id = user.to_string()
  211. yield self.check_user_id_not_appservice_exclusive(user_id)
  212. token = self.auth_handler().generate_access_token(user_id)
  213. try:
  214. yield self.store.register(
  215. user_id=user_id,
  216. token=token,
  217. password_hash=None
  218. )
  219. yield registered_user(self.distributor, user)
  220. except Exception as e:
  221. yield self.store.add_access_token_to_user(user_id, token)
  222. # Ignore Registration errors
  223. logger.exception(e)
  224. defer.returnValue((user_id, token))
  225. @defer.inlineCallbacks
  226. def register_email(self, threepidCreds):
  227. """
  228. Registers emails with an identity server.
  229. Used only by c/s api v1
  230. """
  231. for c in threepidCreds:
  232. logger.info("validating theeepidcred sid %s on id server %s",
  233. c['sid'], c['idServer'])
  234. try:
  235. identity_handler = self.hs.get_handlers().identity_handler
  236. threepid = yield identity_handler.threepid_from_creds(c)
  237. except:
  238. logger.exception("Couldn't validate 3pid")
  239. raise RegistrationError(400, "Couldn't validate 3pid")
  240. if not threepid:
  241. raise RegistrationError(400, "Couldn't validate 3pid")
  242. logger.info("got threepid with medium '%s' and address '%s'",
  243. threepid['medium'], threepid['address'])
  244. @defer.inlineCallbacks
  245. def bind_emails(self, user_id, threepidCreds):
  246. """Links emails with a user ID and informs an identity server.
  247. Used only by c/s api v1
  248. """
  249. # Now we have a matrix ID, bind it to the threepids we were given
  250. for c in threepidCreds:
  251. identity_handler = self.hs.get_handlers().identity_handler
  252. # XXX: This should be a deferred list, shouldn't it?
  253. yield identity_handler.bind_threepid(c, user_id)
  254. @defer.inlineCallbacks
  255. def check_user_id_not_appservice_exclusive(self, user_id, allowed_appservice=None):
  256. # valid user IDs must not clash with any user ID namespaces claimed by
  257. # application services.
  258. services = yield self.store.get_app_services()
  259. interested_services = [
  260. s for s in services
  261. if s.is_interested_in_user(user_id)
  262. and s != allowed_appservice
  263. ]
  264. for service in interested_services:
  265. if service.is_exclusive_user(user_id):
  266. raise SynapseError(
  267. 400, "This user ID is reserved by an application service.",
  268. errcode=Codes.EXCLUSIVE
  269. )
  270. @defer.inlineCallbacks
  271. def _generate_user_id(self, reseed=False):
  272. if reseed or self._next_generated_user_id is None:
  273. self._next_generated_user_id = (
  274. yield self.store.find_next_generated_user_id_localpart()
  275. )
  276. id = self._next_generated_user_id
  277. self._next_generated_user_id += 1
  278. defer.returnValue(str(id))
  279. @defer.inlineCallbacks
  280. def _validate_captcha(self, ip_addr, private_key, challenge, response):
  281. """Validates the captcha provided.
  282. Used only by c/s api v1
  283. Returns:
  284. dict: Containing 'valid'(bool) and 'error_url'(str) if invalid.
  285. """
  286. response = yield self._submit_captcha(ip_addr, private_key, challenge,
  287. response)
  288. # parse Google's response. Lovely format..
  289. lines = response.split('\n')
  290. json = {
  291. "valid": lines[0] == 'true',
  292. "error_url": "http://www.google.com/recaptcha/api/challenge?" +
  293. "error=%s" % lines[1]
  294. }
  295. defer.returnValue(json)
  296. @defer.inlineCallbacks
  297. def _submit_captcha(self, ip_addr, private_key, challenge, response):
  298. """
  299. Used only by c/s api v1
  300. """
  301. data = yield self.captcha_client.post_urlencoded_get_raw(
  302. "http://www.google.com:80/recaptcha/api/verify",
  303. args={
  304. 'privatekey': private_key,
  305. 'remoteip': ip_addr,
  306. 'challenge': challenge,
  307. 'response': response
  308. }
  309. )
  310. defer.returnValue(data)
  311. def auth_handler(self):
  312. return self.hs.get_handlers().auth_handler
  313. @defer.inlineCallbacks
  314. def guest_access_token_for(self, medium, address, inviter_user_id):
  315. access_token = yield self.store.get_3pid_guest_access_token(medium, address)
  316. if access_token:
  317. defer.returnValue(access_token)
  318. _, access_token = yield self.register(
  319. generate_token=True,
  320. make_guest=True
  321. )
  322. access_token = yield self.store.save_or_get_3pid_guest_access_token(
  323. medium, address, access_token, inviter_user_id
  324. )
  325. defer.returnValue(access_token)