From ce3e583d94c9fb3ee98365e07f4695d1b9451434 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 14 Feb 2017 15:05:55 +0000 Subject: [PATCH 01/18] WIP support for msisdn 3pid proxy methods --- synapse/api/constants.py | 2 + synapse/handlers/auth.py | 30 +++++-- synapse/handlers/identity.py | 37 +++++++- synapse/python_dependencies.py | 2 + synapse/rest/client/v2_alpha/account.py | 110 +++++++++++++++++++++-- synapse/rest/client/v2_alpha/register.py | 66 ++++++++++++-- 6 files changed, 228 insertions(+), 19 deletions(-) diff --git a/synapse/api/constants.py b/synapse/api/constants.py index ca23c9c46..489efb7f8 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2014-2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -44,6 +45,7 @@ class JoinRules(object): class LoginType(object): PASSWORD = u"m.login.password" EMAIL_IDENTITY = u"m.login.email.identity" + MSISDN = u"m.login.msisdn" RECAPTCHA = u"m.login.recaptcha" DUMMY = u"m.login.dummy" diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index fffba3438..448bc0b31 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2014 - 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -47,6 +48,7 @@ class AuthHandler(BaseHandler): LoginType.PASSWORD: self._check_password_auth, LoginType.RECAPTCHA: self._check_recaptcha, LoginType.EMAIL_IDENTITY: self._check_email_identity, + LoginType.MSISDN: self._check_msisdn, LoginType.DUMMY: self._check_dummy_auth, } self.bcrypt_rounds = hs.config.bcrypt_rounds @@ -309,12 +311,26 @@ class AuthHandler(BaseHandler): @defer.inlineCallbacks def _check_email_identity(self, authdict, _): + defer.returnValue(self._check_threepid('email', authdict)) + + @defer.inlineCallbacks + def _check_msisdn(self, authdict, _): + defer.returnValue(self._check_threepid('msisdn', authdict)) + + @defer.inlineCallbacks + def _check_dummy_auth(self, authdict, _): + yield run_on_reactor() + defer.returnValue(True) + + @defer.inlineCallbacks + def _check_threepid(self, medium, authdict, ): yield run_on_reactor() if 'threepid_creds' not in authdict: raise LoginError(400, "Missing threepid_creds", Codes.MISSING_PARAM) threepid_creds = authdict['threepid_creds'] + identity_handler = self.hs.get_handlers().identity_handler logger.info("Getting validated threepid. threepidcreds: %r" % (threepid_creds,)) @@ -323,15 +339,19 @@ class AuthHandler(BaseHandler): if not threepid: raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) + if threepid['medium'] != medium: + raise LoginError( + 401, + "Expecting threepid of type '%s', got '%s'" % ( + medium, threepid['medium'], + ), + errcode=Codes.UNAUTHORIZED + ) + threepid['threepid_creds'] = authdict['threepid_creds'] defer.returnValue(threepid) - @defer.inlineCallbacks - def _check_dummy_auth(self, authdict, _): - yield run_on_reactor() - defer.returnValue(True) - def _get_params_recaptcha(self): return {"public_key": self.hs.config.recaptcha_public_key} diff --git a/synapse/handlers/identity.py b/synapse/handlers/identity.py index 559e5d5a7..6a53c5eb4 100644 --- a/synapse/handlers/identity.py +++ b/synapse/handlers/identity.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2015, 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -150,7 +151,7 @@ class IdentityHandler(BaseHandler): params.update(kwargs) try: - data = yield self.http_client.post_urlencoded_get_json( + data = yield self.http_client.post_json_get_json( "https://%s%s" % ( id_server, "/_matrix/identity/api/v1/validate/email/requestToken" @@ -161,3 +162,37 @@ class IdentityHandler(BaseHandler): except CodeMessageException as e: logger.info("Proxied requestToken failed: %r", e) raise e + + @defer.inlineCallbacks + def requestMsisdnToken( + self, id_server, country, phone_number, + client_secret, send_attempt, **kwargs + ): + yield run_on_reactor() + + if not self._should_trust_id_server(id_server): + raise SynapseError( + 400, "Untrusted ID server '%s'" % id_server, + Codes.SERVER_NOT_TRUSTED + ) + + params = { + 'country': country, + 'phone_number': phone_number, + 'client_secret': client_secret, + 'send_attempt': send_attempt, + } + params.update(kwargs) + + try: + data = yield self.http_client.post_json_get_json( + "https://%s%s" % ( + id_server, + "/_matrix/identity/api/v1/validate/msisdn/requestToken" + ), + params + ) + defer.returnValue(data) + except CodeMessageException as e: + logger.info("Proxied requestToken failed: %r", e) + raise e diff --git a/synapse/python_dependencies.py b/synapse/python_dependencies.py index 7817b0cd9..c4777b2a2 100644 --- a/synapse/python_dependencies.py +++ b/synapse/python_dependencies.py @@ -1,4 +1,5 @@ # Copyright 2015, 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -37,6 +38,7 @@ REQUIREMENTS = { "pysaml2>=3.0.0,<4.0.0": ["saml2>=3.0.0,<4.0.0"], "pymacaroons-pynacl": ["pymacaroons"], "msgpack-python>=0.3.0": ["msgpack"], + "phonenumbers>=8.2.0": ["phonenumbers"], } CONDITIONAL_REQUIREMENTS = { "web_client": { diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index 398e7f5eb..cf80f5ca2 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2015, 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,15 +25,17 @@ from ._base import client_v2_patterns import logging +import phonenumbers + logger = logging.getLogger(__name__) -class PasswordRequestTokenRestServlet(RestServlet): +class EmailPasswordRequestTokenRestServlet(RestServlet): PATTERNS = client_v2_patterns("/account/password/email/requestToken$") def __init__(self, hs): - super(PasswordRequestTokenRestServlet, self).__init__() + super(EmailPasswordRequestTokenRestServlet, self).__init__() self.hs = hs self.identity_handler = hs.get_handlers().identity_handler @@ -60,6 +63,50 @@ class PasswordRequestTokenRestServlet(RestServlet): defer.returnValue((200, ret)) +class MsisdnPasswordRequestTokenRestServlet(RestServlet): + PATTERNS = client_v2_patterns("/account/password/msisdn/requestToken$") + + def __init__(self, hs): + super(MsisdnPasswordRequestTokenRestServlet, self).__init__() + self.hs = hs + self.identity_handler = hs.get_handlers().identity_handler + + @defer.inlineCallbacks + def on_POST(self, request): + body = parse_json_object_from_request(request) + + required = [ + 'id_server', 'client_secret', + 'country', 'phone_number', 'send_attempt', + ] + absent = [] + for k in required: + if k not in body: + absent.append(k) + + if absent: + raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + + phoneNumber = None + try: + phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) + except phonenumbers.NumberParseException: + raise SynapseError(400, "Unable to parse phone number") + msisdn = phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + + existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + 'msisdn', msisdn + ) + + if existingUid is None: + raise SynapseError(400, "MSISDN not found", Codes.THREEPID_NOT_FOUND) + + ret = yield self.identity_handler.requestEmailToken(**body) + defer.returnValue((200, ret)) + + class PasswordRestServlet(RestServlet): PATTERNS = client_v2_patterns("/account/password$") @@ -77,7 +124,8 @@ class PasswordRestServlet(RestServlet): authed, result, params, _ = yield self.auth_handler.check_auth([ [LoginType.PASSWORD], - [LoginType.EMAIL_IDENTITY] + [LoginType.EMAIL_IDENTITY], + [LoginType.MSISDN], ], body, self.hs.get_ip_from_request(request)) if not authed: @@ -169,12 +217,12 @@ class DeactivateAccountRestServlet(RestServlet): defer.returnValue((200, {})) -class ThreepidRequestTokenRestServlet(RestServlet): +class EmailThreepidRequestTokenRestServlet(RestServlet): PATTERNS = client_v2_patterns("/account/3pid/email/requestToken$") def __init__(self, hs): self.hs = hs - super(ThreepidRequestTokenRestServlet, self).__init__() + super(EmailThreepidRequestTokenRestServlet, self).__init__() self.identity_handler = hs.get_handlers().identity_handler @defer.inlineCallbacks @@ -201,6 +249,50 @@ class ThreepidRequestTokenRestServlet(RestServlet): defer.returnValue((200, ret)) +class MsisdnThreepidRequestTokenRestServlet(RestServlet): + PATTERNS = client_v2_patterns("/account/3pid/msisdn/requestToken$") + + def __init__(self, hs): + self.hs = hs + super(MsisdnThreepidRequestTokenRestServlet, self).__init__() + self.identity_handler = hs.get_handlers().identity_handler + + @defer.inlineCallbacks + def on_POST(self, request): + body = parse_json_object_from_request(request) + + required = [ + 'id_server', 'client_secret', + 'country', 'phone_number', 'send_attempt', + ] + absent = [] + for k in required: + if k not in body: + absent.append(k) + + if absent: + raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + + phoneNumber = None + try: + phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) + except phonenumbers.NumberParseException: + raise SynapseError(400, "Unable to parse phone number") + msisdn = phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + + existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + 'msisdn', msisdn + ) + + if existingUid is not None: + raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE) + + ret = yield self.identity_handler.requestEmailToken(**body) + defer.returnValue((200, ret)) + + class ThreepidRestServlet(RestServlet): PATTERNS = client_v2_patterns("/account/3pid$") @@ -258,7 +350,7 @@ class ThreepidRestServlet(RestServlet): if 'bind' in body and body['bind']: logger.debug( - "Binding emails %s to %s", + "Binding threepid %s to %s", threepid, user_id ) yield self.identity_handler.bind_threepid( @@ -302,9 +394,11 @@ class ThreepidDeleteRestServlet(RestServlet): def register_servlets(hs, http_server): - PasswordRequestTokenRestServlet(hs).register(http_server) + EmailPasswordRequestTokenRestServlet(hs).register(http_server) + MsisdnPasswordRequestTokenRestServlet(hs).register(http_server) PasswordRestServlet(hs).register(http_server) DeactivateAccountRestServlet(hs).register(http_server) - ThreepidRequestTokenRestServlet(hs).register(http_server) + EmailThreepidRequestTokenRestServlet(hs).register(http_server) + MsisdnThreepidRequestTokenRestServlet(hs).register(http_server) ThreepidRestServlet(hs).register(http_server) ThreepidDeleteRestServlet(hs).register(http_server) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index ccca5a12d..39f61d70b 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Copyright 2015 - 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,6 +26,7 @@ from ._base import client_v2_patterns import logging import hmac +import phonenumbers from hashlib import sha1 from synapse.util.async import run_on_reactor @@ -43,7 +45,7 @@ else: logger = logging.getLogger(__name__) -class RegisterRequestTokenRestServlet(RestServlet): +class EmailRegisterRequestTokenRestServlet(RestServlet): PATTERNS = client_v2_patterns("/register/email/requestToken$") def __init__(self, hs): @@ -51,7 +53,7 @@ class RegisterRequestTokenRestServlet(RestServlet): Args: hs (synapse.server.HomeServer): server """ - super(RegisterRequestTokenRestServlet, self).__init__() + super(EmailRegisterRequestTokenRestServlet, self).__init__() self.hs = hs self.identity_handler = hs.get_handlers().identity_handler @@ -79,6 +81,55 @@ class RegisterRequestTokenRestServlet(RestServlet): defer.returnValue((200, ret)) +class MsisdnRegisterRequestTokenRestServlet(RestServlet): + PATTERNS = client_v2_patterns("/register/msisdn/requestToken$") + + def __init__(self, hs): + """ + Args: + hs (synapse.server.HomeServer): server + """ + super(MsisdnRegisterRequestTokenRestServlet, self).__init__() + self.hs = hs + self.identity_handler = hs.get_handlers().identity_handler + + @defer.inlineCallbacks + def on_POST(self, request): + body = parse_json_object_from_request(request) + + required = [ + 'id_server', 'client_secret', + 'country', 'phone_number', + 'send_attempt', + ] + absent = [] + for k in required: + if k not in body: + absent.append(k) + + if len(absent) > 0: + raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + + phoneNumber = None + try: + phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) + except phonenumbers.NumberParseException: + raise SynapseError(400, "Unable to parse phone number") + msisdn = phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + + existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + 'msisdn', msisdn + ) + + if existingUid is not None: + raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE) + + ret = yield self.identity_handler.requestMsisdnToken(**body) + defer.returnValue((200, ret)) + + class RegisterRestServlet(RestServlet): PATTERNS = client_v2_patterns("/register$") @@ -203,12 +254,16 @@ class RegisterRestServlet(RestServlet): if self.hs.config.enable_registration_captcha: flows = [ [LoginType.RECAPTCHA], - [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA] + [LoginType.EMAIL_IDENTITY, LoginType.RECAPTCHA], + [LoginType.MSISDN, LoginType.RECAPTCHA], + [LoginType.EMAIL_IDENTITY, LoginType.MSISDN, LoginType.RECAPTCHA], ] else: flows = [ [LoginType.DUMMY], - [LoginType.EMAIL_IDENTITY] + [LoginType.EMAIL_IDENTITY], + [LoginType.MSISDN], + [LoginType.EMAIL_IDENTITY, LoginType.MSISDN], ] authed, auth_result, params, session_id = yield self.auth_handler.check_auth( @@ -449,5 +504,6 @@ class RegisterRestServlet(RestServlet): def register_servlets(hs, http_server): - RegisterRequestTokenRestServlet(hs).register(http_server) + EmailRegisterRequestTokenRestServlet(hs).register(http_server) + MsisdnRegisterRequestTokenRestServlet(hs).register(http_server) RegisterRestServlet(hs).register(http_server) From ad882cd54d8349750f363e6127eca14a2e52b2b6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 1 Mar 2017 18:08:51 +0000 Subject: [PATCH 02/18] Just return the deferred straight off defer.returnValue doth not maketh a generator: it would need a yield to be a generator, and this doesn't need a yield. --- synapse/handlers/auth.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 448bc0b31..2ea1d17ca 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -309,13 +309,11 @@ class AuthHandler(BaseHandler): defer.returnValue(True) raise LoginError(401, "", errcode=Codes.UNAUTHORIZED) - @defer.inlineCallbacks def _check_email_identity(self, authdict, _): - defer.returnValue(self._check_threepid('email', authdict)) + return self._check_threepid('email', authdict) - @defer.inlineCallbacks def _check_msisdn(self, authdict, _): - defer.returnValue(self._check_threepid('msisdn', authdict)) + return self._check_threepid('msisdn', authdict) @defer.inlineCallbacks def _check_dummy_auth(self, authdict, _): From b0effa2160b28081e6900bd9dbff265e6e990784 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 Mar 2017 18:34:39 +0000 Subject: [PATCH 03/18] Add msisdns as 3pids during registration and support binding them with the bind_msisdn param --- synapse/rest/client/v2_alpha/register.py | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 39f61d70b..768e75308 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -281,6 +281,7 @@ class RegisterRestServlet(RestServlet): ) # don't re-register the email address add_email = False + add_msisdn = False else: # NB: This may be from the auth handler and NOT from the POST if 'password' not in params: @@ -305,6 +306,7 @@ class RegisterRestServlet(RestServlet): ) add_email = True + add_msisdn = True return_dict = yield self._create_registration_details( registered_user_id, params @@ -317,6 +319,13 @@ class RegisterRestServlet(RestServlet): params.get("bind_email") ) + if add_msisdn and auth_result and LoginType.MSISDN in auth_result: + threepid = auth_result[LoginType.MSISDN] + yield self._register_msisdn_threepid( + registered_user_id, threepid, return_dict["access_token"], + params.get("bind_msisdn") + ) + defer.returnValue((200, return_dict)) def on_OPTIONS(self, _): @@ -426,6 +435,44 @@ class RegisterRestServlet(RestServlet): else: logger.info("bind_email not specified: not binding email") + @defer.inlineCallbacks + def _register_msisdn_threepid(self, user_id, threepid, token, bind_msisdn): + """Add aphone number as a 3pid identifier + + Also optionally binds msisdn to the given user_id on the identity server + + Args: + user_id (str): id of user + threepid (object): m.login.msisdn auth response + token (str): access_token for the user + bind_email (bool): true if the client requested the email to be + bound at the identity server + Returns: + defer.Deferred: + """ + reqd = ('medium', 'address', 'validated_at') + if any(x not in threepid for x in reqd): + logger.info("Can't add incomplete 3pid") + defer.returnValue() + + yield self.auth_handler.add_threepid( + user_id, + threepid['medium'], + threepid['address'], + threepid['validated_at'], + ) + + if bind_msisdn: + logger.info("bind_msisdn specified: binding") + logger.debug("Binding msisdn %s to %s" % ( + threepid, user_id + )) + yield self.identity_handler.bind_threepid( + threepid['threepid_creds'], user_id + ) + else: + logger.info("bind_msisdn not specified: not binding msisdn") + @defer.inlineCallbacks def _create_registration_details(self, user_id, params): """Complete registration of newly-registered user From 00466e2feb12802262835458aa2bf78897c5ae63 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Mar 2017 16:37:23 +0000 Subject: [PATCH 04/18] Support new login format https://docs.google.com/document/d/1-6ZSSW5YvCGhVFDyD2QExAUAdpCWjccvJT5xiyTTG2Y/edit# --- synapse/rest/client/v1/login.py | 97 +++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index 72057f1b0..8de1a0225 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -25,6 +25,7 @@ from .base import ClientV1RestServlet, client_path_patterns import simplejson as json import urllib import urlparse +import phonenumbers import logging from saml2 import BINDING_HTTP_POST @@ -37,6 +38,58 @@ import xml.etree.ElementTree as ET logger = logging.getLogger(__name__) +def login_submission_legacy_convert(submission): + """ + If the input login submission is an old style object + (ie. with top-level user / medium / address) convert it + to a typed object. + Returns: Typed-object style login identifier + """ + if "user" in submission: + submission["identifier"] = { + "type": "m.id.user", + "user": submission["user"], + } + del submission["user"] + + if "medium" in submission and "address" in submission: + submission["identifier"] = { + "type": "m.id.thirdparty", + "medium": submission["medium"], + "address": submission["address"], + } + del submission["medium"] + del submission["address"] + + return submission + + +def login_id_thirdparty_from_phone(identifier): + """ + Convert a phone login identifier type to a generic threepid identifier + Args: + identifier: Login identifier dict of type 'm.id.phone' + + Returns: Login identifier dict of type 'm.id.threepid' + """ + if "country" not in identifier or "number" not in identifier: + raise SynapseError(400, "Invalid phone-type identifier") + phoneNumber = None + try: + phoneNumber = phonenumbers.parse(identifier["number"], identifier["country"]) + except phonenumbers.NumberParseException: + raise SynapseError(400, "Unable to parse phone number") + msisdn = phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + + return { + "type": "m.id.thirdparty", + "medium": "msisdn", + "address": msisdn, + } + + class LoginRestServlet(ClientV1RestServlet): PATTERNS = client_path_patterns("/login$") PASS_TYPE = "m.login.password" @@ -117,20 +170,52 @@ class LoginRestServlet(ClientV1RestServlet): @defer.inlineCallbacks def do_password_login(self, login_submission): - if 'medium' in login_submission and 'address' in login_submission: - address = login_submission['address'] - if login_submission['medium'] == 'email': + if "password" not in login_submission: + raise SynapseError(400, "Missing parameter: password") + + login_submission = login_submission_legacy_convert(login_submission) + + if "identifier" not in login_submission: + raise SynapseError(400, "Missing param: identifier") + + identifier = login_submission["identifier"] + if "type" not in identifier: + raise SynapseError(400, "Login identifier has no type") + + # convert phone type identifiers to geberic threepids + if identifier["type"] == "m.id.phone": + identifier = login_id_thirdparty_from_phone(identifier) + + # convert threepid identifiers to user IDs + if identifier["type"] == "m.id.thirdparty": + if not 'medium' in identifier or not 'address' in identifier: + raise SynapseError(400, "Invalid thirdparty identifier") + + address = identifier['address'] + if identifier['medium'] == 'email': # For emails, transform the address to lowercase. # We store all email addreses as lowercase in the DB. # (See add_threepid in synapse/handlers/auth.py) address = address.lower() user_id = yield self.hs.get_datastore().get_user_id_by_threepid( - login_submission['medium'], address + identifier['medium'], address ) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) - else: - user_id = login_submission['user'] + + identifier = { + "type": "m.id.user", + "user": user_id, + } + + # by this point, the identifier should be an m.id.user: if it's anything + # else, we haven't understood it. + if identifier["type"] != "m.id.user": + raise SynapseError(400, "Unknown login identifier type") + if "user" not in identifier: + raise SynapseError(400, "User identifier is missing 'user' key") + + user_id = identifier["user"] if not user_id.startswith('@'): user_id = UserID.create( From 402a7bf63d0b8fc715ae6659c3b451e1bd44b0f2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 09:33:40 +0000 Subject: [PATCH 05/18] Fix pep8 --- synapse/handlers/auth.py | 2 +- synapse/rest/client/v1/login.py | 6 +++--- synapse/rest/client/v2_alpha/account.py | 4 ++-- synapse/rest/client/v2_alpha/register.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 2ea1d17ca..620591b16 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -344,7 +344,7 @@ class AuthHandler(BaseHandler): medium, threepid['medium'], ), errcode=Codes.UNAUTHORIZED - ) + ) threepid['threepid_creds'] = authdict['threepid_creds'] diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index 8de1a0225..337ba5c02 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -80,7 +80,7 @@ def login_id_thirdparty_from_phone(identifier): except phonenumbers.NumberParseException: raise SynapseError(400, "Unable to parse phone number") msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 + phoneNumber, phonenumbers.PhoneNumberFormat.E164 )[1:] return { @@ -188,7 +188,7 @@ class LoginRestServlet(ClientV1RestServlet): # convert threepid identifiers to user IDs if identifier["type"] == "m.id.thirdparty": - if not 'medium' in identifier or not 'address' in identifier: + if 'medium' not in identifier or 'address' not in identifier: raise SynapseError(400, "Invalid thirdparty identifier") address = identifier['address'] @@ -198,7 +198,7 @@ class LoginRestServlet(ClientV1RestServlet): # (See add_threepid in synapse/handlers/auth.py) address = address.lower() user_id = yield self.hs.get_datastore().get_user_id_by_threepid( - identifier['medium'], address + identifier['medium'], address ) if not user_id: raise LoginError(403, "", errcode=Codes.FORBIDDEN) diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index cf80f5ca2..06bb3617e 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -93,7 +93,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): except phonenumbers.NumberParseException: raise SynapseError(400, "Unable to parse phone number") msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 + phoneNumber, phonenumbers.PhoneNumberFormat.E164 )[1:] existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( @@ -279,7 +279,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet): except phonenumbers.NumberParseException: raise SynapseError(400, "Unable to parse phone number") msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 + phoneNumber, phonenumbers.PhoneNumberFormat.E164 )[1:] existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 768e75308..ad3c70814 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -116,7 +116,7 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet): except phonenumbers.NumberParseException: raise SynapseError(400, "Unable to parse phone number") msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 + phoneNumber, phonenumbers.PhoneNumberFormat.E164 )[1:] existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( From 88df6c0c9a7ce12e7875680131cac421da87ad60 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:03:39 +0000 Subject: [PATCH 06/18] Factor out msisdn canonicalisation Plus a couple of other minor fixes --- synapse/handlers/auth.py | 2 +- synapse/rest/client/v1/login.py | 18 +++++------------- synapse/rest/client/v2_alpha/account.py | 21 +++------------------ synapse/rest/client/v2_alpha/register.py | 11 ++--------- 4 files changed, 11 insertions(+), 41 deletions(-) diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 620591b16..b273a4bee 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -321,7 +321,7 @@ class AuthHandler(BaseHandler): defer.returnValue(True) @defer.inlineCallbacks - def _check_threepid(self, medium, authdict, ): + def _check_threepid(self, medium, authdict): yield run_on_reactor() if 'threepid_creds' not in authdict: diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index 337ba5c02..77a0f00c5 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -19,13 +19,13 @@ from synapse.api.errors import SynapseError, LoginError, Codes from synapse.types import UserID from synapse.http.server import finish_request from synapse.http.servlet import parse_json_object_from_request +from synapse.util.msisdn import phone_number_to_msisdn from .base import ClientV1RestServlet, client_path_patterns import simplejson as json import urllib import urlparse -import phonenumbers import logging from saml2 import BINDING_HTTP_POST @@ -61,8 +61,6 @@ def login_submission_legacy_convert(submission): del submission["medium"] del submission["address"] - return submission - def login_id_thirdparty_from_phone(identifier): """ @@ -74,14 +72,8 @@ def login_id_thirdparty_from_phone(identifier): """ if "country" not in identifier or "number" not in identifier: raise SynapseError(400, "Invalid phone-type identifier") - phoneNumber = None - try: - phoneNumber = phonenumbers.parse(identifier["number"], identifier["country"]) - except phonenumbers.NumberParseException: - raise SynapseError(400, "Unable to parse phone number") - msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 - )[1:] + + msisdn = phone_number_to_msisdn(identifier["country"], identifier["number"]) return { "type": "m.id.thirdparty", @@ -173,7 +165,7 @@ class LoginRestServlet(ClientV1RestServlet): if "password" not in login_submission: raise SynapseError(400, "Missing parameter: password") - login_submission = login_submission_legacy_convert(login_submission) + login_submission_legacy_convert(login_submission) if "identifier" not in login_submission: raise SynapseError(400, "Missing param: identifier") @@ -182,7 +174,7 @@ class LoginRestServlet(ClientV1RestServlet): if "type" not in identifier: raise SynapseError(400, "Login identifier has no type") - # convert phone type identifiers to geberic threepids + # convert phone type identifiers to generic threepids if identifier["type"] == "m.id.phone": identifier = login_id_thirdparty_from_phone(identifier) diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index 06bb3617e..199af60fc 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -20,13 +20,12 @@ from synapse.api.constants import LoginType from synapse.api.errors import LoginError, SynapseError, Codes from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.util.async import run_on_reactor +from synapse.util.msisdn import phone_number_to_msisdn from ._base import client_v2_patterns import logging -import phonenumbers - logger = logging.getLogger(__name__) @@ -87,14 +86,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): if absent: raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) - phoneNumber = None - try: - phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) - except phonenumbers.NumberParseException: - raise SynapseError(400, "Unable to parse phone number") - msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 - )[1:] + msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( 'msisdn', msisdn @@ -273,14 +265,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet): if absent: raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) - phoneNumber = None - try: - phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) - except phonenumbers.NumberParseException: - raise SynapseError(400, "Unable to parse phone number") - msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 - )[1:] + msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( 'msisdn', msisdn diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index ad3c70814..95f9944c4 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -21,12 +21,12 @@ from synapse.api.auth import get_access_token_from_request, has_access_token from synapse.api.constants import LoginType from synapse.api.errors import SynapseError, Codes, UnrecognizedRequestError from synapse.http.servlet import RestServlet, parse_json_object_from_request +from synapse.util.msisdn import phone_number_to_msisdn from ._base import client_v2_patterns import logging import hmac -import phonenumbers from hashlib import sha1 from synapse.util.async import run_on_reactor @@ -110,14 +110,7 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet): if len(absent) > 0: raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) - phoneNumber = None - try: - phoneNumber = phonenumbers.parse(body['phone_number'], body['country']) - except phonenumbers.NumberParseException: - raise SynapseError(400, "Unable to parse phone number") - msisdn = phonenumbers.format_number( - phoneNumber, phonenumbers.PhoneNumberFormat.E164 - )[1:] + msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( 'msisdn', msisdn From 2e27339add58f71485af873cc70284d0bcc2a6bb Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:37:34 +0000 Subject: [PATCH 07/18] Refector out assert_params_in_request and replace requestEmailToken where we meant requestMsisdnToken --- synapse/http/servlet.py | 10 +++++++++ synapse/rest/client/v2_alpha/account.py | 28 ++++++++---------------- synapse/rest/client/v2_alpha/register.py | 26 +++++++--------------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/synapse/http/servlet.py b/synapse/http/servlet.py index 8c22d6f00..9a4c36ad5 100644 --- a/synapse/http/servlet.py +++ b/synapse/http/servlet.py @@ -192,6 +192,16 @@ def parse_json_object_from_request(request): return content +def assert_params_in_request(body, required): + absent = [] + for k in required: + if k not in body: + absent.append(k) + + if len(absent) > 0: + raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + + class RestServlet(object): """ A Synapse REST Servlet. diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index 199af60fc..9a5e1d871 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -18,7 +18,9 @@ from twisted.internet import defer from synapse.api.constants import LoginType from synapse.api.errors import LoginError, SynapseError, Codes -from synapse.http.servlet import RestServlet, parse_json_object_from_request +from synapse.http.servlet import ( + RestServlet, parse_json_object_from_request, assert_params_in_request +) from synapse.util.async import run_on_reactor from synapse.util.msisdn import phone_number_to_msisdn @@ -42,14 +44,9 @@ class EmailPasswordRequestTokenRestServlet(RestServlet): def on_POST(self, request): body = parse_json_object_from_request(request) - required = ['id_server', 'client_secret', 'email', 'send_attempt'] - absent = [] - for k in required: - if k not in body: - absent.append(k) - - if absent: - raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + assert_params_in_request(body, [ + 'id_server', 'client_secret', 'email', 'send_attempt' + ]) existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( 'email', body['email'] @@ -74,17 +71,10 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): def on_POST(self, request): body = parse_json_object_from_request(request) - required = [ + assert_params_in_request(body,[ 'id_server', 'client_secret', 'country', 'phone_number', 'send_attempt', - ] - absent = [] - for k in required: - if k not in body: - absent.append(k) - - if absent: - raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + ]) msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) @@ -95,7 +85,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): if existingUid is None: raise SynapseError(400, "MSISDN not found", Codes.THREEPID_NOT_FOUND) - ret = yield self.identity_handler.requestEmailToken(**body) + ret = yield self.identity_handler.requestMsisdnToken(**body) defer.returnValue((200, ret)) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 95f9944c4..e97b9a32e 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -20,7 +20,9 @@ import synapse from synapse.api.auth import get_access_token_from_request, has_access_token from synapse.api.constants import LoginType from synapse.api.errors import SynapseError, Codes, UnrecognizedRequestError -from synapse.http.servlet import RestServlet, parse_json_object_from_request +from synapse.http.servlet import ( + RestServlet, parse_json_object_from_request, assert_params_in_request +) from synapse.util.msisdn import phone_number_to_msisdn from ._base import client_v2_patterns @@ -61,14 +63,9 @@ class EmailRegisterRequestTokenRestServlet(RestServlet): def on_POST(self, request): body = parse_json_object_from_request(request) - required = ['id_server', 'client_secret', 'email', 'send_attempt'] - absent = [] - for k in required: - if k not in body: - absent.append(k) - - if len(absent) > 0: - raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + assert_params_in_request(body, [ + 'id_server', 'client_secret', 'email', 'send_attempt' + ]) existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( 'email', body['email'] @@ -97,18 +94,11 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet): def on_POST(self, request): body = parse_json_object_from_request(request) - required = [ + assert_params_in_request(body, [ 'id_server', 'client_secret', 'country', 'phone_number', 'send_attempt', - ] - absent = [] - for k in required: - if k not in body: - absent.append(k) - - if len(absent) > 0: - raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) + ]) msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) From 82c5e7de2540a64c7a664f864983409df59d5db9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:42:44 +0000 Subject: [PATCH 08/18] Typos --- synapse/rest/client/v2_alpha/register.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index e97b9a32e..4e872e1a8 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -262,7 +262,7 @@ class RegisterRestServlet(RestServlet): "Already registered user ID %r for this session", registered_user_id ) - # don't re-register the email address + # don't re-register the threepids add_email = False add_msisdn = False else: @@ -420,7 +420,7 @@ class RegisterRestServlet(RestServlet): @defer.inlineCallbacks def _register_msisdn_threepid(self, user_id, threepid, token, bind_msisdn): - """Add aphone number as a 3pid identifier + """Add a phone number as a 3pid identifier Also optionally binds msisdn to the given user_id on the identity server From 0e0aee25c469fe3ba95d7410da670bc3fd73b510 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:46:22 +0000 Subject: [PATCH 09/18] Fix log line --- synapse/handlers/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index b273a4bee..e7a1bb724 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -331,7 +331,7 @@ class AuthHandler(BaseHandler): identity_handler = self.hs.get_handlers().identity_handler - logger.info("Getting validated threepid. threepidcreds: %r" % (threepid_creds,)) + logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds,)) threepid = yield identity_handler.threepid_from_creds(threepid_creds) if not threepid: From 65d43f3ca5965acc45b605e7867d877c0166ee96 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:48:43 +0000 Subject: [PATCH 10/18] Minor fixes from PR feedback --- synapse/rest/client/v1/login.py | 2 +- synapse/rest/client/v2_alpha/register.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index 77a0f00c5..a705fc838 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -66,7 +66,7 @@ def login_id_thirdparty_from_phone(identifier): """ Convert a phone login identifier type to a generic threepid identifier Args: - identifier: Login identifier dict of type 'm.id.phone' + identifier(dict): Login identifier dict of type 'm.id.phone' Returns: Login identifier dict of type 'm.id.threepid' """ diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 4e872e1a8..98b277e07 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -371,7 +371,7 @@ class RegisterRestServlet(RestServlet): reqd = ('medium', 'address', 'validated_at') if any(x not in threepid for x in reqd): logger.info("Can't add incomplete 3pid") - defer.returnValue() + return yield self.auth_handler.add_threepid( user_id, @@ -447,9 +447,7 @@ class RegisterRestServlet(RestServlet): if bind_msisdn: logger.info("bind_msisdn specified: binding") - logger.debug("Binding msisdn %s to %s" % ( - threepid, user_id - )) + logger.debug("Binding msisdn %s to %s", threepid, user_id) yield self.identity_handler.bind_threepid( threepid['threepid_creds'], user_id ) From 85bb322333f383768ceab1817f294fdb217f9b05 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:51:25 +0000 Subject: [PATCH 11/18] Pull out datastore in initialiser --- synapse/rest/client/v2_alpha/account.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index 9a5e1d871..71723ef3d 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -65,6 +65,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): def __init__(self, hs): super(MsisdnPasswordRequestTokenRestServlet, self).__init__() self.hs = hs + self.datastore = self.hs.get_datastore() self.identity_handler = hs.get_handlers().identity_handler @defer.inlineCallbacks @@ -78,7 +79,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) - existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + existingUid = yield self.datastore.get_user_id_by_threepid( 'msisdn', msisdn ) @@ -97,6 +98,7 @@ class PasswordRestServlet(RestServlet): self.hs = hs self.auth = hs.get_auth() self.auth_handler = hs.get_auth_handler() + self.datastore = self.hs.get_datastore() @defer.inlineCallbacks def on_POST(self, request): @@ -132,7 +134,7 @@ class PasswordRestServlet(RestServlet): # (See add_threepid in synapse/handlers/auth.py) threepid['address'] = threepid['address'].lower() # if using email, we must know about the email they're authing with! - threepid_user_id = yield self.hs.get_datastore().get_user_id_by_threepid( + threepid_user_id = yield self.datastore.get_user_id_by_threepid( threepid['medium'], threepid['address'] ) if not threepid_user_id: @@ -206,6 +208,7 @@ class EmailThreepidRequestTokenRestServlet(RestServlet): self.hs = hs super(EmailThreepidRequestTokenRestServlet, self).__init__() self.identity_handler = hs.get_handlers().identity_handler + self.datastore = self.hs.get_datastore() @defer.inlineCallbacks def on_POST(self, request): @@ -220,7 +223,7 @@ class EmailThreepidRequestTokenRestServlet(RestServlet): if absent: raise SynapseError(400, "Missing params: %r" % absent, Codes.MISSING_PARAM) - existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + existingUid = yield self.datastore.get_user_id_by_threepid( 'email', body['email'] ) @@ -238,6 +241,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet): self.hs = hs super(MsisdnThreepidRequestTokenRestServlet, self).__init__() self.identity_handler = hs.get_handlers().identity_handler + self.datastore = self.hs.get_datastore() @defer.inlineCallbacks def on_POST(self, request): @@ -257,7 +261,7 @@ class MsisdnThreepidRequestTokenRestServlet(RestServlet): msisdn = phone_number_to_msisdn(body['country'], body['phone_number']) - existingUid = yield self.hs.get_datastore().get_user_id_by_threepid( + existingUid = yield self.datastore.get_user_id_by_threepid( 'msisdn', msisdn ) @@ -277,6 +281,7 @@ class ThreepidRestServlet(RestServlet): self.identity_handler = hs.get_handlers().identity_handler self.auth = hs.get_auth() self.auth_handler = hs.get_auth_handler() + self.datastore = self.hs.get_datastore() @defer.inlineCallbacks def on_GET(self, request): @@ -284,7 +289,7 @@ class ThreepidRestServlet(RestServlet): requester = yield self.auth.get_user_by_req(request) - threepids = yield self.hs.get_datastore().user_get_threepids( + threepids = yield self.datastore.user_get_threepids( requester.user.to_string() ) From a9e2b9ec16e15267d812b0148a014dde6e3db64c Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:53:36 +0000 Subject: [PATCH 12/18] Add msisdn util file --- synapse/util/msisdn.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 synapse/util/msisdn.py diff --git a/synapse/util/msisdn.py b/synapse/util/msisdn.py new file mode 100644 index 000000000..77ef0aa19 --- /dev/null +++ b/synapse/util/msisdn.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Copyright 2015, 2016 OpenMarket Ltd +# Copyright 2017 Vector Creations Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import phonenumbers +from synapse.api.errors import SynapseError + +def phone_number_to_msisdn(country, number): + try: + phoneNumber = phonenumbers.parse(number, country) + except phonenumbers.NumberParseException: + raise SynapseError(400, "Unable to parse phone number") + return phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] From 1c99934b280485f564b357de204785d55cf2ca62 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 11:58:20 +0000 Subject: [PATCH 13/18] pep8 --- synapse/rest/client/v2_alpha/account.py | 2 +- synapse/util/msisdn.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/synapse/rest/client/v2_alpha/account.py b/synapse/rest/client/v2_alpha/account.py index 71723ef3d..aac76edf1 100644 --- a/synapse/rest/client/v2_alpha/account.py +++ b/synapse/rest/client/v2_alpha/account.py @@ -72,7 +72,7 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet): def on_POST(self, request): body = parse_json_object_from_request(request) - assert_params_in_request(body,[ + assert_params_in_request(body, [ 'id_server', 'client_secret', 'country', 'phone_number', 'send_attempt', ]) diff --git a/synapse/util/msisdn.py b/synapse/util/msisdn.py index 77ef0aa19..6905c5ce7 100644 --- a/synapse/util/msisdn.py +++ b/synapse/util/msisdn.py @@ -17,6 +17,7 @@ import phonenumbers from synapse.api.errors import SynapseError + def phone_number_to_msisdn(country, number): try: phoneNumber = phonenumbers.parse(number, country) From d4d3629aaf930d2febfc98f8ef1ef6bd315b8a4e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 17:01:26 +0000 Subject: [PATCH 14/18] Better error message --- synapse/rest/client/v2_alpha/register.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 98b277e07..2c184b731 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -107,7 +107,9 @@ class MsisdnRegisterRequestTokenRestServlet(RestServlet): ) if existingUid is not None: - raise SynapseError(400, "MSISDN is already in use", Codes.THREEPID_IN_USE) + raise SynapseError( + 400, "Phone number is already in use", Codes.THREEPID_IN_USE + ) ret = yield self.identity_handler.requestMsisdnToken(**body) defer.returnValue((200, ret)) From 727124a76250ece5f1014b4099d5efc7b43f2aaf Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 19:00:23 +0000 Subject: [PATCH 15/18] Not any more, it doesn't --- synapse/rest/client/v1/login.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py index a705fc838..c4bbb7027 100644 --- a/synapse/rest/client/v1/login.py +++ b/synapse/rest/client/v1/login.py @@ -43,7 +43,6 @@ def login_submission_legacy_convert(submission): If the input login submission is an old style object (ie. with top-level user / medium / address) convert it to a typed object. - Returns: Typed-object style login identifier """ if "user" in submission: submission["identifier"] = { From 3edc57296dbefa4a453ac11031614d827e32edf3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 19:00:51 +0000 Subject: [PATCH 16/18] Incorrectly copied copyright This file post-dates OM --- synapse/util/msisdn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/util/msisdn.py b/synapse/util/msisdn.py index 6905c5ce7..d320e411f 100644 --- a/synapse/util/msisdn.py +++ b/synapse/util/msisdn.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2015, 2016 OpenMarket Ltd # Copyright 2017 Vector Creations Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); From 9d0d40fc15e4bc79eee46242ae27980073cd528b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 19:05:29 +0000 Subject: [PATCH 17/18] Docs --- synapse/util/msisdn.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/synapse/util/msisdn.py b/synapse/util/msisdn.py index d320e411f..607161e7f 100644 --- a/synapse/util/msisdn.py +++ b/synapse/util/msisdn.py @@ -18,6 +18,19 @@ from synapse.api.errors import SynapseError def phone_number_to_msisdn(country, number): + """ + Takes an ISO-3166-1 2 letter country code and phone number and + returns an msisdn representing the canonical version of that + phone number. + Args: + country (str): ISO-3166-1 2 letter country code + number (str): Phone number in a national or international format + + Returns: + (str) The canonical form of the phone number, as an msisdn + Raises: + SynapseError if the number could not be parsed. + """ try: phoneNumber = phonenumbers.parse(number, country) except phonenumbers.NumberParseException: From ece7e00048e6990434eda33b06b598f88237e0c4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Mar 2017 19:07:18 +0000 Subject: [PATCH 18/18] Comment when our 3pids would be incomplete --- synapse/rest/client/v2_alpha/register.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py index 2c184b731..7448c1346 100644 --- a/synapse/rest/client/v2_alpha/register.py +++ b/synapse/rest/client/v2_alpha/register.py @@ -372,6 +372,7 @@ class RegisterRestServlet(RestServlet): """ reqd = ('medium', 'address', 'validated_at') if any(x not in threepid for x in reqd): + # This will only happen if the ID server returns a malformed response logger.info("Can't add incomplete 3pid") return @@ -437,6 +438,7 @@ class RegisterRestServlet(RestServlet): """ reqd = ('medium', 'address', 'validated_at') if any(x not in threepid for x in reqd): + # This will only happen if the ID server returns a malformed response logger.info("Can't add incomplete 3pid") defer.returnValue()