From d6ecbbdf0ad4fb49d189044ebf73f333fdd323dc Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Sep 2014 18:22:27 +0100 Subject: [PATCH] Add support for registering with a threepid to the HS (get credentials from the client and check them against an ID server). --- synapse/handlers/register.py | 62 ++++++++++++++++++++++++++++++++++-- synapse/http/client.py | 40 ++++++++++++++++++++--- synapse/rest/register.py | 7 +++- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py index 593c603346..d56d2663d7 100644 --- a/synapse/handlers/register.py +++ b/synapse/handlers/register.py @@ -20,9 +20,13 @@ from synapse.types import UserID from synapse.api.errors import SynapseError, RegistrationError from ._base import BaseHandler import synapse.util.stringutils as stringutils +from synapse.http.client import PlainHttpClient import base64 import bcrypt +import logging + +logger = logging.getLogger(__name__) class RegistrationHandler(BaseHandler): @@ -34,7 +38,7 @@ class RegistrationHandler(BaseHandler): self.distributor.declare("registered_user") @defer.inlineCallbacks - def register(self, localpart=None, password=None): + def register(self, localpart=None, password=None, threepidCreds=None): """Registers a new client on the server. Args: @@ -47,6 +51,20 @@ class RegistrationHandler(BaseHandler): Raises: RegistrationError if there was a problem registering. """ + + if threepidCreds: + for c in threepidCreds: + logger.info("validating theeepidcred sid %s on id server %s", c['sid'], c['idServer']) + try: + threepid = yield self._threepid_from_creds(c) + except: + logger.err() + raise RegistrationError(400, "Couldn't validate 3pid") + + if not threepid: + raise RegistrationError(400, "Couldn't validate 3pid") + logger.info("got threepid medium %s address %s", threepid['medium'], threepid['address']) + password_hash = None if password: password_hash = bcrypt.hashpw(password, bcrypt.gensalt()) @@ -61,7 +79,6 @@ class RegistrationHandler(BaseHandler): password_hash=password_hash) self.distributor.fire("registered_user", user) - defer.returnValue((user_id, token)) else: # autogen a random user ID attempts = 0 @@ -80,7 +97,6 @@ class RegistrationHandler(BaseHandler): password_hash=password_hash) self.distributor.fire("registered_user", user) - defer.returnValue((user_id, token)) except SynapseError: # if user id is taken, just generate another user_id = None @@ -90,6 +106,15 @@ class RegistrationHandler(BaseHandler): raise RegistrationError( 500, "Cannot generate user ID.") + # Now we have a matrix ID, bind it to the threepids we were given + if threepidCreds: + for c in threepidCreds: + # XXX: This should be a deferred list, shouldn't it? + yield self._bind_threepid(c, user_id) + + + defer.returnValue((user_id, token)) + def _generate_token(self, user_id): # urlsafe variant uses _ and - so use . as the separator and replace # all =s with .s so http clients don't quote =s when it is used as @@ -99,3 +124,34 @@ class RegistrationHandler(BaseHandler): def _generate_user_id(self): return "-" + stringutils.random_string(18) + + @defer.inlineCallbacks + def _threepid_from_creds(self, creds): + httpCli = PlainHttpClient(self.hs) + # XXX: make this configurable! + trustedIdServers = [ 'matrix.org:8090' ] + if not creds['idServer'] in trustedIdServers: + logger.warn('%s is not a trusted ID server: rejecting 3pid credentials', creds['idServer']) + defer.returnValue(None) + data = yield httpCli.get_json( + creds['idServer'], + "/_matrix/identity/api/v1/3pid/getValidated3pid", + { 'sid': creds['sid'], 'clientSecret': creds['clientSecret'] } + ) + + if 'medium' in data: + defer.returnValue(data) + defer.returnValue(None) + + @defer.inlineCallbacks + def _bind_threepid(self, creds, mxid): + httpCli = PlainHttpClient(self.hs) + data = yield httpCli.post_urlencoded_get_json( + creds['idServer'], + "/_matrix/identity/api/v1/3pid/bind", + { 'sid': creds['sid'], 'clientSecret': creds['clientSecret'], 'mxid':mxid } + ) + defer.returnValue(data) + + + diff --git a/synapse/http/client.py b/synapse/http/client.py index 093bdf0e3f..5a5e40a26e 100644 --- a/synapse/http/client.py +++ b/synapse/http/client.py @@ -15,7 +15,7 @@ from twisted.internet import defer, reactor -from twisted.web.client import _AgentBase, _URI, readBody +from twisted.web.client import _AgentBase, _URI, readBody, FileBodyProducer from twisted.web.http_headers import Headers from synapse.http.endpoint import matrix_endpoint @@ -25,6 +25,8 @@ from syutil.jsonutil import encode_canonical_json from synapse.api.errors import CodeMessageException +from StringIO import StringIO + import json import logging import urllib @@ -155,6 +157,26 @@ class TwistedHttpClient(HttpClient): defer.returnValue(json.loads(body)) + @defer.inlineCallbacks + def post_urlencoded_get_json(self, destination, path, args={}): + if destination in _destination_mappings: + destination = _destination_mappings[destination] + + logger.debug("post_urlencoded_get_json args: %s", args) + query_bytes = urllib.urlencode(args, True) + + response = yield self._create_request( + destination.encode("ascii"), + "POST", + path.encode("ascii"), + producer=FileBodyProducer(StringIO(urllib.urlencode(args))), + headers_dict={"Content-Type": ["application/x-www-form-urlencoded"]} + ) + + body = yield readBody(response) + + defer.returnValue(json.loads(body)) + @defer.inlineCallbacks def _create_request(self, destination, method, path_bytes, param_bytes=b"", query_bytes=b"", producer=None, headers_dict={}): @@ -178,10 +200,7 @@ class TwistedHttpClient(HttpClient): retries_left = 5 # TODO: setup and pass in an ssl_context to enable TLS - endpoint = matrix_endpoint( - reactor, destination, timeout=10, - ssl_context_factory=self.hs.tls_context_factory - ) + endpoint = self._getEndpoint(reactor, destination); while True: try: @@ -223,6 +242,17 @@ class TwistedHttpClient(HttpClient): defer.returnValue(response) + def _getEndpoint(self, reactor, destination): + return matrix_endpoint( + reactor, destination, timeout=10, + ssl_context_factory=self.hs.tls_context_factory + ) + + +class PlainHttpClient(TwistedHttpClient): + def _getEndpoint(self, reactor, destination): + return matrix_endpoint(reactor, destination, timeout=10) + def _print_ex(e): if hasattr(e, "reasons") and e.reasons: diff --git a/synapse/rest/register.py b/synapse/rest/register.py index f17ec11cf4..27ab7f182b 100644 --- a/synapse/rest/register.py +++ b/synapse/rest/register.py @@ -47,10 +47,15 @@ class RegisterRestServlet(RestServlet): except KeyError: pass # user_id is optional + threepidCreds = None + if 'threepidCreds' in register_json: + threepidCreds = register_json['threepidCreds'] + handler = self.handlers.registration_handler (user_id, token) = yield handler.register( localpart=desired_user_id, - password=password) + password=password, + threepidCreds=threepidCreds) result = { "user_id": user_id,