identity.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015, 2016 OpenMarket Ltd
  3. # Copyright 2017 Vector Creations Ltd
  4. # Copyright 2018 New Vector Ltd
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. """Utilities for interacting with Identity Servers"""
  18. import logging
  19. from canonicaljson import json
  20. from twisted.internet import defer
  21. from synapse.api.errors import (
  22. CodeMessageException,
  23. Codes,
  24. MatrixCodeMessageException,
  25. SynapseError,
  26. )
  27. from ._base import BaseHandler
  28. logger = logging.getLogger(__name__)
  29. class IdentityHandler(BaseHandler):
  30. def __init__(self, hs):
  31. super(IdentityHandler, self).__init__(hs)
  32. self.http_client = hs.get_simple_http_client()
  33. self.federation_http_client = hs.get_http_client()
  34. self.trusted_id_servers = set(hs.config.trusted_third_party_id_servers)
  35. self.trust_any_id_server_just_for_testing_do_not_use = (
  36. hs.config.use_insecure_ssl_client_just_for_testing_do_not_use
  37. )
  38. def _should_trust_id_server(self, id_server):
  39. if id_server not in self.trusted_id_servers:
  40. if self.trust_any_id_server_just_for_testing_do_not_use:
  41. logger.warn(
  42. "Trusting untrustworthy ID server %r even though it isn't"
  43. " in the trusted id list for testing because"
  44. " 'use_insecure_ssl_client_just_for_testing_do_not_use'"
  45. " is set in the config",
  46. id_server,
  47. )
  48. else:
  49. return False
  50. return True
  51. @defer.inlineCallbacks
  52. def threepid_from_creds(self, creds):
  53. if 'id_server' in creds:
  54. id_server = creds['id_server']
  55. elif 'idServer' in creds:
  56. id_server = creds['idServer']
  57. else:
  58. raise SynapseError(400, "No id_server in creds")
  59. if 'client_secret' in creds:
  60. client_secret = creds['client_secret']
  61. elif 'clientSecret' in creds:
  62. client_secret = creds['clientSecret']
  63. else:
  64. raise SynapseError(400, "No client_secret in creds")
  65. if not self._should_trust_id_server(id_server):
  66. logger.warn(
  67. '%s is not a trusted ID server: rejecting 3pid ' +
  68. 'credentials', id_server
  69. )
  70. defer.returnValue(None)
  71. data = {}
  72. try:
  73. data = yield self.http_client.get_json(
  74. "https://%s%s" % (
  75. id_server,
  76. "/_matrix/identity/api/v1/3pid/getValidated3pid"
  77. ),
  78. {'sid': creds['sid'], 'client_secret': client_secret}
  79. )
  80. except MatrixCodeMessageException as e:
  81. logger.info("getValidated3pid failed with Matrix error: %r", e)
  82. raise SynapseError(e.code, e.msg, e.errcode)
  83. except CodeMessageException as e:
  84. data = json.loads(e.msg)
  85. if 'medium' in data:
  86. defer.returnValue(data)
  87. defer.returnValue(None)
  88. @defer.inlineCallbacks
  89. def bind_threepid(self, creds, mxid):
  90. logger.debug("binding threepid %r to %s", creds, mxid)
  91. data = None
  92. if 'id_server' in creds:
  93. id_server = creds['id_server']
  94. elif 'idServer' in creds:
  95. id_server = creds['idServer']
  96. else:
  97. raise SynapseError(400, "No id_server in creds")
  98. if 'client_secret' in creds:
  99. client_secret = creds['client_secret']
  100. elif 'clientSecret' in creds:
  101. client_secret = creds['clientSecret']
  102. else:
  103. raise SynapseError(400, "No client_secret in creds")
  104. try:
  105. data = yield self.http_client.post_urlencoded_get_json(
  106. "https://%s%s" % (
  107. id_server, "/_matrix/identity/api/v1/3pid/bind"
  108. ),
  109. {
  110. 'sid': creds['sid'],
  111. 'client_secret': client_secret,
  112. 'mxid': mxid,
  113. }
  114. )
  115. logger.debug("bound threepid %r to %s", creds, mxid)
  116. except CodeMessageException as e:
  117. data = json.loads(e.msg)
  118. defer.returnValue(data)
  119. @defer.inlineCallbacks
  120. def unbind_threepid(self, mxid, threepid):
  121. """
  122. Removes a binding from an identity server
  123. Args:
  124. mxid (str): Matrix user ID of binding to be removed
  125. threepid (dict): Dict with medium & address of binding to be removed
  126. Returns:
  127. Deferred[bool]: True on success, otherwise False
  128. """
  129. logger.debug("unbinding threepid %r from %s", threepid, mxid)
  130. if not self.trusted_id_servers:
  131. logger.warn("Can't unbind threepid: no trusted ID servers set in config")
  132. defer.returnValue(False)
  133. # We don't track what ID server we added 3pids on (perhaps we ought to)
  134. # but we assume that any of the servers in the trusted list are in the
  135. # same ID server federation, so we can pick any one of them to send the
  136. # deletion request to.
  137. id_server = next(iter(self.trusted_id_servers))
  138. url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
  139. content = {
  140. "mxid": mxid,
  141. "threepid": threepid,
  142. }
  143. headers = {}
  144. # we abuse the federation http client to sign the request, but we have to send it
  145. # using the normal http client since we don't want the SRV lookup and want normal
  146. # 'browser-like' HTTPS.
  147. self.federation_http_client.sign_request(
  148. destination=None,
  149. method='POST',
  150. url_bytes='/_matrix/identity/api/v1/3pid/unbind'.encode('ascii'),
  151. headers_dict=headers,
  152. content=content,
  153. destination_is=id_server,
  154. )
  155. yield self.http_client.post_json_get_json(
  156. url,
  157. content,
  158. headers,
  159. )
  160. defer.returnValue(True)
  161. @defer.inlineCallbacks
  162. def requestEmailToken(self, id_server, email, client_secret, send_attempt, **kwargs):
  163. if not self._should_trust_id_server(id_server):
  164. raise SynapseError(
  165. 400, "Untrusted ID server '%s'" % id_server,
  166. Codes.SERVER_NOT_TRUSTED
  167. )
  168. params = {
  169. 'email': email,
  170. 'client_secret': client_secret,
  171. 'send_attempt': send_attempt,
  172. }
  173. params.update(kwargs)
  174. try:
  175. data = yield self.http_client.post_json_get_json(
  176. "https://%s%s" % (
  177. id_server,
  178. "/_matrix/identity/api/v1/validate/email/requestToken"
  179. ),
  180. params
  181. )
  182. defer.returnValue(data)
  183. except MatrixCodeMessageException as e:
  184. logger.info("Proxied requestToken failed with Matrix error: %r", e)
  185. raise SynapseError(e.code, e.msg, e.errcode)
  186. except CodeMessageException as e:
  187. logger.info("Proxied requestToken failed: %r", e)
  188. raise e
  189. @defer.inlineCallbacks
  190. def requestMsisdnToken(
  191. self, id_server, country, phone_number,
  192. client_secret, send_attempt, **kwargs
  193. ):
  194. if not self._should_trust_id_server(id_server):
  195. raise SynapseError(
  196. 400, "Untrusted ID server '%s'" % id_server,
  197. Codes.SERVER_NOT_TRUSTED
  198. )
  199. params = {
  200. 'country': country,
  201. 'phone_number': phone_number,
  202. 'client_secret': client_secret,
  203. 'send_attempt': send_attempt,
  204. }
  205. params.update(kwargs)
  206. try:
  207. data = yield self.http_client.post_json_get_json(
  208. "https://%s%s" % (
  209. id_server,
  210. "/_matrix/identity/api/v1/validate/msisdn/requestToken"
  211. ),
  212. params
  213. )
  214. defer.returnValue(data)
  215. except MatrixCodeMessageException as e:
  216. logger.info("Proxied requestToken failed with Matrix error: %r", e)
  217. raise SynapseError(e.code, e.msg, e.errcode)
  218. except CodeMessageException as e:
  219. logger.info("Proxied requestToken failed: %r", e)
  220. raise e