register.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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. """This module contains REST servlets to do with registration: /register"""
  16. from twisted.internet import defer
  17. from synapse.api.errors import SynapseError, Codes
  18. from synapse.api.constants import LoginType
  19. from synapse.api.auth import get_access_token_from_request
  20. from .base import ClientV1RestServlet, client_path_patterns
  21. import synapse.util.stringutils as stringutils
  22. from synapse.http.servlet import parse_json_object_from_request
  23. from synapse.types import create_requester
  24. from synapse.util.async import run_on_reactor
  25. from hashlib import sha1
  26. import hmac
  27. import logging
  28. logger = logging.getLogger(__name__)
  29. # We ought to be using hmac.compare_digest() but on older pythons it doesn't
  30. # exist. It's a _really minor_ security flaw to use plain string comparison
  31. # because the timing attack is so obscured by all the other code here it's
  32. # unlikely to make much difference
  33. if hasattr(hmac, "compare_digest"):
  34. compare_digest = hmac.compare_digest
  35. else:
  36. def compare_digest(a, b):
  37. return a == b
  38. class RegisterRestServlet(ClientV1RestServlet):
  39. """Handles registration with the home server.
  40. This servlet is in control of the registration flow; the registration
  41. handler doesn't have a concept of multi-stages or sessions.
  42. """
  43. PATTERNS = client_path_patterns("/register$", releases=(), include_in_unstable=False)
  44. def __init__(self, hs):
  45. """
  46. Args:
  47. hs (synapse.server.HomeServer): server
  48. """
  49. super(RegisterRestServlet, self).__init__(hs)
  50. # sessions are stored as:
  51. # self.sessions = {
  52. # "session_id" : { __session_dict__ }
  53. # }
  54. # TODO: persistent storage
  55. self.sessions = {}
  56. self.enable_registration = hs.config.enable_registration
  57. self.auth_handler = hs.get_auth_handler()
  58. self.handlers = hs.get_handlers()
  59. def on_GET(self, request):
  60. if self.hs.config.enable_registration_captcha:
  61. return (
  62. 200,
  63. {"flows": [
  64. {
  65. "type": LoginType.RECAPTCHA,
  66. "stages": [
  67. LoginType.RECAPTCHA,
  68. LoginType.EMAIL_IDENTITY,
  69. LoginType.PASSWORD
  70. ]
  71. },
  72. {
  73. "type": LoginType.RECAPTCHA,
  74. "stages": [LoginType.RECAPTCHA, LoginType.PASSWORD]
  75. }
  76. ]}
  77. )
  78. else:
  79. return (
  80. 200,
  81. {"flows": [
  82. {
  83. "type": LoginType.EMAIL_IDENTITY,
  84. "stages": [
  85. LoginType.EMAIL_IDENTITY, LoginType.PASSWORD
  86. ]
  87. },
  88. {
  89. "type": LoginType.PASSWORD
  90. }
  91. ]}
  92. )
  93. @defer.inlineCallbacks
  94. def on_POST(self, request):
  95. register_json = parse_json_object_from_request(request)
  96. session = (register_json["session"]
  97. if "session" in register_json else None)
  98. login_type = None
  99. if "type" not in register_json:
  100. raise SynapseError(400, "Missing 'type' key.")
  101. try:
  102. login_type = register_json["type"]
  103. is_application_server = login_type == LoginType.APPLICATION_SERVICE
  104. is_using_shared_secret = login_type == LoginType.SHARED_SECRET
  105. can_register = (
  106. self.enable_registration
  107. or is_application_server
  108. or is_using_shared_secret
  109. )
  110. if not can_register:
  111. raise SynapseError(403, "Registration has been disabled")
  112. stages = {
  113. LoginType.RECAPTCHA: self._do_recaptcha,
  114. LoginType.PASSWORD: self._do_password,
  115. LoginType.EMAIL_IDENTITY: self._do_email_identity,
  116. LoginType.APPLICATION_SERVICE: self._do_app_service,
  117. LoginType.SHARED_SECRET: self._do_shared_secret,
  118. }
  119. session_info = self._get_session_info(request, session)
  120. logger.debug("%s : session info %s request info %s",
  121. login_type, session_info, register_json)
  122. response = yield stages[login_type](
  123. request,
  124. register_json,
  125. session_info
  126. )
  127. if "access_token" not in response:
  128. # isn't a final response
  129. response["session"] = session_info["id"]
  130. defer.returnValue((200, response))
  131. except KeyError as e:
  132. logger.exception(e)
  133. raise SynapseError(400, "Missing JSON keys for login type %s." % (
  134. login_type,
  135. ))
  136. def on_OPTIONS(self, request):
  137. return (200, {})
  138. def _get_session_info(self, request, session_id):
  139. if not session_id:
  140. # create a new session
  141. while session_id is None or session_id in self.sessions:
  142. session_id = stringutils.random_string(24)
  143. self.sessions[session_id] = {
  144. "id": session_id,
  145. LoginType.EMAIL_IDENTITY: False,
  146. LoginType.RECAPTCHA: False
  147. }
  148. return self.sessions[session_id]
  149. def _save_session(self, session):
  150. # TODO: Persistent storage
  151. logger.debug("Saving session %s", session)
  152. self.sessions[session["id"]] = session
  153. def _remove_session(self, session):
  154. logger.debug("Removing session %s", session)
  155. self.sessions.pop(session["id"])
  156. @defer.inlineCallbacks
  157. def _do_recaptcha(self, request, register_json, session):
  158. if not self.hs.config.enable_registration_captcha:
  159. raise SynapseError(400, "Captcha not required.")
  160. yield self._check_recaptcha(request, register_json, session)
  161. session[LoginType.RECAPTCHA] = True # mark captcha as done
  162. self._save_session(session)
  163. defer.returnValue({
  164. "next": [LoginType.PASSWORD, LoginType.EMAIL_IDENTITY]
  165. })
  166. @defer.inlineCallbacks
  167. def _check_recaptcha(self, request, register_json, session):
  168. if ("captcha_bypass_hmac" in register_json and
  169. self.hs.config.captcha_bypass_secret):
  170. if "user" not in register_json:
  171. raise SynapseError(400, "Captcha bypass needs 'user'")
  172. want = hmac.new(
  173. key=self.hs.config.captcha_bypass_secret,
  174. msg=register_json["user"],
  175. digestmod=sha1,
  176. ).hexdigest()
  177. # str() because otherwise hmac complains that 'unicode' does not
  178. # have the buffer interface
  179. got = str(register_json["captcha_bypass_hmac"])
  180. if compare_digest(want, got):
  181. session["user"] = register_json["user"]
  182. defer.returnValue(None)
  183. else:
  184. raise SynapseError(
  185. 400, "Captcha bypass HMAC incorrect",
  186. errcode=Codes.CAPTCHA_NEEDED
  187. )
  188. challenge = None
  189. user_response = None
  190. try:
  191. challenge = register_json["challenge"]
  192. user_response = register_json["response"]
  193. except KeyError:
  194. raise SynapseError(400, "Captcha response is required",
  195. errcode=Codes.CAPTCHA_NEEDED)
  196. ip_addr = self.hs.get_ip_from_request(request)
  197. handler = self.handlers.registration_handler
  198. yield handler.check_recaptcha(
  199. ip_addr,
  200. self.hs.config.recaptcha_private_key,
  201. challenge,
  202. user_response
  203. )
  204. @defer.inlineCallbacks
  205. def _do_email_identity(self, request, register_json, session):
  206. if (self.hs.config.enable_registration_captcha and
  207. not session[LoginType.RECAPTCHA]):
  208. raise SynapseError(400, "Captcha is required.")
  209. threepidCreds = register_json['threepidCreds']
  210. handler = self.handlers.registration_handler
  211. logger.debug("Registering email. threepidcreds: %s" % (threepidCreds))
  212. yield handler.register_email(threepidCreds)
  213. session["threepidCreds"] = threepidCreds # store creds for next stage
  214. session[LoginType.EMAIL_IDENTITY] = True # mark email as done
  215. self._save_session(session)
  216. defer.returnValue({
  217. "next": LoginType.PASSWORD
  218. })
  219. @defer.inlineCallbacks
  220. def _do_password(self, request, register_json, session):
  221. yield run_on_reactor()
  222. if (self.hs.config.enable_registration_captcha and
  223. not session[LoginType.RECAPTCHA]):
  224. # captcha should've been done by this stage!
  225. raise SynapseError(400, "Captcha is required.")
  226. if ("user" in session and "user" in register_json and
  227. session["user"] != register_json["user"]):
  228. raise SynapseError(
  229. 400, "Cannot change user ID during registration"
  230. )
  231. password = register_json["password"].encode("utf-8")
  232. desired_user_id = (
  233. register_json["user"].encode("utf-8")
  234. if "user" in register_json else None
  235. )
  236. handler = self.handlers.registration_handler
  237. (user_id, token) = yield handler.register(
  238. localpart=desired_user_id,
  239. password=password
  240. )
  241. if session[LoginType.EMAIL_IDENTITY]:
  242. logger.debug("Binding emails %s to %s" % (
  243. session["threepidCreds"], user_id)
  244. )
  245. yield handler.bind_emails(user_id, session["threepidCreds"])
  246. result = {
  247. "user_id": user_id,
  248. "access_token": token,
  249. "home_server": self.hs.hostname,
  250. }
  251. self._remove_session(session)
  252. defer.returnValue(result)
  253. @defer.inlineCallbacks
  254. def _do_app_service(self, request, register_json, session):
  255. as_token = get_access_token_from_request(request)
  256. if "user" not in register_json:
  257. raise SynapseError(400, "Expected 'user' key.")
  258. user_localpart = register_json["user"].encode("utf-8")
  259. handler = self.handlers.registration_handler
  260. user_id = yield handler.appservice_register(
  261. user_localpart, as_token
  262. )
  263. token = yield self.auth_handler.issue_access_token(user_id)
  264. self._remove_session(session)
  265. defer.returnValue({
  266. "user_id": user_id,
  267. "access_token": token,
  268. "home_server": self.hs.hostname,
  269. })
  270. @defer.inlineCallbacks
  271. def _do_shared_secret(self, request, register_json, session):
  272. yield run_on_reactor()
  273. if not isinstance(register_json.get("mac", None), basestring):
  274. raise SynapseError(400, "Expected mac.")
  275. if not isinstance(register_json.get("user", None), basestring):
  276. raise SynapseError(400, "Expected 'user' key.")
  277. if not isinstance(register_json.get("password", None), basestring):
  278. raise SynapseError(400, "Expected 'password' key.")
  279. if not self.hs.config.registration_shared_secret:
  280. raise SynapseError(400, "Shared secret registration is not enabled")
  281. user = register_json["user"].encode("utf-8")
  282. password = register_json["password"].encode("utf-8")
  283. admin = register_json.get("admin", None)
  284. # Its important to check as we use null bytes as HMAC field separators
  285. if "\x00" in user:
  286. raise SynapseError(400, "Invalid user")
  287. if "\x00" in password:
  288. raise SynapseError(400, "Invalid password")
  289. # str() because otherwise hmac complains that 'unicode' does not
  290. # have the buffer interface
  291. got_mac = str(register_json["mac"])
  292. want_mac = hmac.new(
  293. key=self.hs.config.registration_shared_secret,
  294. digestmod=sha1,
  295. )
  296. want_mac.update(user)
  297. want_mac.update("\x00")
  298. want_mac.update(password)
  299. want_mac.update("\x00")
  300. want_mac.update("admin" if admin else "notadmin")
  301. want_mac = want_mac.hexdigest()
  302. if compare_digest(want_mac, got_mac):
  303. handler = self.handlers.registration_handler
  304. user_id, token = yield handler.register(
  305. localpart=user,
  306. password=password,
  307. admin=bool(admin),
  308. )
  309. self._remove_session(session)
  310. defer.returnValue({
  311. "user_id": user_id,
  312. "access_token": token,
  313. "home_server": self.hs.hostname,
  314. })
  315. else:
  316. raise SynapseError(
  317. 403, "HMAC incorrect",
  318. )
  319. class CreateUserRestServlet(ClientV1RestServlet):
  320. """Handles user creation via a server-to-server interface
  321. """
  322. PATTERNS = client_path_patterns("/createUser$", releases=())
  323. def __init__(self, hs):
  324. super(CreateUserRestServlet, self).__init__(hs)
  325. self.store = hs.get_datastore()
  326. self.handlers = hs.get_handlers()
  327. @defer.inlineCallbacks
  328. def on_POST(self, request):
  329. user_json = parse_json_object_from_request(request)
  330. access_token = get_access_token_from_request(request)
  331. app_service = self.store.get_app_service_by_token(
  332. access_token
  333. )
  334. if not app_service:
  335. raise SynapseError(403, "Invalid application service token.")
  336. requester = create_requester(app_service.sender)
  337. logger.debug("creating user: %s", user_json)
  338. response = yield self._do_create(requester, user_json)
  339. defer.returnValue((200, response))
  340. def on_OPTIONS(self, request):
  341. return 403, {}
  342. @defer.inlineCallbacks
  343. def _do_create(self, requester, user_json):
  344. yield run_on_reactor()
  345. if "localpart" not in user_json:
  346. raise SynapseError(400, "Expected 'localpart' key.")
  347. if "displayname" not in user_json:
  348. raise SynapseError(400, "Expected 'displayname' key.")
  349. localpart = user_json["localpart"].encode("utf-8")
  350. displayname = user_json["displayname"].encode("utf-8")
  351. password_hash = user_json["password_hash"].encode("utf-8") \
  352. if user_json.get("password_hash") else None
  353. handler = self.handlers.registration_handler
  354. user_id, token = yield handler.get_or_create_user(
  355. requester=requester,
  356. localpart=localpart,
  357. displayname=displayname,
  358. password_hash=password_hash
  359. )
  360. defer.returnValue({
  361. "user_id": user_id,
  362. "access_token": token,
  363. "home_server": self.hs.hostname,
  364. })
  365. def register_servlets(hs, http_server):
  366. RegisterRestServlet(hs).register(http_server)
  367. CreateUserRestServlet(hs).register(http_server)