Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

806 linhas
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. import attr
  21. from synapse.api.errors import (
  22. CodeMessageException,
  23. Codes,
  24. HttpResponseException,
  25. SynapseError,
  26. )
  27. from synapse.api.ratelimiting import Ratelimiter
  28. from synapse.http import RequestTimedOutError
  29. from synapse.http.client import SimpleHttpClient
  30. from synapse.http.site import SynapseRequest
  31. from synapse.types import JsonDict, Requester
  32. from synapse.util import json_decoder
  33. from synapse.util.hash import sha256_and_url_safe_base64
  34. from synapse.util.stringutils import (
  35. assert_valid_client_secret,
  36. random_string,
  37. valid_id_server_location,
  38. )
  39. if TYPE_CHECKING:
  40. from synapse.server import HomeServer
  41. logger = logging.getLogger(__name__)
  42. id_server_scheme = "https://"
  43. class IdentityHandler:
  44. def __init__(self, hs: "HomeServer"):
  45. self.store = hs.get_datastores().main
  46. # An HTTP client for contacting trusted URLs.
  47. self.http_client = SimpleHttpClient(hs)
  48. # An HTTP client for contacting identity servers specified by clients.
  49. self._http_client = SimpleHttpClient(
  50. hs,
  51. ip_blocklist=hs.config.server.federation_ip_range_blocklist,
  52. ip_allowlist=hs.config.server.federation_ip_range_allowlist,
  53. )
  54. self.federation_http_client = hs.get_federation_http_client()
  55. self.hs = hs
  56. self._web_client_location = hs.config.email.invite_client_location
  57. # Ratelimiters for `/requestToken` endpoints.
  58. self._3pid_validation_ratelimiter_ip = Ratelimiter(
  59. store=self.store,
  60. clock=hs.get_clock(),
  61. cfg=hs.config.ratelimiting.rc_3pid_validation,
  62. )
  63. self._3pid_validation_ratelimiter_address = Ratelimiter(
  64. store=self.store,
  65. clock=hs.get_clock(),
  66. cfg=hs.config.ratelimiting.rc_3pid_validation,
  67. )
  68. async def ratelimit_request_token_requests(
  69. self,
  70. request: SynapseRequest,
  71. medium: str,
  72. address: str,
  73. ) -> None:
  74. """Used to ratelimit requests to `/requestToken` by IP and address.
  75. Args:
  76. request: The associated request
  77. medium: The type of threepid, e.g. "msisdn" or "email"
  78. address: The actual threepid ID, e.g. the phone number or email address
  79. """
  80. await self._3pid_validation_ratelimiter_ip.ratelimit(
  81. None, (medium, request.getClientAddress().host)
  82. )
  83. await self._3pid_validation_ratelimiter_address.ratelimit(
  84. None, (medium, address)
  85. )
  86. async def threepid_from_creds(
  87. self, id_server: str, creds: Dict[str, str]
  88. ) -> Optional[JsonDict]:
  89. """
  90. Retrieve and validate a threepid identifier from a "credentials" dictionary against a
  91. given identity server
  92. Args:
  93. id_server: The identity server to validate 3PIDs against. Must be a
  94. complete URL including the protocol (http(s)://)
  95. creds: Dictionary containing the following keys:
  96. * client_secret|clientSecret: A unique secret str provided by the client
  97. * sid: The ID of the validation session
  98. Returns:
  99. A dictionary consisting of response params to the /getValidated3pid
  100. endpoint of the Identity Service API, or None if the threepid was not found
  101. """
  102. client_secret = creds.get("client_secret") or creds.get("clientSecret")
  103. if not client_secret:
  104. raise SynapseError(
  105. 400, "Missing param client_secret in creds", errcode=Codes.MISSING_PARAM
  106. )
  107. assert_valid_client_secret(client_secret)
  108. session_id = creds.get("sid")
  109. if not session_id:
  110. raise SynapseError(
  111. 400, "Missing param session_id in creds", errcode=Codes.MISSING_PARAM
  112. )
  113. query_params = {"sid": session_id, "client_secret": client_secret}
  114. url = id_server + "/_matrix/identity/api/v1/3pid/getValidated3pid"
  115. try:
  116. data = await self.http_client.get_json(url, query_params)
  117. except RequestTimedOutError:
  118. raise SynapseError(500, "Timed out contacting identity server")
  119. except HttpResponseException as e:
  120. logger.info(
  121. "%s returned %i for threepid validation for: %s",
  122. id_server,
  123. e.code,
  124. creds,
  125. )
  126. return None
  127. # Old versions of Sydent return a 200 http code even on a failed validation
  128. # check. Thus, in addition to the HttpResponseException check above (which
  129. # checks for non-200 errors), we need to make sure validation_session isn't
  130. # actually an error, identified by the absence of a "medium" key
  131. # See https://github.com/matrix-org/sydent/issues/215 for details
  132. if "medium" in data:
  133. return data
  134. logger.info("%s reported non-validated threepid: %s", id_server, creds)
  135. return None
  136. async def bind_threepid(
  137. self,
  138. client_secret: str,
  139. sid: str,
  140. mxid: str,
  141. id_server: str,
  142. id_access_token: str,
  143. ) -> JsonDict:
  144. """Bind a 3PID to an identity server
  145. Args:
  146. client_secret: A unique secret provided by the client
  147. sid: The ID of the validation session
  148. mxid: The MXID to bind the 3PID to
  149. id_server: The domain of the identity server to query
  150. id_access_token: The access token to authenticate to the identity
  151. server with
  152. Raises:
  153. SynapseError: On any of the following conditions
  154. - the supplied id_server is not a valid identity server name
  155. - we failed to contact the supplied identity server
  156. Returns:
  157. The response from the identity server
  158. """
  159. logger.debug("Proxying threepid bind request for %s to %s", mxid, id_server)
  160. if not valid_id_server_location(id_server):
  161. raise SynapseError(
  162. 400,
  163. "id_server must be a valid hostname with optional port and path components",
  164. )
  165. bind_data = {"sid": sid, "client_secret": client_secret, "mxid": mxid}
  166. bind_url = "https://%s/_matrix/identity/v2/3pid/bind" % (id_server,)
  167. headers = {"Authorization": create_id_access_token_header(id_access_token)}
  168. try:
  169. # Use the blacklisting http client as this call is only to identity servers
  170. # provided by a client
  171. data = await self._http_client.post_json_get_json(
  172. bind_url, bind_data, headers=headers
  173. )
  174. # Remember where we bound the threepid
  175. await self.store.add_user_bound_threepid(
  176. user_id=mxid,
  177. medium=data["medium"],
  178. address=data["address"],
  179. id_server=id_server,
  180. )
  181. return data
  182. except HttpResponseException as e:
  183. logger.error("3PID bind failed with Matrix error: %r", e)
  184. raise e.to_synapse_error()
  185. except RequestTimedOutError:
  186. raise SynapseError(500, "Timed out contacting identity server")
  187. except CodeMessageException as e:
  188. data = json_decoder.decode(e.msg) # XXX WAT?
  189. return data
  190. async def try_unbind_threepid(
  191. self, mxid: str, medium: str, address: str, id_server: Optional[str]
  192. ) -> bool:
  193. """Attempt to remove a 3PID from one or more identity servers.
  194. Args:
  195. mxid: Matrix user ID of binding to be removed
  196. medium: The medium of the third-party ID.
  197. address: The address of the third-party ID.
  198. id_server: An identity server to attempt to unbind from. If None,
  199. attempt to remove the association from all identity servers
  200. known to potentially have it.
  201. Raises:
  202. SynapseError: If we failed to contact one or more identity servers.
  203. Returns:
  204. True on success, otherwise False if the identity server doesn't
  205. support unbinding (or no identity server to contact was found).
  206. """
  207. if id_server:
  208. id_servers = [id_server]
  209. else:
  210. id_servers = await self.store.get_id_servers_user_bound(
  211. mxid, medium, address
  212. )
  213. # We don't know where to unbind, so we don't have a choice but to return
  214. if not id_servers:
  215. return False
  216. changed = True
  217. for id_server in id_servers:
  218. changed &= await self._try_unbind_threepid_with_id_server(
  219. mxid, medium, address, id_server
  220. )
  221. return changed
  222. async def _try_unbind_threepid_with_id_server(
  223. self, mxid: str, medium: str, address: str, id_server: str
  224. ) -> bool:
  225. """Removes a binding from an identity server
  226. Args:
  227. mxid: Matrix user ID of binding to be removed
  228. medium: The medium of the third-party ID
  229. address: The address of the third-party ID
  230. id_server: Identity server to unbind from
  231. Raises:
  232. SynapseError: On any of the following conditions
  233. - the supplied id_server is not a valid identity server name
  234. - we failed to contact the supplied identity server
  235. Returns:
  236. True on success, otherwise False if the identity
  237. server doesn't support unbinding
  238. """
  239. if not valid_id_server_location(id_server):
  240. raise SynapseError(
  241. 400,
  242. "id_server must be a valid hostname with optional port and path components",
  243. )
  244. url = "https://%s/_matrix/identity/v2/3pid/unbind" % (id_server,)
  245. url_bytes = b"/_matrix/identity/v2/3pid/unbind"
  246. content = {
  247. "mxid": mxid,
  248. "threepid": {"medium": medium, "address": address},
  249. }
  250. # we abuse the federation http client to sign the request, but we have to send it
  251. # using the normal http client since we don't want the SRV lookup and want normal
  252. # 'browser-like' HTTPS.
  253. auth_headers = self.federation_http_client.build_auth_headers(
  254. destination=None,
  255. method=b"POST",
  256. url_bytes=url_bytes,
  257. content=content,
  258. destination_is=id_server.encode("ascii"),
  259. )
  260. headers = {b"Authorization": auth_headers}
  261. try:
  262. # Use the blacklisting http client as this call is only to identity servers
  263. # provided by a client
  264. await self._http_client.post_json_get_json(url, content, headers)
  265. changed = True
  266. except HttpResponseException as e:
  267. changed = False
  268. if e.code in (400, 404, 501):
  269. # The remote server probably doesn't support unbinding (yet)
  270. logger.warning("Received %d response while unbinding threepid", e.code)
  271. else:
  272. logger.error("Failed to unbind threepid on identity server: %s", e)
  273. raise SynapseError(500, "Failed to contact identity server")
  274. except RequestTimedOutError:
  275. raise SynapseError(500, "Timed out contacting identity server")
  276. await self.store.remove_user_bound_threepid(mxid, medium, address, id_server)
  277. return changed
  278. async def send_threepid_validation(
  279. self,
  280. email_address: str,
  281. client_secret: str,
  282. send_attempt: int,
  283. send_email_func: Callable[[str, str, str, str], Awaitable],
  284. next_link: Optional[str] = None,
  285. ) -> str:
  286. """Send a threepid validation email for password reset or
  287. registration purposes
  288. Args:
  289. email_address: The user's email address
  290. client_secret: The provided client secret
  291. send_attempt: Which send attempt this is
  292. send_email_func: A function that takes an email address, token,
  293. client_secret and session_id, sends an email
  294. and returns an Awaitable.
  295. next_link: The URL to redirect the user to after validation
  296. Returns:
  297. The new session_id upon success
  298. Raises:
  299. SynapseError is an error occurred when sending the email
  300. """
  301. # Check that this email/client_secret/send_attempt combo is new or
  302. # greater than what we've seen previously
  303. session = await self.store.get_threepid_validation_session(
  304. "email", client_secret, address=email_address, validated=False
  305. )
  306. # Check to see if a session already exists and that it is not yet
  307. # marked as validated
  308. if session and session.validated_at is None:
  309. session_id = session.session_id
  310. last_send_attempt = session.last_send_attempt
  311. # Check that the send_attempt is higher than previous attempts
  312. if send_attempt <= last_send_attempt:
  313. # If not, just return a success without sending an email
  314. return session_id
  315. else:
  316. # An non-validated session does not exist yet.
  317. # Generate a session id
  318. session_id = random_string(16)
  319. if next_link:
  320. # Manipulate the next_link to add the sid, because the caller won't get
  321. # it until we send a response, by which time we've sent the mail.
  322. if "?" in next_link:
  323. next_link += "&"
  324. else:
  325. next_link += "?"
  326. next_link += "sid=" + urllib.parse.quote(session_id)
  327. # Generate a new validation token
  328. token = random_string(32)
  329. # Send the mail with the link containing the token, client_secret
  330. # and session_id
  331. try:
  332. await send_email_func(email_address, token, client_secret, session_id)
  333. except Exception:
  334. logger.exception(
  335. "Error sending threepid validation email to %s", email_address
  336. )
  337. raise SynapseError(500, "An error was encountered when sending the email")
  338. token_expires = (
  339. self.hs.get_clock().time_msec()
  340. + self.hs.config.email.email_validation_token_lifetime
  341. )
  342. await self.store.start_or_continue_validation_session(
  343. "email",
  344. email_address,
  345. session_id,
  346. client_secret,
  347. send_attempt,
  348. next_link,
  349. token,
  350. token_expires,
  351. )
  352. return session_id
  353. async def requestMsisdnToken(
  354. self,
  355. id_server: str,
  356. country: str,
  357. phone_number: str,
  358. client_secret: str,
  359. send_attempt: int,
  360. next_link: Optional[str] = None,
  361. ) -> JsonDict:
  362. """
  363. Request an external server send an SMS message on our behalf for the purposes of
  364. threepid validation.
  365. Args:
  366. id_server: The identity server to proxy to
  367. country: The country code of the phone number
  368. phone_number: The number to send the message to
  369. client_secret: The unique client_secret sends by the user
  370. send_attempt: Which attempt this is
  371. next_link: A link to redirect the user to once they submit the token
  372. Returns:
  373. The json response body from the server
  374. """
  375. params = {
  376. "country": country,
  377. "phone_number": phone_number,
  378. "client_secret": client_secret,
  379. "send_attempt": send_attempt,
  380. }
  381. if next_link:
  382. params["next_link"] = next_link
  383. try:
  384. data = await self.http_client.post_json_get_json(
  385. id_server + "/_matrix/identity/api/v1/validate/msisdn/requestToken",
  386. params,
  387. )
  388. except HttpResponseException as e:
  389. logger.info("Proxied requestToken failed: %r", e)
  390. raise e.to_synapse_error()
  391. except RequestTimedOutError:
  392. raise SynapseError(500, "Timed out contacting identity server")
  393. # we need to tell the client to send the token back to us, since it doesn't
  394. # otherwise know where to send it, so add submit_url response parameter
  395. # (see also MSC2078)
  396. data["submit_url"] = (
  397. self.hs.config.server.public_baseurl
  398. + "_matrix/client/unstable/add_threepid/msisdn/submit_token"
  399. )
  400. return data
  401. async def validate_threepid_session(
  402. self, client_secret: str, sid: str
  403. ) -> Optional[JsonDict]:
  404. """Validates a threepid session with only the client secret and session ID
  405. Tries validating against any configured account_threepid_delegates as well as locally.
  406. Args:
  407. client_secret: A secret provided by the client
  408. sid: The ID of the session
  409. Returns:
  410. The json response if validation was successful, otherwise None
  411. """
  412. # XXX: We shouldn't need to keep wrapping and unwrapping this value
  413. threepid_creds = {"client_secret": client_secret, "sid": sid}
  414. # We don't actually know which medium this 3PID is. Thus we first assume it's email,
  415. # and if validation fails we try msisdn
  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 attr.asdict(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. return await self.threepid_from_creds(
  428. self.hs.config.registration.account_threepid_delegate_msisdn,
  429. threepid_creds,
  430. )
  431. return None
  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"