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