identity.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. HttpResponseException,
  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. try:
  72. data = yield self.http_client.get_json(
  73. "https://%s%s" % (
  74. id_server,
  75. "/_matrix/identity/api/v1/3pid/getValidated3pid"
  76. ),
  77. {'sid': creds['sid'], 'client_secret': client_secret}
  78. )
  79. except HttpResponseException as e:
  80. logger.info("getValidated3pid failed with Matrix error: %r", e)
  81. raise e.to_synapse_error()
  82. if 'medium' in data:
  83. defer.returnValue(data)
  84. defer.returnValue(None)
  85. @defer.inlineCallbacks
  86. def bind_threepid(self, creds, mxid):
  87. logger.debug("binding threepid %r to %s", creds, mxid)
  88. data = None
  89. if 'id_server' in creds:
  90. id_server = creds['id_server']
  91. elif 'idServer' in creds:
  92. id_server = creds['idServer']
  93. else:
  94. raise SynapseError(400, "No id_server in creds")
  95. if 'client_secret' in creds:
  96. client_secret = creds['client_secret']
  97. elif 'clientSecret' in creds:
  98. client_secret = creds['clientSecret']
  99. else:
  100. raise SynapseError(400, "No client_secret in creds")
  101. try:
  102. data = yield self.http_client.post_urlencoded_get_json(
  103. "https://%s%s" % (
  104. id_server, "/_matrix/identity/api/v1/3pid/bind"
  105. ),
  106. {
  107. 'sid': creds['sid'],
  108. 'client_secret': client_secret,
  109. 'mxid': mxid,
  110. }
  111. )
  112. logger.debug("bound threepid %r to %s", creds, mxid)
  113. except CodeMessageException as e:
  114. data = json.loads(e.msg) # XXX WAT?
  115. defer.returnValue(data)
  116. @defer.inlineCallbacks
  117. def try_unbind_threepid(self, mxid, threepid):
  118. """Removes a binding from an identity server
  119. Args:
  120. mxid (str): Matrix user ID of binding to be removed
  121. threepid (dict): Dict with medium & address of binding to be removed
  122. Raises:
  123. SynapseError: If we failed to contact the identity server
  124. Returns:
  125. Deferred[bool]: True on success, otherwise False if the identity
  126. server doesn't support unbinding
  127. """
  128. logger.debug("unbinding threepid %r from %s", threepid, mxid)
  129. if not self.trusted_id_servers:
  130. logger.warn("Can't unbind threepid: no trusted ID servers set in config")
  131. defer.returnValue(False)
  132. # We don't track what ID server we added 3pids on (perhaps we ought to)
  133. # but we assume that any of the servers in the trusted list are in the
  134. # same ID server federation, so we can pick any one of them to send the
  135. # deletion request to.
  136. id_server = next(iter(self.trusted_id_servers))
  137. url = "https://%s/_matrix/identity/api/v1/3pid/unbind" % (id_server,)
  138. content = {
  139. "mxid": mxid,
  140. "threepid": threepid,
  141. }
  142. # we abuse the federation http client to sign the request, but we have to send it
  143. # using the normal http client since we don't want the SRV lookup and want normal
  144. # 'browser-like' HTTPS.
  145. auth_headers = self.federation_http_client.build_auth_headers(
  146. destination=None,
  147. method='POST',
  148. url_bytes='/_matrix/identity/api/v1/3pid/unbind'.encode('ascii'),
  149. content=content,
  150. destination_is=id_server,
  151. )
  152. headers = {
  153. b"Authorization": auth_headers,
  154. }
  155. try:
  156. yield self.http_client.post_json_get_json(
  157. url,
  158. content,
  159. headers,
  160. )
  161. except HttpResponseException as e:
  162. if e.code in (400, 404, 501,):
  163. # The remote server probably doesn't support unbinding (yet)
  164. logger.warn("Received %d response while unbinding threepid", e.code)
  165. defer.returnValue(False)
  166. else:
  167. logger.error("Failed to unbind threepid on identity server: %s", e)
  168. raise SynapseError(502, "Failed to contact identity server")
  169. defer.returnValue(True)
  170. @defer.inlineCallbacks
  171. def requestEmailToken(self, id_server, email, client_secret, send_attempt, **kwargs):
  172. if not self._should_trust_id_server(id_server):
  173. raise SynapseError(
  174. 400, "Untrusted ID server '%s'" % id_server,
  175. Codes.SERVER_NOT_TRUSTED
  176. )
  177. params = {
  178. 'email': email,
  179. 'client_secret': client_secret,
  180. 'send_attempt': send_attempt,
  181. }
  182. params.update(kwargs)
  183. try:
  184. data = yield self.http_client.post_json_get_json(
  185. "https://%s%s" % (
  186. id_server,
  187. "/_matrix/identity/api/v1/validate/email/requestToken"
  188. ),
  189. params
  190. )
  191. defer.returnValue(data)
  192. except HttpResponseException as e:
  193. logger.info("Proxied requestToken failed: %r", e)
  194. raise e.to_synapse_error()
  195. @defer.inlineCallbacks
  196. def requestMsisdnToken(
  197. self, id_server, country, phone_number,
  198. client_secret, send_attempt, **kwargs
  199. ):
  200. if not self._should_trust_id_server(id_server):
  201. raise SynapseError(
  202. 400, "Untrusted ID server '%s'" % id_server,
  203. Codes.SERVER_NOT_TRUSTED
  204. )
  205. params = {
  206. 'country': country,
  207. 'phone_number': phone_number,
  208. 'client_secret': client_secret,
  209. 'send_attempt': send_attempt,
  210. }
  211. params.update(kwargs)
  212. try:
  213. data = yield self.http_client.post_json_get_json(
  214. "https://%s%s" % (
  215. id_server,
  216. "/_matrix/identity/api/v1/validate/msisdn/requestToken"
  217. ),
  218. params
  219. )
  220. defer.returnValue(data)
  221. except HttpResponseException as e:
  222. logger.info("Proxied requestToken failed: %r", e)
  223. raise e.to_synapse_error()