From a812f596039d062da3071973ef04b783732d0725 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 20 Apr 2017 11:59:40 +0100 Subject: [PATCH 1/2] Support config for SMS originators --- README.rst | 10 +++++ sydent/http/servlets/msisdnservlet.py | 3 +- sydent/sms/openmarket.py | 17 +++++++- sydent/validators/msisdnvalidator.py | 61 ++++++++++++++++++++++++--- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 81321432..3239d47c 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,16 @@ Having installed dependencies, you can run sydent using:: This will create a configuration file in sydent.conf with some defaults. You'll most likely want to change the server name and specify a mail relay. +Defaults for SMS originators will not be added to the generated config file, these should be added in the form:: + + originators. = : + +Where country code is the numeric country code, or 'default' to specify the originator used for countries not listed. For example, to use a selection of long codes for the US/Canda, a short code for the UK and an alphanumertic originator for everywhere else:: + + originators.1 = long:12125552368,long:12125552369 + originators.44 = short:12345 + originators.default = alpha:Matrix + Requests ======== diff --git a/sydent/http/servlets/msisdnservlet.py b/sydent/http/servlets/msisdnservlet.py index ff368f3b..750aaed4 100644 --- a/sydent/http/servlets/msisdnservlet.py +++ b/sydent/http/servlets/msisdnservlet.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 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. @@ -67,7 +68,7 @@ def render_POST(self, request): try: sid = self.sydent.validators.msisdn.requestToken( - msisdn, clientSecret, sendAttempt, None + phone_number_object, clientSecret, sendAttempt, None ) except Exception as e: logger.error("Exception sending SMS: %r", e); diff --git a/sydent/sms/openmarket.py b/sydent/sms/openmarket.py index bf69fcf3..490bb50d 100644 --- a/sydent/sms/openmarket.py +++ b/sydent/sms/openmarket.py @@ -30,6 +30,18 @@ # Useful for testing. #API_BASE_URL = "http://smsc-cie.openmarket.com/sms/v4/mt" +TONS = { + 'long': 1, + 'short': 3, + 'alpha': 5, +} + +def tonFromType(t): + if t in TONS: + return TONS[t] + raise Exception("Unknown number type (%s) for originator" % t) + + class OpenMarketSMS: def __init__(self, sydent): self.sydent = sydent @@ -49,8 +61,9 @@ def sendTextSMS(self, body, dest, source=None): }, } if source: - body['source'] = { - "address": source, + body['mobileTerminate']['source'] = { + "ton": tonFromType(source['type']), + "address": source['text'], } b64creds = b64encode(b"%s:%s" % ( diff --git a/sydent/validators/msisdnvalidator.py b/sydent/validators/msisdnvalidator.py index 4a3cc803..dcd7d296 100644 --- a/sydent/validators/msisdnvalidator.py +++ b/sydent/validators/msisdnvalidator.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright 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. @@ -16,6 +17,7 @@ import logging import urllib +import phonenumbers from sydent.db.valsession import ThreePidValSessionStore from sydent.validators import ValidationSession, common @@ -31,9 +33,33 @@ def __init__(self, sydent): self.sydent = sydent self.omSms = OpenMarketSMS(sydent) - def requestToken(self, msisdn, clientSecret, sendAttempt, nextLink): + # cache originators from config file + self.originators = {} + for opt in self.sydent.cfg.options('sms'): + if opt.startswith('originators.'): + country = opt.split('.')[1] + rawVal = self.sydent.cfg.get('sms', opt) + rawList = [i.strip() for i in rawVal.split(',')] + + self.originators[country] = [] + for origString in rawList: + parts = origString.split(':') + if len(parts) != 2: + raise Exception("Originators must be in form: long:, short: or alpha:, separated by commas") + if parts[0] not in ['long', 'short', 'alpha']: + raise Exception("Invalid originator type: valid types are long, short and alpha") + self.originators[country].append({ + "type": parts[0], + "text": parts[1], + }) + + def requestToken(self, phoneNumber, clientSecret, sendAttempt, nextLink): valSessionStore = ThreePidValSessionStore(self.sydent) + msisdn = phonenumbers.format_number( + phoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + valSession = valSessionStore.getOrCreateTokenSession( medium='msisdn', address=msisdn, clientSecret=clientSecret ) @@ -44,20 +70,43 @@ def requestToken(self, msisdn, clientSecret, sendAttempt, nextLink): logger.info("Not texting code because current send attempt (%d) is not less than given send attempt (%s)", int(sendAttempt), int(valSession.sendAttemptNumber)) return valSession.id + smsBodyTemplate = self.sydent.cfg.get('sms', 'bodyTemplate') + originator = self.getOriginator(phoneNumber) + logger.info( - "Attempting to text code %s to %s", - valSession.token, msisdn, + "Attempting to text code %s to %s (country %d) with originator %s", + valSession.token, msisdn, phoneNumber.country_code, originator ) - smsBodyTemplate = self.sydent.cfg.get('sms', 'bodyTemplate') - smsBody = smsBodyTemplate.format(token=valSession.token) - self.omSms.sendTextSMS(smsBody, msisdn) + self.omSms.sendTextSMS(smsBody, msisdn, originator) valSessionStore.setSendAttemptNumber(valSession.id, sendAttempt) return valSession.id + def getOriginator(self, destPhoneNumber): + countryCode = str(destPhoneNumber.country_code) + + origs = [{ + "type": "alpha", + "text": "Matrix", + }] + if countryCode in self.originators: + origs = self.originators[countryCode] + elif 'default' in self.originators: + origs = self.originators['default'] + + # deterministically pick an originator from the list of possible + # originators, so if someone requests multiple codes, they come from + # a consistent number (if there's any chance that some originators are + # more likley to work than others, we may want to change, but it feels + # like this should be something other than just picking one randomly). + msisdn = phonenumbers.format_number( + destPhoneNumber, phonenumbers.PhoneNumberFormat.E164 + )[1:] + return origs[sum([int(i) for i in msisdn]) % len(origs)] + def validateSessionWithToken(self, sid, clientSecret, token): return common.validateSessionWithToken(self.sydent, sid, clientSecret, token) From 93cb5faf64f4af652734c7e35bd1f36551bbab79 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 20 Apr 2017 13:41:55 +0100 Subject: [PATCH 2/2] Doc TONS constant --- sydent/sms/openmarket.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sydent/sms/openmarket.py b/sydent/sms/openmarket.py index 490bb50d..9e2a5158 100644 --- a/sydent/sms/openmarket.py +++ b/sydent/sms/openmarket.py @@ -30,6 +30,7 @@ # Useful for testing. #API_BASE_URL = "http://smsc-cie.openmarket.com/sms/v4/mt" +# The TON (ie. Type of Number) codes by type used in our config file TONS = { 'long': 1, 'short': 3,