msisdnservlet.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2017 Vector Creations Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import logging
  17. from twisted.web.resource import Resource
  18. import phonenumbers
  19. from sydent.validators import (
  20. IncorrectClientSecretException, SessionExpiredException, DestinationRejectedException
  21. )
  22. from sydent.http.servlets import get_args, jsonwrap, send_cors
  23. logger = logging.getLogger(__name__)
  24. class MsisdnRequestCodeServlet(Resource):
  25. isLeaf = True
  26. def __init__(self, syd):
  27. self.sydent = syd
  28. @jsonwrap
  29. def render_POST(self, request):
  30. send_cors(request)
  31. error, args = get_args(request, ('phone_number', 'country', 'client_secret', 'send_attempt'))
  32. if error:
  33. request.setResponseCode(400)
  34. return error
  35. raw_phone_number = args['phone_number']
  36. country = args['country']
  37. clientSecret = args['client_secret']
  38. sendAttempt = args['send_attempt']
  39. try:
  40. phone_number_object = phonenumbers.parse(raw_phone_number, country)
  41. except Exception as e:
  42. logger.warn("Invalid phone number given: %r", e)
  43. request.setResponseCode(400)
  44. return {'errcode': 'M_INVALID_PHONE_NUMBER', 'error': "Invalid phone number" }
  45. msisdn = phonenumbers.format_number(
  46. phone_number_object, phonenumbers.PhoneNumberFormat.E164
  47. )[1:]
  48. # International formatted number. The same as an E164 but with spaces
  49. # in appropriate places to make it nicer for the humans.
  50. intl_fmt = phonenumbers.format_number(
  51. phone_number_object, phonenumbers.PhoneNumberFormat.INTERNATIONAL
  52. )
  53. resp = None
  54. try:
  55. sid = self.sydent.validators.msisdn.requestToken(
  56. phone_number_object, clientSecret, sendAttempt, None
  57. )
  58. except DestinationRejectedException:
  59. logger.error("Destination rejected for number: %s", msisdn);
  60. request.setResponseCode(400)
  61. resp = {'errcode': 'M_DESTINATION_REJECTED', 'error': 'Phone numbers in this country are not currently supported'}
  62. except Exception as e:
  63. logger.error("Exception sending SMS: %r", e);
  64. request.setResponseCode(500)
  65. resp = {'errcode': 'M_UNKNOWN', 'error':'Internal Server Error'}
  66. if not resp:
  67. resp = {
  68. 'success': True, 'sid': str(sid),
  69. 'msisdn': msisdn, 'intl_fmt': intl_fmt,
  70. }
  71. return resp
  72. @jsonwrap
  73. def render_OPTIONS(self, request):
  74. send_cors(request)
  75. request.setResponseCode(200)
  76. return {}
  77. class MsisdnValidateCodeServlet(Resource):
  78. isLeaf = True
  79. def __init__(self, syd):
  80. self.sydent = syd
  81. def render_GET(self, request):
  82. send_cors(request)
  83. err, args = get_args(request, ('token', 'sid', 'client_secret'))
  84. if err:
  85. return err
  86. resp = self.do_validate_request(args)
  87. if 'success' in resp and resp['success']:
  88. msg = "Verification successful! Please return to your Matrix client to continue."
  89. if 'next_link' in args:
  90. next_link = args['next_link']
  91. request.setResponseCode(302)
  92. request.setHeader("Location", next_link)
  93. else:
  94. msg = "Verification failed: you may need to request another verification text"
  95. templateFile = self.sydent.cfg.get('http', 'verify_response_template')
  96. request.setHeader("Content-Type", "text/html")
  97. return open(templateFile).read() % {'message': msg}
  98. @jsonwrap
  99. def render_POST(self, request):
  100. send_cors(request)
  101. err, args = get_args(request, ('token', 'sid', 'client_secret'))
  102. if err:
  103. return err
  104. return self.do_validate_request(args)
  105. def do_validate_request(self, args):
  106. sid = args['sid']
  107. tokenString = args['token']
  108. clientSecret = args['client_secret']
  109. try:
  110. resp = self.sydent.validators.msisdn.validateSessionWithToken(sid, clientSecret, tokenString)
  111. except IncorrectClientSecretException:
  112. return {'success': False, 'errcode': 'M_INCORRECT_CLIENT_SECRET',
  113. 'error': "Client secret does not match the one given when requesting the token"}
  114. except SessionExpiredException:
  115. return {'success': False, 'errcode': 'M_SESSION_EXPIRED',
  116. 'error': "This validation session has expired: call requestToken again"}
  117. if not resp:
  118. resp = {'success': False}
  119. return resp
  120. @jsonwrap
  121. def render_OPTIONS(self, request):
  122. send_cors(request)
  123. request.setResponseCode(200)
  124. return {}