You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

806 lines
30 KiB

  1. # Copyright 2015, 2016 OpenMarket Ltd
  2. # Copyright 2017 Vector Creations 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. """Utilities for interacting with Identity Servers"""
  17. import logging
  18. import urllib.parse
  19. from typing import TYPE_CHECKING, Awaitable, Callable, Dict, List, Optional, Tuple
  20. from synapse.api.errors import (
  21. CodeMessageException,
  22. Codes,
  23. HttpResponseException,
  24. SynapseError,
  25. )
  26. from synapse.api.ratelimiting import Ratelimiter
  27. from synapse.http import RequestTimedOutError
  28. from synapse.http.client import SimpleHttpClient
  29. from synapse.http.site import SynapseRequest
  30. from synapse.types import JsonDict, Requester
  31. from synapse.util import json_decoder
  32. from synapse.util.hash import sha256_and_url_safe_base64
  33. from synapse.util.stringutils import (
  34. assert_valid_client_secret,
  35. random_string,
  36. valid_id_server_location,
  37. )
  38. if TYPE_CHECKING:
  39. from synapse.server import HomeServer
  40. logger = logging.getLogger(__name__)
  41. id_server_scheme = "https://"
  42. class IdentityHandler:
  43. def __init__(self, hs: "HomeServer"):
  44. self.store = hs.get_datastores().main
  45. # An HTTP client for contacting trusted URLs.
  46. self.http_client = SimpleHttpClient(hs)
  47. # An HTTP client for contacting identity servers specified by clients.
  48. self._http_client = SimpleHttpClient(
  49. hs,
  50. ip_blocklist=hs.config.server.federation_ip_range_blocklist,
  51. ip_allowlist=hs.config.server.federation_ip_range_allowlist,
  52. )
  53. self.federation_http_client = hs.get_federation_http_client()
  54. self.hs = hs
  55. self._web_client_location = hs.config.email.invite_client_location
  56. # Ratelimiters for `/requestToken` endpoints.
  57. self._3pid_validation_ratelimiter_ip = Ratelimiter(
  58. store=self.store,
  59. clock=hs.get_clock(),
  60. cfg=hs.config.ratelimiting.rc_3pid_validation,
  61. )
  62. self._3pid_validation_ratelimiter_address = Ratelimiter(
  63. store=self.store,
  64. clock=hs.get_clock(),
  65. cfg=hs.config.ratelimiting.rc_3pid_validation,
  66. )
  67. async def ratelimit_request_token_requests(
  68. self,
  69. request: SynapseRequest,
  70. medium: str,
  71. address: str,
  72. ) -> None:
  73. """Used to ratelimit requests to `/requestToken` by IP and address.
  74. Args:
  75. request: The associated request
  76. medium: The type of threepid, e.g. "msisdn" or "email"
  77. address: The actual threepid ID, e.g. the phone number or email address
  78. """
  79. await self._3pid_validation_ratelimiter_ip.ratelimit(
  80. None, (medium, request.getClientAddress().host)
  81. )
  82. await self._3pid_validation_ratelimiter_address.ratelimit(
  83. None, (medium, address)
  84. )
  85. async def threepid_from_creds(
  86. self, id_server: str, creds: Dict[str, str]
  87. ) -> Optional[JsonDict]:
  88. """
  89. Retrieve and validate a threepid identifier from a "credentials" dictionary against a
  90. given identity server
  91. Args:
  92. id_server: The identity server to validate 3PIDs against. Must be a
  93. complete URL including the protocol (http(s)://)
  94. creds: Dictionary containing the following keys:
  95. * client_secret|clientSecret: A unique secret str provided by the client
  96. * sid: The ID of the validation session
  97. Returns:
  98. A dictionary consisting of response params to the /getValidated3pid
  99. endpoint of the Identity Service API, or None if the threepid was not found
  100. """
  101. client_secret = creds.get("client_secret") or creds.get("clientSecret")
  102. if not client_secret:
  103. raise SynapseError(
  104. 400, "Missing param client_secret in creds", errcode=Codes.MISSING_PARAM
  105. )
  106. assert_valid_client_secret(client_secret)
  107. session_id = creds.get("sid")
  108. if not session_id:
  109. raise SynapseError(
  110. 400, "Missing param session_id in creds", errcode=Codes.MISSING_PARAM
  111. )
  112. query_params = {"sid": session_id, "client_secret": client_secret}
  113. url = id_server + "/_matrix/identity/api/v1/3pid/getValidated3pid"
  114. try:
  115. data = await self.http_client.get_json(url, query_params)
  116. except RequestTimedOutError:
  117. raise SynapseError(500, "Timed out contacting identity server")
  118. except HttpResponseException as e:
  119. logger.info(
  120. "%s returned %i for threepid validation for: %s",
  121. id_server,
  122. e.code,
  123. creds,
  124. )
  125. return None
  126. # Old versions of Sydent return a 200 http code even on a failed validation
  127. # check. Thus, in addition to the HttpResponseException check above (which
  128. # checks for non-200 errors), we need to make sure validation_session isn't
  129. # actually an error, identified by the absence of a "medium" key
  130. # See https://github.com/matrix-org/sydent/issues/215 for details
  131. if "medium" in data:
  132. return data
  133. logger.info("%s reported non-validated threepid: %s", id_server, creds)
  134. return None
  135. async def bind_threepid(
  136. self,
  137. client_secret: str,
  138. sid: str,
  139. mxid: str,
  140. id_server: str,
  141. id_access_token: str,
  142. ) -> JsonDict:
  143. """Bind a 3PID to an identity server
  144. Args:
  145. client_secret: A unique secret provided by the client
  146. sid: The ID of the validation session
  147. mxid: The MXID to bind the 3PID to
  148. id_server: The domain of the identity server to query
  149. id_access_token: The access token to authenticate to the identity
  150. server with
  151. Raises:
  152. SynapseError: On any of the following conditions
  153. - the supplied id_server is not a valid identity server name
  154. - we failed to contact the supplied identity server
  155. Returns:
  156. The response from the identity server
  157. """
  158. logger.debug("Proxying threepid bind request for %s to %s", mxid, id_server)
  159. if not valid_id_server_location(id_server):
  160. raise SynapseError(
  161. 400,
  162. "id_server must be a valid hostname with optional port and path components",
  163. )
  164. bind_data = {"sid": sid, "client_secret": client_secret, "mxid": mxid}
  165. bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server,)
  166. headers = {"Authorization": create_id_access_token_header(id_access_token)}
  167. try:
  168. # Use the blacklisting http client as this call is only to identity servers
  169. # provided by a client
  170. data = await self._http_client.post_json_get_json(
  171. bind_url, bind_data, headers=headers
  172. )
  173. # Remember where we bound the threepid
  174. await self.store.add_user_bound_threepid(
  175. user_id=mxid,
  176. medium=data["medium"],
  177. address=data["address"],
  178. id_server=id_server,
  179. )
  180. return data
  181. except HttpResponseException as e:
  182. logger.error("3PID bind failed with Matrix error: %r", e)
  183. raise e.to_synapse_error()
  184. except RequestTimedOutError:
  185. raise SynapseError(500, "Timed out contacting identity server")
  186. except CodeMessageException as e:
  187. data = json_decoder.decode(e.msg) # XXX WAT?
  188. return data
  189. async def try_unbind_threepid(
  190. self, mxid: str, medium: str, address: str, id_server: Optional[str]
  191. ) -> bool:
  192. """Attempt to remove a 3PID from one or more identity servers.
  193. Args:
  194. mxid: Matrix user ID of binding to be removed
  195. medium: The medium of the third-party ID.
  196. address: The address of the third-party ID.
  197. id_server: An identity server to attempt to unbind from. If None,
  198. attempt to remove the association from all identity servers
  199. known to potentially have it.
  200. Raises:
  201. SynapseError: If we failed to contact one or more identity servers.
  202. Returns:
  203. True on success, otherwise False if the identity server doesn't
  204. support unbinding (or no identity server to contact was found).
  205. """
  206. if id_server:
  207. id_servers = [id_server]
  208. else:
  209. id_servers = await self.store.get_id_servers_user_bound(
  210. mxid, medium, address
  211. )
  212. # We don't know where to unbind, so we don't have a choice but to return
  213. if not id_servers:
  214. return False
  215. changed = True
  216. for id_server in id_servers:
  217. changed &= await self._try_unbind_threepid_with_id_server(
  218. mxid, medium, address, id_server
  219. )
  220. return changed
  221. async def _try_unbind_threepid_with_id_server(
  222. self, mxid: str, medium: str, address: str, id_server: str
  223. ) -> bool:
  224. """Removes a binding from an identity server
  225. Args:
  226. mxid: Matrix user ID of binding to be removed
  227. medium: The medium of the third-party ID
  228. address: The address of the third-party ID
  229. id_server: Identity server to unbind from
  230. Raises:
  231. SynapseError: On any of the following conditions
  232. - the supplied id_server is not a valid identity server name
  233. - we failed to contact the supplied identity server
  234. Returns:
  235. True on success, otherwise False if the identity
  236. server doesn't support unbinding
  237. """
  238. if not valid_id_server_location(id_server):
  239. raise SynapseError(
  240. 400,
  241. "id_server must be a valid hostname with optional port and path components",
  242. )
  243. url = "https://%s/_matrix/identity/v2/3pid/unbind" % (id_server,)
  244. url_bytes = b"/_matrix/identity/v2/3pid/unbind"
  245. content = {
  246. "mxid": mxid,
  247. "threepid": {"medium": medium, "address": address},
  248. }
  249. # we abuse the federation http client to sign the request, but we have to send it
  250. # using the normal http client since we don't want the SRV lookup and want normal
  251. # 'browser-like' HTTPS.
  252. auth_headers = self.federation_http_client.build_auth_headers(
  253. destination=None,
  254. method=b"POST",
  255. url_bytes=url_bytes,
  256. content=content,
  257. destination_is=id_server.encode("ascii"),
  258. )
  259. headers = {b"Authorization": auth_headers}
  260. try:
  261. # Use the blacklisting http client as this call is only to identity servers
  262. # provided by a client
  263. await self._http_client.post_json_get_json(url, content, headers)
  264. changed = True
  265. except HttpResponseException as e:
  266. changed = False
  267. if e.code in (400, 404, 501):
  268. # The remote server probably doesn't support unbinding (yet)
  269. logger.warning("Received %d response while unbinding threepid", e.code)
  270. else:
  271. logger.error("Failed to unbind threepid on identity server: %s", e)
  272. raise SynapseError(500, "Failed to contact identity server")
  273. except RequestTimedOutError:
  274. raise SynapseError(500, "Timed out contacting identity server")
  275. await self.store.remove_user_bound_threepid(mxid, medium, address, id_server)
  276. return changed
  277. async def send_threepid_validation(
  278. self,
  279. email_address: str,
  280. client_secret: str,
  281. send_attempt: int,
  282. send_email_func: Callable[[str, str, str, str], Awaitable],
  283. next_link: Optional[str] = None,
  284. ) -> str:
  285. """Send a threepid validation email for password reset or
  286. registration purposes
  287. Args:
  288. email_address: The user's email address
  289. client_secret: The provided client secret
  290. send_attempt: Which send attempt this is
  291. send_email_func: A function that takes an email address, token,
  292. client_secret and session_id, sends an email
  293. and returns an Awaitable.
  294. next_link: The URL to redirect the user to after validation
  295. Returns:
  296. The new session_id upon success
  297. Raises:
  298. SynapseError is an error occurred when sending the email
  299. """
  300. # Check that this email/client_secret/send_attempt combo is new or
  301. # greater than what we've seen previously
  302. session = await self.store.get_threepid_validation_session(
  303. "email", client_secret, address=email_address, validated=False
  304. )
  305. # Check to see if a session already exists and that it is not yet
  306. # marked as validated
  307. if session and session.get("validated_at") is None:
  308. session_id = session["session_id"]
  309. last_send_attempt = session["last_send_attempt"]
  310. # Check that the send_attempt is higher than previous attempts
  311. if send_attempt <= last_send_attempt:
  312. # If not, just return a success without sending an email
  313. return session_id
  314. else:
  315. # An non-validated session does not exist yet.
  316. # Generate a session id
  317. session_id = random_string(16)
  318. if next_link:
  319. # Manipulate the next_link to add the sid, because the caller won't get
  320. # it until we send a response, by which time we've sent the mail.
  321. if "?" in next_link:
  322. next_link += "&"
  323. else:
  324. next_link += "?"
  325. next_link += "sid=" + urllib.parse.quote(session_id)
  326. # Generate a new validation token
  327. token = random_string(32)
  328. # Send the mail with the link containing the token, client_secret
  329. # and session_id
  330. try:
  331. await send_email_func(email_address, token, client_secret, session_id)
  332. except Exception:
  333. logger.exception(
  334. "Error sending threepid validation email to %s", email_address
  335. )
  336. raise SynapseError(500, "An error was encountered when sending the email")
  337. token_expires = (
  338. self.hs.get_clock().time_msec()
  339. + self.hs.config.email.email_validation_token_lifetime
  340. )
  341. await self.store.start_or_continue_validation_session(
  342. "email",
  343. email_address,
  344. session_id,
  345. client_secret,
  346. send_attempt,
  347. next_link,
  348. token,
  349. token_expires,
  350. )
  351. return session_id
  352. async def requestMsisdnToken(
  353. self,
  354. id_server: str,
  355. country: str,
  356. phone_number: str,
  357. client_secret: str,
  358. send_attempt: int,
  359. next_link: Optional[str] = None,
  360. ) -> JsonDict:
  361. """
  362. Request an external server send an SMS message on our behalf for the purposes of
  363. threepid validation.
  364. Args:
  365. id_server: The identity server to proxy to
  366. country: The country code of the phone number
  367. phone_number: The number to send the message to
  368. client_secret: The unique client_secret sends by the user
  369. send_attempt: Which attempt this is
  370. next_link: A link to redirect the user to once they submit the token
  371. Returns:
  372. The json response body from the server
  373. """
  374. params = {
  375. "country": country,
  376. "phone_number": phone_number,
  377. "client_secret": client_secret,
  378. "send_attempt": send_attempt,
  379. }
  380. if next_link:
  381. params["next_link"] = next_link
  382. try:
  383. data = await self.http_client.post_json_get_json(
  384. id_server + "/_matrix/identity/api/v1/validate/msisdn/requestToken",
  385. params,
  386. )
  387. except HttpResponseException as e:
  388. logger.info("Proxied requestToken failed: %r", e)
  389. raise e.to_synapse_error()
  390. except RequestTimedOutError:
  391. raise SynapseError(500, "Timed out contacting identity server")
  392. # we need to tell the client to send the token back to us, since it doesn't
  393. # otherwise know where to send it, so add submit_url response parameter
  394. # (see also MSC2078)
  395. data["submit_url"] = (
  396. self.hs.config.server.public_baseurl
  397. + "_matrix/client/unstable/add_threepid/msisdn/submit_token"
  398. )
  399. return data
  400. async def validate_threepid_session(
  401. self, client_secret: str, sid: str
  402. ) -> Optional[JsonDict]:
  403. """Validates a threepid session with only the client secret and session ID
  404. Tries validating against any configured account_threepid_delegates as well as locally.
  405. Args:
  406. client_secret: A secret provided by the client
  407. sid: The ID of the session
  408. Returns:
  409. The json response if validation was successful, otherwise None
  410. """
  411. # XXX: We shouldn't need to keep wrapping and unwrapping this value
  412. threepid_creds = {"client_secret": client_secret, "sid": sid}
  413. # We don't actually know which medium this 3PID is. Thus we first assume it's email,
  414. # and if validation fails we try msisdn
  415. validation_session = None
  416. # Try to validate as email
  417. if self.hs.config.email.can_verify_email:
  418. # Get a validated session matching these details
  419. validation_session = await self.store.get_threepid_validation_session(
  420. "email", client_secret, sid=sid, validated=True
  421. )
  422. if validation_session:
  423. return validation_session
  424. # Try to validate as msisdn
  425. if self.hs.config.registration.account_threepid_delegate_msisdn:
  426. # Ask our delegated msisdn identity server
  427. validation_session = await self.threepid_from_creds(
  428. self.hs.config.registration.account_threepid_delegate_msisdn,
  429. threepid_creds,
  430. )
  431. return validation_session
  432. async def proxy_msisdn_submit_token(
  433. self, id_server: str, client_secret: str, sid: str, token: str
  434. ) -> JsonDict:
  435. """Proxy a POST submitToken request to an identity server for verification purposes
  436. Args:
  437. id_server: The identity server URL to contact
  438. client_secret: Secret provided by the client
  439. sid: The ID of the session
  440. token: The verification token
  441. Raises:
  442. SynapseError: If we failed to contact the identity server
  443. Returns:
  444. The response dict from the identity server
  445. """
  446. body = {"client_secret": client_secret, "sid": sid, "token": token}
  447. try:
  448. return await self.http_client.post_json_get_json(
  449. id_server + "/_matrix/identity/api/v1/validate/msisdn/submitToken",
  450. body,
  451. )
  452. except RequestTimedOutError:
  453. raise SynapseError(500, "Timed out contacting identity server")
  454. except HttpResponseException as e:
  455. logger.warning("Error contacting msisdn account_threepid_delegate: %s", e)
  456. raise SynapseError(400, "Error contacting the identity server")
  457. async def lookup_3pid(
  458. self, id_server: str, medium: str, address: str, id_access_token: str
  459. ) -> Optional[str]:
  460. """Looks up a 3pid in the passed identity server.
  461. Args:
  462. id_server: The server name (including port, if required)
  463. of the identity server to use.
  464. medium: The type of the third party identifier (e.g. "email").
  465. address: The third party identifier (e.g. "foo@example.com").
  466. id_access_token: The access token to authenticate to the identity
  467. server with
  468. Returns:
  469. the matrix ID of the 3pid, or None if it is not recognized.
  470. """
  471. try:
  472. results = await self._lookup_3pid_v2(
  473. id_server, id_access_token, medium, address
  474. )
  475. return results
  476. except Exception as e:
  477. logger.warning("Error when looking up hashing details: %s", e)
  478. return None
  479. async def _lookup_3pid_v2(
  480. self, id_server: str, id_access_token: str, medium: str, address: str
  481. ) -> Optional[str]:
  482. """Looks up a 3pid in the passed identity server using v2 lookup.
  483. Args:
  484. id_server: The server name (including port, if required)
  485. of the identity server to use.
  486. id_access_token: The access token to authenticate to the identity server with
  487. medium: The type of the third party identifier (e.g. "email").
  488. address: The third party identifier (e.g. "foo@example.com").
  489. Returns:
  490. the matrix ID of the 3pid, or None if it is not recognised.
  491. """
  492. # Check what hashing details are supported by this identity server
  493. try:
  494. hash_details = await self._http_client.get_json(
  495. "%s%s/_matrix/identity/v2/hash_details" % (id_server_scheme, id_server),
  496. {"access_token": id_access_token},
  497. )
  498. except RequestTimedOutError:
  499. raise SynapseError(500, "Timed out contacting identity server")
  500. if not isinstance(hash_details, dict):
  501. logger.warning(
  502. "Got non-dict object when checking hash details of %s%s: %s",
  503. id_server_scheme,
  504. id_server,
  505. hash_details,
  506. )
  507. raise SynapseError(
  508. 400,
  509. "Non-dict object from %s%s during v2 hash_details request: %s"
  510. % (id_server_scheme, id_server, hash_details),
  511. )
  512. # Extract information from hash_details
  513. supported_lookup_algorithms = hash_details.get("algorithms")
  514. lookup_pepper = hash_details.get("lookup_pepper")
  515. if (
  516. not supported_lookup_algorithms
  517. or not isinstance(supported_lookup_algorithms, list)
  518. or not lookup_pepper
  519. or not isinstance(lookup_pepper, str)
  520. ):
  521. raise SynapseError(
  522. 400,
  523. "Invalid hash details received from identity server %s%s: %s"
  524. % (id_server_scheme, id_server, hash_details),
  525. )
  526. # Check if any of the supported lookup algorithms are present
  527. if LookupAlgorithm.SHA256 in supported_lookup_algorithms:
  528. # Perform a hashed lookup
  529. lookup_algorithm = LookupAlgorithm.SHA256
  530. # Hash address, medium and the pepper with sha256
  531. to_hash = "%s %s %s" % (address, medium, lookup_pepper)
  532. lookup_value = sha256_and_url_safe_base64(to_hash)
  533. elif LookupAlgorithm.NONE in supported_lookup_algorithms:
  534. # Perform a non-hashed lookup
  535. lookup_algorithm = LookupAlgorithm.NONE
  536. # Combine together plaintext address and medium
  537. lookup_value = "%s %s" % (address, medium)
  538. else:
  539. logger.warning(
  540. "None of the provided lookup algorithms of %s are supported: %s",
  541. id_server,
  542. supported_lookup_algorithms,
  543. )
  544. raise SynapseError(
  545. 400,
  546. "Provided identity server does not support any v2 lookup "
  547. "algorithms that this homeserver supports.",
  548. )
  549. # Authenticate with identity server given the access token from the client
  550. headers = {"Authorization": create_id_access_token_header(id_access_token)}
  551. try:
  552. lookup_results = await self._http_client.post_json_get_json(
  553. "%s%s/_matrix/identity/v2/lookup" % (id_server_scheme, id_server),
  554. {
  555. "addresses": [lookup_value],
  556. "algorithm": lookup_algorithm,
  557. "pepper": lookup_pepper,
  558. },
  559. headers=headers,
  560. )
  561. except RequestTimedOutError:
  562. raise SynapseError(500, "Timed out contacting identity server")
  563. except Exception as e:
  564. logger.warning("Error when performing a v2 3pid lookup: %s", e)
  565. raise SynapseError(
  566. 500, "Unknown error occurred during identity server lookup"
  567. )
  568. # Check for a mapping from what we looked up to an MXID
  569. if "mappings" not in lookup_results or not isinstance(
  570. lookup_results["mappings"], dict
  571. ):
  572. logger.warning("No results from 3pid lookup")
  573. return None
  574. # Return the MXID if it's available, or None otherwise
  575. mxid = lookup_results["mappings"].get(lookup_value)
  576. return mxid
  577. async def ask_id_server_for_third_party_invite(
  578. self,
  579. requester: Requester,
  580. id_server: str,
  581. medium: str,
  582. address: str,
  583. room_id: str,
  584. inviter_user_id: str,
  585. room_alias: str,
  586. room_avatar_url: str,
  587. room_join_rules: str,
  588. room_name: str,
  589. room_type: Optional[str],
  590. inviter_display_name: str,
  591. inviter_avatar_url: str,
  592. id_access_token: str,
  593. ) -> Tuple[str, List[Dict[str, str]], Dict[str, str], str]:
  594. """
  595. Asks an identity server for a third party invite.
  596. Args:
  597. requester
  598. id_server: hostname + optional port for the identity server.
  599. medium: The literal string "email".
  600. address: The third party address being invited.
  601. room_id: The ID of the room to which the user is invited.
  602. inviter_user_id: The user ID of the inviter.
  603. room_alias: An alias for the room, for cosmetic notifications.
  604. room_avatar_url: The URL of the room's avatar, for cosmetic
  605. notifications.
  606. room_join_rules: The join rules of the email (e.g. "public").
  607. room_name: The m.room.name of the room.
  608. room_type: The type of the room from its m.room.create event (e.g "m.space").
  609. inviter_display_name: The current display name of the
  610. inviter.
  611. inviter_avatar_url: The URL of the inviter's avatar.
  612. id_access_token: The access token to authenticate to the identity
  613. server with
  614. Returns:
  615. A tuple containing:
  616. token: The token which must be signed to prove authenticity.
  617. public_keys ([{"public_key": str, "key_validity_url": str}]):
  618. public_key is a base64-encoded ed25519 public key.
  619. fallback_public_key: One element from public_keys.
  620. display_name: A user-friendly name to represent the invited user.
  621. """
  622. invite_config = {
  623. "medium": medium,
  624. "address": address,
  625. "room_id": room_id,
  626. "room_alias": room_alias,
  627. "room_avatar_url": room_avatar_url,
  628. "room_join_rules": room_join_rules,
  629. "room_name": room_name,
  630. "sender": inviter_user_id,
  631. "sender_display_name": inviter_display_name,
  632. "sender_avatar_url": inviter_avatar_url,
  633. }
  634. if room_type is not None:
  635. invite_config["room_type"] = room_type
  636. # If a custom web client location is available, include it in the request.
  637. if self._web_client_location:
  638. invite_config["org.matrix.web_client_location"] = self._web_client_location
  639. # Add the identity service access token to the JSON body and use the v2
  640. # Identity Service endpoints
  641. data = None
  642. key_validity_url = "%s%s/_matrix/identity/v2/pubkey/isvalid" % (
  643. id_server_scheme,
  644. id_server,
  645. )
  646. url = "%s%s/_matrix/identity/v2/store-invite" % (id_server_scheme, id_server)
  647. try:
  648. data = await self._http_client.post_json_get_json(
  649. url,
  650. invite_config,
  651. {"Authorization": create_id_access_token_header(id_access_token)},
  652. )
  653. except RequestTimedOutError:
  654. raise SynapseError(500, "Timed out contacting identity server")
  655. token = data["token"]
  656. public_keys = data.get("public_keys", [])
  657. if "public_key" in data:
  658. fallback_public_key = {
  659. "public_key": data["public_key"],
  660. "key_validity_url": key_validity_url,
  661. }
  662. else:
  663. fallback_public_key = public_keys[0]
  664. if not public_keys:
  665. public_keys.append(fallback_public_key)
  666. display_name = data["display_name"]
  667. return token, public_keys, fallback_public_key, display_name
  668. def create_id_access_token_header(id_access_token: str) -> List[str]:
  669. """Create an Authorization header for passing to SimpleHttpClient as the header value
  670. of an HTTP request.
  671. Args:
  672. id_access_token: An identity server access token.
  673. Returns:
  674. The ascii-encoded bearer token encased in a list.
  675. """
  676. # Prefix with Bearer
  677. bearer_token = "Bearer %s" % id_access_token
  678. # Encode headers to standard ascii
  679. bearer_token.encode("ascii")
  680. # Return as a list as that's how SimpleHttpClient takes header values
  681. return [bearer_token]
  682. class LookupAlgorithm:
  683. """
  684. Supported hashing algorithms when performing a 3PID lookup.
  685. SHA256 - Hashing an (address, medium, pepper) combo with sha256, then url-safe base64
  686. encoding
  687. NONE - Not performing any hashing. Simply sending an (address, medium) combo in plaintext
  688. """
  689. SHA256 = "sha256"
  690. NONE = "none"