threepidunbindservlet.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 OpenMarket Ltd
  3. # Copyright 2018 New Vector 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. from __future__ import absolute_import
  17. import json
  18. import logging
  19. from sydent.hs_federation.verifier import NoAuthenticationError
  20. from signedjson.sign import SignatureVerifyException
  21. from sydent.http.servlets import dict_to_json_bytes
  22. from sydent.db.valsession import ThreePidValSessionStore
  23. from sydent.util.stringutils import is_valid_client_secret
  24. from sydent.validators import (
  25. IncorrectClientSecretException,
  26. InvalidSessionIdException,
  27. SessionNotValidatedException,
  28. )
  29. from twisted.web.resource import Resource
  30. from twisted.web import server
  31. from twisted.internet import defer
  32. logger = logging.getLogger(__name__)
  33. class ThreePidUnbindServlet(Resource):
  34. def __init__(self, sydent):
  35. self.sydent = sydent
  36. def render_POST(self, request):
  37. self._async_render_POST(request)
  38. return server.NOT_DONE_YET
  39. @defer.inlineCallbacks
  40. def _async_render_POST(self, request):
  41. try:
  42. try:
  43. # json.loads doesn't allow bytes in Python 3.5
  44. body = json.loads(request.content.read().decode("UTF-8"))
  45. except ValueError:
  46. request.setResponseCode(400)
  47. request.write(dict_to_json_bytes({'errcode': 'M_BAD_JSON', 'error': 'Malformed JSON'}))
  48. request.finish()
  49. return
  50. missing = [k for k in ("threepid", "mxid") if k not in body]
  51. if len(missing) > 0:
  52. request.setResponseCode(400)
  53. msg = "Missing parameters: "+(",".join(missing))
  54. request.write(dict_to_json_bytes({'errcode': 'M_MISSING_PARAMS', 'error': msg}))
  55. request.finish()
  56. return
  57. threepid = body['threepid']
  58. mxid = body['mxid']
  59. if 'medium' not in threepid or 'address' not in threepid:
  60. request.setResponseCode(400)
  61. request.write(dict_to_json_bytes({'errcode': 'M_MISSING_PARAMS', 'error': 'Threepid lacks medium / address'}))
  62. request.finish()
  63. return
  64. # We now check for authentication in two different ways, depending
  65. # on the contents of the request. If the user has supplied "sid"
  66. # (the Session ID returned by Sydent during the original binding)
  67. # and "client_secret" fields, they are trying to prove that they
  68. # were the original author of the bind. We then check that what
  69. # they supply matches and if it does, allow the unbind.
  70. #
  71. # However if these fields are not supplied, we instead check
  72. # whether the request originated from a homeserver, and if so the
  73. # same homeserver that originally created the bind. We do this by
  74. # checking the signature of the request. If it all matches up, we
  75. # allow the unbind.
  76. #
  77. # Only one method of authentication is required.
  78. if 'sid' in body and 'client_secret' in body:
  79. sid = body['sid']
  80. client_secret = body['client_secret']
  81. if not is_valid_client_secret(client_secret):
  82. request.setResponseCode(400)
  83. request.write(dict_to_json_bytes({
  84. 'errcode': 'M_INVALID_PARAM',
  85. 'error': 'Invalid client_secret provided'
  86. }))
  87. request.finish()
  88. return
  89. valSessionStore = ThreePidValSessionStore(self.sydent)
  90. try:
  91. s = valSessionStore.getValidatedSession(sid, client_secret)
  92. except (IncorrectClientSecretException, InvalidSessionIdException):
  93. request.setResponseCode(401)
  94. request.write(dict_to_json_bytes({
  95. 'errcode': 'M_NO_VALID_SESSION',
  96. 'error': "No valid session was found matching that sid and client secret"
  97. }))
  98. request.finish()
  99. return
  100. except SessionNotValidatedException:
  101. request.setResponseCode(403)
  102. request.write(dict_to_json_bytes({
  103. 'errcode': 'M_SESSION_NOT_VALIDATED',
  104. 'error': "This validation session has not yet been completed"
  105. }))
  106. return
  107. if s.medium != threepid['medium'] or s.address != threepid['address']:
  108. request.setResponseCode(403)
  109. request.write(dict_to_json_bytes({
  110. 'errcode': 'M_FORBIDDEN',
  111. 'error': 'Provided session information does not match medium/address combo',
  112. }))
  113. request.finish()
  114. return
  115. else:
  116. try:
  117. origin_server_name = yield self.sydent.sig_verifier.authenticate_request(request, body)
  118. except SignatureVerifyException as ex:
  119. request.setResponseCode(401)
  120. request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': str(ex)}))
  121. request.finish()
  122. return
  123. except NoAuthenticationError as ex:
  124. request.setResponseCode(401)
  125. request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': str(ex)}))
  126. request.finish()
  127. return
  128. except:
  129. logger.exception("Exception whilst authenticating unbind request")
  130. request.setResponseCode(500)
  131. request.write(dict_to_json_bytes({'errcode': 'M_UNKNOWN', 'error': 'Internal Server Error'}))
  132. request.finish()
  133. return
  134. if not mxid.endswith(':' + origin_server_name):
  135. request.setResponseCode(403)
  136. request.write(dict_to_json_bytes({'errcode': 'M_FORBIDDEN', 'error': 'Origin server name does not match mxid'}))
  137. request.finish()
  138. return
  139. self.sydent.threepidBinder.removeBinding(threepid, mxid)
  140. request.write(dict_to_json_bytes({}))
  141. request.finish()
  142. except Exception as ex:
  143. logger.exception("Exception whilst handling unbind")
  144. request.setResponseCode(500)
  145. request.write(dict_to_json_bytes({'errcode': 'M_UNKNOWN', 'error': str(ex)}))
  146. request.finish()