server.py 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. # Copyright 2018 New Vector Ltd
  4. # Copyright 2019 The Matrix.org Foundation C.I.C.
  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. import functools
  18. import logging
  19. import re
  20. from typing import Optional, Tuple, Type
  21. import synapse
  22. from synapse.api.errors import Codes, FederationDeniedError, SynapseError
  23. from synapse.api.room_versions import RoomVersions
  24. from synapse.api.urls import (
  25. FEDERATION_UNSTABLE_PREFIX,
  26. FEDERATION_V1_PREFIX,
  27. FEDERATION_V2_PREFIX,
  28. )
  29. from synapse.http.endpoint import parse_and_validate_server_name
  30. from synapse.http.server import JsonResource
  31. from synapse.http.servlet import (
  32. parse_boolean_from_args,
  33. parse_integer_from_args,
  34. parse_json_object_from_request,
  35. parse_string_from_args,
  36. )
  37. from synapse.logging.context import run_in_background
  38. from synapse.logging.opentracing import (
  39. start_active_span,
  40. start_active_span_from_request,
  41. tags,
  42. whitelisted_homeserver,
  43. )
  44. from synapse.server import HomeServer
  45. from synapse.types import ThirdPartyInstanceID, get_domain_from_id
  46. from synapse.util.ratelimitutils import FederationRateLimiter
  47. from synapse.util.versionstring import get_version_string
  48. logger = logging.getLogger(__name__)
  49. class TransportLayerServer(JsonResource):
  50. """Handles incoming federation HTTP requests"""
  51. def __init__(self, hs, servlet_groups=None):
  52. """Initialize the TransportLayerServer
  53. Will by default register all servlets. For custom behaviour, pass in
  54. a list of servlet_groups to register.
  55. Args:
  56. hs (synapse.server.HomeServer): homeserver
  57. servlet_groups (list[str], optional): List of servlet groups to register.
  58. Defaults to ``DEFAULT_SERVLET_GROUPS``.
  59. """
  60. self.hs = hs
  61. self.clock = hs.get_clock()
  62. self.servlet_groups = servlet_groups
  63. super(TransportLayerServer, self).__init__(hs, canonical_json=False)
  64. self.authenticator = Authenticator(hs)
  65. self.ratelimiter = FederationRateLimiter(
  66. self.clock, config=hs.config.rc_federation
  67. )
  68. self.register_servlets()
  69. def register_servlets(self):
  70. register_servlets(
  71. self.hs,
  72. resource=self,
  73. ratelimiter=self.ratelimiter,
  74. authenticator=self.authenticator,
  75. servlet_groups=self.servlet_groups,
  76. )
  77. class AuthenticationError(SynapseError):
  78. """There was a problem authenticating the request"""
  79. pass
  80. class NoAuthenticationError(AuthenticationError):
  81. """The request had no authentication information"""
  82. pass
  83. class Authenticator:
  84. def __init__(self, hs: HomeServer):
  85. self._clock = hs.get_clock()
  86. self.keyring = hs.get_keyring()
  87. self.server_name = hs.hostname
  88. self.store = hs.get_datastore()
  89. self.federation_domain_whitelist = hs.config.federation_domain_whitelist
  90. self.notifier = hs.get_notifier()
  91. self.replication_client = None
  92. if hs.config.worker.worker_app:
  93. self.replication_client = hs.get_tcp_replication()
  94. # A method just so we can pass 'self' as the authenticator to the Servlets
  95. async def authenticate_request(self, request, content):
  96. now = self._clock.time_msec()
  97. json_request = {
  98. "method": request.method.decode("ascii"),
  99. "uri": request.uri.decode("ascii"),
  100. "destination": self.server_name,
  101. "signatures": {},
  102. }
  103. if content is not None:
  104. json_request["content"] = content
  105. origin = None
  106. auth_headers = request.requestHeaders.getRawHeaders(b"Authorization")
  107. if not auth_headers:
  108. raise NoAuthenticationError(
  109. 401, "Missing Authorization headers", Codes.UNAUTHORIZED
  110. )
  111. for auth in auth_headers:
  112. if auth.startswith(b"X-Matrix"):
  113. (origin, key, sig) = _parse_auth_header(auth)
  114. json_request["origin"] = origin
  115. json_request["signatures"].setdefault(origin, {})[key] = sig
  116. if (
  117. self.federation_domain_whitelist is not None
  118. and origin not in self.federation_domain_whitelist
  119. ):
  120. raise FederationDeniedError(origin)
  121. if not json_request["signatures"]:
  122. raise NoAuthenticationError(
  123. 401, "Missing Authorization headers", Codes.UNAUTHORIZED
  124. )
  125. await self.keyring.verify_json_for_server(
  126. origin, json_request, now, "Incoming request"
  127. )
  128. logger.debug("Request from %s", origin)
  129. request.authenticated_entity = origin
  130. # If we get a valid signed request from the other side, its probably
  131. # alive
  132. retry_timings = await self.store.get_destination_retry_timings(origin)
  133. if retry_timings and retry_timings["retry_last_ts"]:
  134. run_in_background(self._reset_retry_timings, origin)
  135. return origin
  136. async def _reset_retry_timings(self, origin):
  137. try:
  138. logger.info("Marking origin %r as up", origin)
  139. await self.store.set_destination_retry_timings(origin, None, 0, 0)
  140. # Inform the relevant places that the remote server is back up.
  141. self.notifier.notify_remote_server_up(origin)
  142. if self.replication_client:
  143. # If we're on a worker we try and inform master about this. The
  144. # replication client doesn't hook into the notifier to avoid
  145. # infinite loops where we send a `REMOTE_SERVER_UP` command to
  146. # master, which then echoes it back to us which in turn pokes
  147. # the notifier.
  148. self.replication_client.send_remote_server_up(origin)
  149. except Exception:
  150. logger.exception("Error resetting retry timings on %s", origin)
  151. def _parse_auth_header(header_bytes):
  152. """Parse an X-Matrix auth header
  153. Args:
  154. header_bytes (bytes): header value
  155. Returns:
  156. Tuple[str, str, str]: origin, key id, signature.
  157. Raises:
  158. AuthenticationError if the header could not be parsed
  159. """
  160. try:
  161. header_str = header_bytes.decode("utf-8")
  162. params = header_str.split(" ")[1].split(",")
  163. param_dict = dict(kv.split("=") for kv in params)
  164. def strip_quotes(value):
  165. if value.startswith('"'):
  166. return value[1:-1]
  167. else:
  168. return value
  169. origin = strip_quotes(param_dict["origin"])
  170. # ensure that the origin is a valid server name
  171. parse_and_validate_server_name(origin)
  172. key = strip_quotes(param_dict["key"])
  173. sig = strip_quotes(param_dict["sig"])
  174. return origin, key, sig
  175. except Exception as e:
  176. logger.warning(
  177. "Error parsing auth header '%s': %s",
  178. header_bytes.decode("ascii", "replace"),
  179. e,
  180. )
  181. raise AuthenticationError(
  182. 400, "Malformed Authorization header", Codes.UNAUTHORIZED
  183. )
  184. class BaseFederationServlet:
  185. """Abstract base class for federation servlet classes.
  186. The servlet object should have a PATH attribute which takes the form of a regexp to
  187. match against the request path (excluding the /federation/v1 prefix).
  188. The servlet should also implement one or more of on_GET, on_POST, on_PUT, to match
  189. the appropriate HTTP method. These methods must be *asynchronous* and have the
  190. signature:
  191. on_<METHOD>(self, origin, content, query, **kwargs)
  192. With arguments:
  193. origin (unicode|None): The authenticated server_name of the calling server,
  194. unless REQUIRE_AUTH is set to False and authentication failed.
  195. content (unicode|None): decoded json body of the request. None if the
  196. request was a GET.
  197. query (dict[bytes, list[bytes]]): Query params from the request. url-decoded
  198. (ie, '+' and '%xx' are decoded) but note that it is *not* utf8-decoded
  199. yet.
  200. **kwargs (dict[unicode, unicode]): the dict mapping keys to path
  201. components as specified in the path match regexp.
  202. Returns:
  203. Optional[Tuple[int, object]]: either (response code, response object) to
  204. return a JSON response, or None if the request has already been handled.
  205. Raises:
  206. SynapseError: to return an error code
  207. Exception: other exceptions will be caught, logged, and a 500 will be
  208. returned.
  209. """
  210. PATH = "" # Overridden in subclasses, the regex to match against the path.
  211. REQUIRE_AUTH = True
  212. PREFIX = FEDERATION_V1_PREFIX # Allows specifying the API version
  213. def __init__(self, handler, authenticator, ratelimiter, server_name):
  214. self.handler = handler
  215. self.authenticator = authenticator
  216. self.ratelimiter = ratelimiter
  217. def _wrap(self, func):
  218. authenticator = self.authenticator
  219. ratelimiter = self.ratelimiter
  220. @functools.wraps(func)
  221. async def new_func(request, *args, **kwargs):
  222. """A callback which can be passed to HttpServer.RegisterPaths
  223. Args:
  224. request (twisted.web.http.Request):
  225. *args: unused?
  226. **kwargs (dict[unicode, unicode]): the dict mapping keys to path
  227. components as specified in the path match regexp.
  228. Returns:
  229. Tuple[int, object]|None: (response code, response object) as returned by
  230. the callback method. None if the request has already been handled.
  231. """
  232. content = None
  233. if request.method in [b"PUT", b"POST"]:
  234. # TODO: Handle other method types? other content types?
  235. content = parse_json_object_from_request(request)
  236. try:
  237. origin = await authenticator.authenticate_request(request, content)
  238. except NoAuthenticationError:
  239. origin = None
  240. if self.REQUIRE_AUTH:
  241. logger.warning(
  242. "authenticate_request failed: missing authentication"
  243. )
  244. raise
  245. except Exception as e:
  246. logger.warning("authenticate_request failed: %s", e)
  247. raise
  248. request_tags = {
  249. "request_id": request.get_request_id(),
  250. tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER,
  251. tags.HTTP_METHOD: request.get_method(),
  252. tags.HTTP_URL: request.get_redacted_uri(),
  253. tags.PEER_HOST_IPV6: request.getClientIP(),
  254. "authenticated_entity": origin,
  255. "servlet_name": request.request_metrics.name,
  256. }
  257. # Only accept the span context if the origin is authenticated
  258. # and whitelisted
  259. if origin and whitelisted_homeserver(origin):
  260. scope = start_active_span_from_request(
  261. request, "incoming-federation-request", tags=request_tags
  262. )
  263. else:
  264. scope = start_active_span(
  265. "incoming-federation-request", tags=request_tags
  266. )
  267. with scope:
  268. if origin:
  269. with ratelimiter.ratelimit(origin) as d:
  270. await d
  271. if request._disconnected:
  272. logger.warning(
  273. "client disconnected before we started processing "
  274. "request"
  275. )
  276. return -1, None
  277. response = await func(
  278. origin, content, request.args, *args, **kwargs
  279. )
  280. else:
  281. response = await func(
  282. origin, content, request.args, *args, **kwargs
  283. )
  284. return response
  285. return new_func
  286. def register(self, server):
  287. pattern = re.compile("^" + self.PREFIX + self.PATH + "$")
  288. for method in ("GET", "PUT", "POST"):
  289. code = getattr(self, "on_%s" % (method), None)
  290. if code is None:
  291. continue
  292. server.register_paths(
  293. method, (pattern,), self._wrap(code), self.__class__.__name__,
  294. )
  295. class FederationSendServlet(BaseFederationServlet):
  296. PATH = "/send/(?P<transaction_id>[^/]*)/?"
  297. def __init__(self, handler, server_name, **kwargs):
  298. super(FederationSendServlet, self).__init__(
  299. handler, server_name=server_name, **kwargs
  300. )
  301. self.server_name = server_name
  302. # This is when someone is trying to send us a bunch of data.
  303. async def on_PUT(self, origin, content, query, transaction_id):
  304. """ Called on PUT /send/<transaction_id>/
  305. Args:
  306. request (twisted.web.http.Request): The HTTP request.
  307. transaction_id (str): The transaction_id associated with this
  308. request. This is *not* None.
  309. Returns:
  310. Tuple of `(code, response)`, where
  311. `response` is a python dict to be converted into JSON that is
  312. used as the response body.
  313. """
  314. # Parse the request
  315. try:
  316. transaction_data = content
  317. logger.debug("Decoded %s: %s", transaction_id, str(transaction_data))
  318. logger.info(
  319. "Received txn %s from %s. (PDUs: %d, EDUs: %d)",
  320. transaction_id,
  321. origin,
  322. len(transaction_data.get("pdus", [])),
  323. len(transaction_data.get("edus", [])),
  324. )
  325. # We should ideally be getting this from the security layer.
  326. # origin = body["origin"]
  327. # Add some extra data to the transaction dict that isn't included
  328. # in the request body.
  329. transaction_data.update(
  330. transaction_id=transaction_id, destination=self.server_name
  331. )
  332. except Exception as e:
  333. logger.exception(e)
  334. return 400, {"error": "Invalid transaction"}
  335. try:
  336. code, response = await self.handler.on_incoming_transaction(
  337. origin, transaction_data
  338. )
  339. except Exception:
  340. logger.exception("on_incoming_transaction failed")
  341. raise
  342. return code, response
  343. class FederationEventServlet(BaseFederationServlet):
  344. PATH = "/event/(?P<event_id>[^/]*)/?"
  345. # This is when someone asks for a data item for a given server data_id pair.
  346. async def on_GET(self, origin, content, query, event_id):
  347. return await self.handler.on_pdu_request(origin, event_id)
  348. class FederationStateV1Servlet(BaseFederationServlet):
  349. PATH = "/state/(?P<context>[^/]*)/?"
  350. # This is when someone asks for all data for a given context.
  351. async def on_GET(self, origin, content, query, context):
  352. return await self.handler.on_context_state_request(
  353. origin,
  354. context,
  355. parse_string_from_args(query, "event_id", None, required=False),
  356. )
  357. class FederationStateIdsServlet(BaseFederationServlet):
  358. PATH = "/state_ids/(?P<room_id>[^/]*)/?"
  359. async def on_GET(self, origin, content, query, room_id):
  360. return await self.handler.on_state_ids_request(
  361. origin,
  362. room_id,
  363. parse_string_from_args(query, "event_id", None, required=True),
  364. )
  365. class FederationBackfillServlet(BaseFederationServlet):
  366. PATH = "/backfill/(?P<context>[^/]*)/?"
  367. async def on_GET(self, origin, content, query, context):
  368. versions = [x.decode("ascii") for x in query[b"v"]]
  369. limit = parse_integer_from_args(query, "limit", None)
  370. if not limit:
  371. return 400, {"error": "Did not include limit param"}
  372. return await self.handler.on_backfill_request(origin, context, versions, limit)
  373. class FederationQueryServlet(BaseFederationServlet):
  374. PATH = "/query/(?P<query_type>[^/]*)"
  375. # This is when we receive a server-server Query
  376. async def on_GET(self, origin, content, query, query_type):
  377. return await self.handler.on_query_request(
  378. query_type,
  379. {k.decode("utf8"): v[0].decode("utf-8") for k, v in query.items()},
  380. )
  381. class FederationMakeJoinServlet(BaseFederationServlet):
  382. PATH = "/make_join/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
  383. async def on_GET(self, origin, _content, query, context, user_id):
  384. """
  385. Args:
  386. origin (unicode): The authenticated server_name of the calling server
  387. _content (None): (GETs don't have bodies)
  388. query (dict[bytes, list[bytes]]): Query params from the request.
  389. **kwargs (dict[unicode, unicode]): the dict mapping keys to path
  390. components as specified in the path match regexp.
  391. Returns:
  392. Tuple[int, object]: (response code, response object)
  393. """
  394. versions = query.get(b"ver")
  395. if versions is not None:
  396. supported_versions = [v.decode("utf-8") for v in versions]
  397. else:
  398. supported_versions = ["1"]
  399. content = await self.handler.on_make_join_request(
  400. origin, context, user_id, supported_versions=supported_versions
  401. )
  402. return 200, content
  403. class FederationMakeLeaveServlet(BaseFederationServlet):
  404. PATH = "/make_leave/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
  405. async def on_GET(self, origin, content, query, context, user_id):
  406. content = await self.handler.on_make_leave_request(origin, context, user_id)
  407. return 200, content
  408. class FederationV1SendLeaveServlet(BaseFederationServlet):
  409. PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  410. async def on_PUT(self, origin, content, query, room_id, event_id):
  411. content = await self.handler.on_send_leave_request(origin, content, room_id)
  412. return 200, (200, content)
  413. class FederationV2SendLeaveServlet(BaseFederationServlet):
  414. PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<event_id>[^/]*)"
  415. PREFIX = FEDERATION_V2_PREFIX
  416. async def on_PUT(self, origin, content, query, room_id, event_id):
  417. content = await self.handler.on_send_leave_request(origin, content, room_id)
  418. return 200, content
  419. class FederationEventAuthServlet(BaseFederationServlet):
  420. PATH = "/event_auth/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  421. async def on_GET(self, origin, content, query, context, event_id):
  422. return await self.handler.on_event_auth(origin, context, event_id)
  423. class FederationV1SendJoinServlet(BaseFederationServlet):
  424. PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  425. async def on_PUT(self, origin, content, query, context, event_id):
  426. # TODO(paul): assert that context/event_id parsed from path actually
  427. # match those given in content
  428. content = await self.handler.on_send_join_request(origin, content, context)
  429. return 200, (200, content)
  430. class FederationV2SendJoinServlet(BaseFederationServlet):
  431. PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  432. PREFIX = FEDERATION_V2_PREFIX
  433. async def on_PUT(self, origin, content, query, context, event_id):
  434. # TODO(paul): assert that context/event_id parsed from path actually
  435. # match those given in content
  436. content = await self.handler.on_send_join_request(origin, content, context)
  437. return 200, content
  438. class FederationV1InviteServlet(BaseFederationServlet):
  439. PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  440. async def on_PUT(self, origin, content, query, context, event_id):
  441. # We don't get a room version, so we have to assume its EITHER v1 or
  442. # v2. This is "fine" as the only difference between V1 and V2 is the
  443. # state resolution algorithm, and we don't use that for processing
  444. # invites
  445. content = await self.handler.on_invite_request(
  446. origin, content, room_version_id=RoomVersions.V1.identifier
  447. )
  448. # V1 federation API is defined to return a content of `[200, {...}]`
  449. # due to a historical bug.
  450. return 200, (200, content)
  451. class FederationV2InviteServlet(BaseFederationServlet):
  452. PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  453. PREFIX = FEDERATION_V2_PREFIX
  454. async def on_PUT(self, origin, content, query, context, event_id):
  455. # TODO(paul): assert that context/event_id parsed from path actually
  456. # match those given in content
  457. room_version = content["room_version"]
  458. event = content["event"]
  459. invite_room_state = content["invite_room_state"]
  460. # Synapse expects invite_room_state to be in unsigned, as it is in v1
  461. # API
  462. event.setdefault("unsigned", {})["invite_room_state"] = invite_room_state
  463. content = await self.handler.on_invite_request(
  464. origin, event, room_version_id=room_version
  465. )
  466. return 200, content
  467. class FederationThirdPartyInviteExchangeServlet(BaseFederationServlet):
  468. PATH = "/exchange_third_party_invite/(?P<room_id>[^/]*)"
  469. async def on_PUT(self, origin, content, query, room_id):
  470. content = await self.handler.on_exchange_third_party_invite_request(
  471. room_id, content
  472. )
  473. return 200, content
  474. class FederationClientKeysQueryServlet(BaseFederationServlet):
  475. PATH = "/user/keys/query"
  476. async def on_POST(self, origin, content, query):
  477. return await self.handler.on_query_client_keys(origin, content)
  478. class FederationUserDevicesQueryServlet(BaseFederationServlet):
  479. PATH = "/user/devices/(?P<user_id>[^/]*)"
  480. async def on_GET(self, origin, content, query, user_id):
  481. return await self.handler.on_query_user_devices(origin, user_id)
  482. class FederationClientKeysClaimServlet(BaseFederationServlet):
  483. PATH = "/user/keys/claim"
  484. async def on_POST(self, origin, content, query):
  485. response = await self.handler.on_claim_client_keys(origin, content)
  486. return 200, response
  487. class FederationGetMissingEventsServlet(BaseFederationServlet):
  488. # TODO(paul): Why does this path alone end with "/?" optional?
  489. PATH = "/get_missing_events/(?P<room_id>[^/]*)/?"
  490. async def on_POST(self, origin, content, query, room_id):
  491. limit = int(content.get("limit", 10))
  492. earliest_events = content.get("earliest_events", [])
  493. latest_events = content.get("latest_events", [])
  494. content = await self.handler.on_get_missing_events(
  495. origin,
  496. room_id=room_id,
  497. earliest_events=earliest_events,
  498. latest_events=latest_events,
  499. limit=limit,
  500. )
  501. return 200, content
  502. class On3pidBindServlet(BaseFederationServlet):
  503. PATH = "/3pid/onbind"
  504. REQUIRE_AUTH = False
  505. async def on_POST(self, origin, content, query):
  506. if "invites" in content:
  507. last_exception = None
  508. for invite in content["invites"]:
  509. try:
  510. if "signed" not in invite or "token" not in invite["signed"]:
  511. message = (
  512. "Rejecting received notification of third-"
  513. "party invite without signed: %s" % (invite,)
  514. )
  515. logger.info(message)
  516. raise SynapseError(400, message)
  517. await self.handler.exchange_third_party_invite(
  518. invite["sender"],
  519. invite["mxid"],
  520. invite["room_id"],
  521. invite["signed"],
  522. )
  523. except Exception as e:
  524. last_exception = e
  525. if last_exception:
  526. raise last_exception
  527. return 200, {}
  528. class OpenIdUserInfo(BaseFederationServlet):
  529. """
  530. Exchange a bearer token for information about a user.
  531. The response format should be compatible with:
  532. http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
  533. GET /openid/userinfo?access_token=ABDEFGH HTTP/1.1
  534. HTTP/1.1 200 OK
  535. Content-Type: application/json
  536. {
  537. "sub": "@userpart:example.org",
  538. }
  539. """
  540. PATH = "/openid/userinfo"
  541. REQUIRE_AUTH = False
  542. async def on_GET(self, origin, content, query):
  543. token = query.get(b"access_token", [None])[0]
  544. if token is None:
  545. return (
  546. 401,
  547. {"errcode": "M_MISSING_TOKEN", "error": "Access Token required"},
  548. )
  549. user_id = await self.handler.on_openid_userinfo(token.decode("ascii"))
  550. if user_id is None:
  551. return (
  552. 401,
  553. {
  554. "errcode": "M_UNKNOWN_TOKEN",
  555. "error": "Access Token unknown or expired",
  556. },
  557. )
  558. return 200, {"sub": user_id}
  559. class PublicRoomList(BaseFederationServlet):
  560. """
  561. Fetch the public room list for this server.
  562. This API returns information in the same format as /publicRooms on the
  563. client API, but will only ever include local public rooms and hence is
  564. intended for consumption by other homeservers.
  565. GET /publicRooms HTTP/1.1
  566. HTTP/1.1 200 OK
  567. Content-Type: application/json
  568. {
  569. "chunk": [
  570. {
  571. "aliases": [
  572. "#test:localhost"
  573. ],
  574. "guest_can_join": false,
  575. "name": "test room",
  576. "num_joined_members": 3,
  577. "room_id": "!whkydVegtvatLfXmPN:localhost",
  578. "world_readable": false
  579. }
  580. ],
  581. "end": "END",
  582. "start": "START"
  583. }
  584. """
  585. PATH = "/publicRooms"
  586. def __init__(self, handler, authenticator, ratelimiter, server_name, allow_access):
  587. super(PublicRoomList, self).__init__(
  588. handler, authenticator, ratelimiter, server_name
  589. )
  590. self.allow_access = allow_access
  591. async def on_GET(self, origin, content, query):
  592. if not self.allow_access:
  593. raise FederationDeniedError(origin)
  594. limit = parse_integer_from_args(query, "limit", 0)
  595. since_token = parse_string_from_args(query, "since", None)
  596. include_all_networks = parse_boolean_from_args(
  597. query, "include_all_networks", False
  598. )
  599. third_party_instance_id = parse_string_from_args(
  600. query, "third_party_instance_id", None
  601. )
  602. if include_all_networks:
  603. network_tuple = None
  604. elif third_party_instance_id:
  605. network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
  606. else:
  607. network_tuple = ThirdPartyInstanceID(None, None)
  608. if limit == 0:
  609. # zero is a special value which corresponds to no limit.
  610. limit = None
  611. data = await self.handler.get_local_public_room_list(
  612. limit, since_token, network_tuple=network_tuple, from_federation=True
  613. )
  614. return 200, data
  615. async def on_POST(self, origin, content, query):
  616. # This implements MSC2197 (Search Filtering over Federation)
  617. if not self.allow_access:
  618. raise FederationDeniedError(origin)
  619. limit = int(content.get("limit", 100)) # type: Optional[int]
  620. since_token = content.get("since", None)
  621. search_filter = content.get("filter", None)
  622. include_all_networks = content.get("include_all_networks", False)
  623. third_party_instance_id = content.get("third_party_instance_id", None)
  624. if include_all_networks:
  625. network_tuple = None
  626. if third_party_instance_id is not None:
  627. raise SynapseError(
  628. 400, "Can't use include_all_networks with an explicit network"
  629. )
  630. elif third_party_instance_id is None:
  631. network_tuple = ThirdPartyInstanceID(None, None)
  632. else:
  633. network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
  634. if search_filter is None:
  635. logger.warning("Nonefilter")
  636. if limit == 0:
  637. # zero is a special value which corresponds to no limit.
  638. limit = None
  639. data = await self.handler.get_local_public_room_list(
  640. limit=limit,
  641. since_token=since_token,
  642. search_filter=search_filter,
  643. network_tuple=network_tuple,
  644. from_federation=True,
  645. )
  646. return 200, data
  647. class FederationVersionServlet(BaseFederationServlet):
  648. PATH = "/version"
  649. REQUIRE_AUTH = False
  650. async def on_GET(self, origin, content, query):
  651. return (
  652. 200,
  653. {"server": {"name": "Synapse", "version": get_version_string(synapse)}},
  654. )
  655. class FederationGroupsProfileServlet(BaseFederationServlet):
  656. """Get/set the basic profile of a group on behalf of a user
  657. """
  658. PATH = "/groups/(?P<group_id>[^/]*)/profile"
  659. async def on_GET(self, origin, content, query, group_id):
  660. requester_user_id = parse_string_from_args(query, "requester_user_id")
  661. if get_domain_from_id(requester_user_id) != origin:
  662. raise SynapseError(403, "requester_user_id doesn't match origin")
  663. new_content = await self.handler.get_group_profile(group_id, requester_user_id)
  664. return 200, new_content
  665. async def on_POST(self, origin, content, query, group_id):
  666. requester_user_id = parse_string_from_args(query, "requester_user_id")
  667. if get_domain_from_id(requester_user_id) != origin:
  668. raise SynapseError(403, "requester_user_id doesn't match origin")
  669. new_content = await self.handler.update_group_profile(
  670. group_id, requester_user_id, content
  671. )
  672. return 200, new_content
  673. class FederationGroupsSummaryServlet(BaseFederationServlet):
  674. PATH = "/groups/(?P<group_id>[^/]*)/summary"
  675. async def on_GET(self, origin, content, query, group_id):
  676. requester_user_id = parse_string_from_args(query, "requester_user_id")
  677. if get_domain_from_id(requester_user_id) != origin:
  678. raise SynapseError(403, "requester_user_id doesn't match origin")
  679. new_content = await self.handler.get_group_summary(group_id, requester_user_id)
  680. return 200, new_content
  681. class FederationGroupsRoomsServlet(BaseFederationServlet):
  682. """Get the rooms in a group on behalf of a user
  683. """
  684. PATH = "/groups/(?P<group_id>[^/]*)/rooms"
  685. async def on_GET(self, origin, content, query, group_id):
  686. requester_user_id = parse_string_from_args(query, "requester_user_id")
  687. if get_domain_from_id(requester_user_id) != origin:
  688. raise SynapseError(403, "requester_user_id doesn't match origin")
  689. new_content = await self.handler.get_rooms_in_group(group_id, requester_user_id)
  690. return 200, new_content
  691. class FederationGroupsAddRoomsServlet(BaseFederationServlet):
  692. """Add/remove room from group
  693. """
  694. PATH = "/groups/(?P<group_id>[^/]*)/room/(?P<room_id>[^/]*)"
  695. async def on_POST(self, origin, content, query, group_id, room_id):
  696. requester_user_id = parse_string_from_args(query, "requester_user_id")
  697. if get_domain_from_id(requester_user_id) != origin:
  698. raise SynapseError(403, "requester_user_id doesn't match origin")
  699. new_content = await self.handler.add_room_to_group(
  700. group_id, requester_user_id, room_id, content
  701. )
  702. return 200, new_content
  703. async def on_DELETE(self, origin, content, query, group_id, room_id):
  704. requester_user_id = parse_string_from_args(query, "requester_user_id")
  705. if get_domain_from_id(requester_user_id) != origin:
  706. raise SynapseError(403, "requester_user_id doesn't match origin")
  707. new_content = await self.handler.remove_room_from_group(
  708. group_id, requester_user_id, room_id
  709. )
  710. return 200, new_content
  711. class FederationGroupsAddRoomsConfigServlet(BaseFederationServlet):
  712. """Update room config in group
  713. """
  714. PATH = (
  715. "/groups/(?P<group_id>[^/]*)/room/(?P<room_id>[^/]*)"
  716. "/config/(?P<config_key>[^/]*)"
  717. )
  718. async def on_POST(self, origin, content, query, group_id, room_id, config_key):
  719. requester_user_id = parse_string_from_args(query, "requester_user_id")
  720. if get_domain_from_id(requester_user_id) != origin:
  721. raise SynapseError(403, "requester_user_id doesn't match origin")
  722. result = await self.handler.update_room_in_group(
  723. group_id, requester_user_id, room_id, config_key, content
  724. )
  725. return 200, result
  726. class FederationGroupsUsersServlet(BaseFederationServlet):
  727. """Get the users in a group on behalf of a user
  728. """
  729. PATH = "/groups/(?P<group_id>[^/]*)/users"
  730. async def on_GET(self, origin, content, query, group_id):
  731. requester_user_id = parse_string_from_args(query, "requester_user_id")
  732. if get_domain_from_id(requester_user_id) != origin:
  733. raise SynapseError(403, "requester_user_id doesn't match origin")
  734. new_content = await self.handler.get_users_in_group(group_id, requester_user_id)
  735. return 200, new_content
  736. class FederationGroupsInvitedUsersServlet(BaseFederationServlet):
  737. """Get the users that have been invited to a group
  738. """
  739. PATH = "/groups/(?P<group_id>[^/]*)/invited_users"
  740. async def on_GET(self, origin, content, query, group_id):
  741. requester_user_id = parse_string_from_args(query, "requester_user_id")
  742. if get_domain_from_id(requester_user_id) != origin:
  743. raise SynapseError(403, "requester_user_id doesn't match origin")
  744. new_content = await self.handler.get_invited_users_in_group(
  745. group_id, requester_user_id
  746. )
  747. return 200, new_content
  748. class FederationGroupsInviteServlet(BaseFederationServlet):
  749. """Ask a group server to invite someone to the group
  750. """
  751. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/invite"
  752. async def on_POST(self, origin, content, query, group_id, user_id):
  753. requester_user_id = parse_string_from_args(query, "requester_user_id")
  754. if get_domain_from_id(requester_user_id) != origin:
  755. raise SynapseError(403, "requester_user_id doesn't match origin")
  756. new_content = await self.handler.invite_to_group(
  757. group_id, user_id, requester_user_id, content
  758. )
  759. return 200, new_content
  760. class FederationGroupsAcceptInviteServlet(BaseFederationServlet):
  761. """Accept an invitation from the group server
  762. """
  763. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/accept_invite"
  764. async def on_POST(self, origin, content, query, group_id, user_id):
  765. if get_domain_from_id(user_id) != origin:
  766. raise SynapseError(403, "user_id doesn't match origin")
  767. new_content = await self.handler.accept_invite(group_id, user_id, content)
  768. return 200, new_content
  769. class FederationGroupsJoinServlet(BaseFederationServlet):
  770. """Attempt to join a group
  771. """
  772. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/join"
  773. async def on_POST(self, origin, content, query, group_id, user_id):
  774. if get_domain_from_id(user_id) != origin:
  775. raise SynapseError(403, "user_id doesn't match origin")
  776. new_content = await self.handler.join_group(group_id, user_id, content)
  777. return 200, new_content
  778. class FederationGroupsRemoveUserServlet(BaseFederationServlet):
  779. """Leave or kick a user from the group
  780. """
  781. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/remove"
  782. async def on_POST(self, origin, content, query, group_id, user_id):
  783. requester_user_id = parse_string_from_args(query, "requester_user_id")
  784. if get_domain_from_id(requester_user_id) != origin:
  785. raise SynapseError(403, "requester_user_id doesn't match origin")
  786. new_content = await self.handler.remove_user_from_group(
  787. group_id, user_id, requester_user_id, content
  788. )
  789. return 200, new_content
  790. class FederationGroupsLocalInviteServlet(BaseFederationServlet):
  791. """A group server has invited a local user
  792. """
  793. PATH = "/groups/local/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/invite"
  794. async def on_POST(self, origin, content, query, group_id, user_id):
  795. if get_domain_from_id(group_id) != origin:
  796. raise SynapseError(403, "group_id doesn't match origin")
  797. new_content = await self.handler.on_invite(group_id, user_id, content)
  798. return 200, new_content
  799. class FederationGroupsRemoveLocalUserServlet(BaseFederationServlet):
  800. """A group server has removed a local user
  801. """
  802. PATH = "/groups/local/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/remove"
  803. async def on_POST(self, origin, content, query, group_id, user_id):
  804. if get_domain_from_id(group_id) != origin:
  805. raise SynapseError(403, "user_id doesn't match origin")
  806. new_content = await self.handler.user_removed_from_group(
  807. group_id, user_id, content
  808. )
  809. return 200, new_content
  810. class FederationGroupsRenewAttestaionServlet(BaseFederationServlet):
  811. """A group or user's server renews their attestation
  812. """
  813. PATH = "/groups/(?P<group_id>[^/]*)/renew_attestation/(?P<user_id>[^/]*)"
  814. async def on_POST(self, origin, content, query, group_id, user_id):
  815. # We don't need to check auth here as we check the attestation signatures
  816. new_content = await self.handler.on_renew_attestation(
  817. group_id, user_id, content
  818. )
  819. return 200, new_content
  820. class FederationGroupsSummaryRoomsServlet(BaseFederationServlet):
  821. """Add/remove a room from the group summary, with optional category.
  822. Matches both:
  823. - /groups/:group/summary/rooms/:room_id
  824. - /groups/:group/summary/categories/:category/rooms/:room_id
  825. """
  826. PATH = (
  827. "/groups/(?P<group_id>[^/]*)/summary"
  828. "(/categories/(?P<category_id>[^/]+))?"
  829. "/rooms/(?P<room_id>[^/]*)"
  830. )
  831. async def on_POST(self, origin, content, query, group_id, category_id, room_id):
  832. requester_user_id = parse_string_from_args(query, "requester_user_id")
  833. if get_domain_from_id(requester_user_id) != origin:
  834. raise SynapseError(403, "requester_user_id doesn't match origin")
  835. if category_id == "":
  836. raise SynapseError(400, "category_id cannot be empty string")
  837. resp = await self.handler.update_group_summary_room(
  838. group_id,
  839. requester_user_id,
  840. room_id=room_id,
  841. category_id=category_id,
  842. content=content,
  843. )
  844. return 200, resp
  845. async def on_DELETE(self, origin, content, query, group_id, category_id, room_id):
  846. requester_user_id = parse_string_from_args(query, "requester_user_id")
  847. if get_domain_from_id(requester_user_id) != origin:
  848. raise SynapseError(403, "requester_user_id doesn't match origin")
  849. if category_id == "":
  850. raise SynapseError(400, "category_id cannot be empty string")
  851. resp = await self.handler.delete_group_summary_room(
  852. group_id, requester_user_id, room_id=room_id, category_id=category_id
  853. )
  854. return 200, resp
  855. class FederationGroupsCategoriesServlet(BaseFederationServlet):
  856. """Get all categories for a group
  857. """
  858. PATH = "/groups/(?P<group_id>[^/]*)/categories/?"
  859. async def on_GET(self, origin, content, query, group_id):
  860. requester_user_id = parse_string_from_args(query, "requester_user_id")
  861. if get_domain_from_id(requester_user_id) != origin:
  862. raise SynapseError(403, "requester_user_id doesn't match origin")
  863. resp = await self.handler.get_group_categories(group_id, requester_user_id)
  864. return 200, resp
  865. class FederationGroupsCategoryServlet(BaseFederationServlet):
  866. """Add/remove/get a category in a group
  867. """
  868. PATH = "/groups/(?P<group_id>[^/]*)/categories/(?P<category_id>[^/]+)"
  869. async def on_GET(self, origin, content, query, group_id, category_id):
  870. requester_user_id = parse_string_from_args(query, "requester_user_id")
  871. if get_domain_from_id(requester_user_id) != origin:
  872. raise SynapseError(403, "requester_user_id doesn't match origin")
  873. resp = await self.handler.get_group_category(
  874. group_id, requester_user_id, category_id
  875. )
  876. return 200, resp
  877. async def on_POST(self, origin, content, query, group_id, category_id):
  878. requester_user_id = parse_string_from_args(query, "requester_user_id")
  879. if get_domain_from_id(requester_user_id) != origin:
  880. raise SynapseError(403, "requester_user_id doesn't match origin")
  881. if category_id == "":
  882. raise SynapseError(400, "category_id cannot be empty string")
  883. resp = await self.handler.upsert_group_category(
  884. group_id, requester_user_id, category_id, content
  885. )
  886. return 200, resp
  887. async def on_DELETE(self, origin, content, query, group_id, category_id):
  888. requester_user_id = parse_string_from_args(query, "requester_user_id")
  889. if get_domain_from_id(requester_user_id) != origin:
  890. raise SynapseError(403, "requester_user_id doesn't match origin")
  891. if category_id == "":
  892. raise SynapseError(400, "category_id cannot be empty string")
  893. resp = await self.handler.delete_group_category(
  894. group_id, requester_user_id, category_id
  895. )
  896. return 200, resp
  897. class FederationGroupsRolesServlet(BaseFederationServlet):
  898. """Get roles in a group
  899. """
  900. PATH = "/groups/(?P<group_id>[^/]*)/roles/?"
  901. async def on_GET(self, origin, content, query, group_id):
  902. requester_user_id = parse_string_from_args(query, "requester_user_id")
  903. if get_domain_from_id(requester_user_id) != origin:
  904. raise SynapseError(403, "requester_user_id doesn't match origin")
  905. resp = await self.handler.get_group_roles(group_id, requester_user_id)
  906. return 200, resp
  907. class FederationGroupsRoleServlet(BaseFederationServlet):
  908. """Add/remove/get a role in a group
  909. """
  910. PATH = "/groups/(?P<group_id>[^/]*)/roles/(?P<role_id>[^/]+)"
  911. async def on_GET(self, origin, content, query, group_id, role_id):
  912. requester_user_id = parse_string_from_args(query, "requester_user_id")
  913. if get_domain_from_id(requester_user_id) != origin:
  914. raise SynapseError(403, "requester_user_id doesn't match origin")
  915. resp = await self.handler.get_group_role(group_id, requester_user_id, role_id)
  916. return 200, resp
  917. async def on_POST(self, origin, content, query, group_id, role_id):
  918. requester_user_id = parse_string_from_args(query, "requester_user_id")
  919. if get_domain_from_id(requester_user_id) != origin:
  920. raise SynapseError(403, "requester_user_id doesn't match origin")
  921. if role_id == "":
  922. raise SynapseError(400, "role_id cannot be empty string")
  923. resp = await self.handler.update_group_role(
  924. group_id, requester_user_id, role_id, content
  925. )
  926. return 200, resp
  927. async def on_DELETE(self, origin, content, query, group_id, role_id):
  928. requester_user_id = parse_string_from_args(query, "requester_user_id")
  929. if get_domain_from_id(requester_user_id) != origin:
  930. raise SynapseError(403, "requester_user_id doesn't match origin")
  931. if role_id == "":
  932. raise SynapseError(400, "role_id cannot be empty string")
  933. resp = await self.handler.delete_group_role(
  934. group_id, requester_user_id, role_id
  935. )
  936. return 200, resp
  937. class FederationGroupsSummaryUsersServlet(BaseFederationServlet):
  938. """Add/remove a user from the group summary, with optional role.
  939. Matches both:
  940. - /groups/:group/summary/users/:user_id
  941. - /groups/:group/summary/roles/:role/users/:user_id
  942. """
  943. PATH = (
  944. "/groups/(?P<group_id>[^/]*)/summary"
  945. "(/roles/(?P<role_id>[^/]+))?"
  946. "/users/(?P<user_id>[^/]*)"
  947. )
  948. async def on_POST(self, origin, content, query, group_id, role_id, user_id):
  949. requester_user_id = parse_string_from_args(query, "requester_user_id")
  950. if get_domain_from_id(requester_user_id) != origin:
  951. raise SynapseError(403, "requester_user_id doesn't match origin")
  952. if role_id == "":
  953. raise SynapseError(400, "role_id cannot be empty string")
  954. resp = await self.handler.update_group_summary_user(
  955. group_id,
  956. requester_user_id,
  957. user_id=user_id,
  958. role_id=role_id,
  959. content=content,
  960. )
  961. return 200, resp
  962. async def on_DELETE(self, origin, content, query, group_id, role_id, user_id):
  963. requester_user_id = parse_string_from_args(query, "requester_user_id")
  964. if get_domain_from_id(requester_user_id) != origin:
  965. raise SynapseError(403, "requester_user_id doesn't match origin")
  966. if role_id == "":
  967. raise SynapseError(400, "role_id cannot be empty string")
  968. resp = await self.handler.delete_group_summary_user(
  969. group_id, requester_user_id, user_id=user_id, role_id=role_id
  970. )
  971. return 200, resp
  972. class FederationGroupsBulkPublicisedServlet(BaseFederationServlet):
  973. """Get roles in a group
  974. """
  975. PATH = "/get_groups_publicised"
  976. async def on_POST(self, origin, content, query):
  977. resp = await self.handler.bulk_get_publicised_groups(
  978. content["user_ids"], proxy=False
  979. )
  980. return 200, resp
  981. class FederationGroupsSettingJoinPolicyServlet(BaseFederationServlet):
  982. """Sets whether a group is joinable without an invite or knock
  983. """
  984. PATH = "/groups/(?P<group_id>[^/]*)/settings/m.join_policy"
  985. async def on_PUT(self, origin, content, query, group_id):
  986. requester_user_id = parse_string_from_args(query, "requester_user_id")
  987. if get_domain_from_id(requester_user_id) != origin:
  988. raise SynapseError(403, "requester_user_id doesn't match origin")
  989. new_content = await self.handler.set_group_join_policy(
  990. group_id, requester_user_id, content
  991. )
  992. return 200, new_content
  993. class RoomComplexityServlet(BaseFederationServlet):
  994. """
  995. Indicates to other servers how complex (and therefore likely
  996. resource-intensive) a public room this server knows about is.
  997. """
  998. PATH = "/rooms/(?P<room_id>[^/]*)/complexity"
  999. PREFIX = FEDERATION_UNSTABLE_PREFIX
  1000. async def on_GET(self, origin, content, query, room_id):
  1001. store = self.handler.hs.get_datastore()
  1002. is_public = await store.is_room_world_readable_or_publicly_joinable(room_id)
  1003. if not is_public:
  1004. raise SynapseError(404, "Room not found", errcode=Codes.INVALID_PARAM)
  1005. complexity = await store.get_room_complexity(room_id)
  1006. return 200, complexity
  1007. FEDERATION_SERVLET_CLASSES = (
  1008. FederationSendServlet,
  1009. FederationEventServlet,
  1010. FederationStateV1Servlet,
  1011. FederationStateIdsServlet,
  1012. FederationBackfillServlet,
  1013. FederationQueryServlet,
  1014. FederationMakeJoinServlet,
  1015. FederationMakeLeaveServlet,
  1016. FederationEventServlet,
  1017. FederationV1SendJoinServlet,
  1018. FederationV2SendJoinServlet,
  1019. FederationV1SendLeaveServlet,
  1020. FederationV2SendLeaveServlet,
  1021. FederationV1InviteServlet,
  1022. FederationV2InviteServlet,
  1023. FederationGetMissingEventsServlet,
  1024. FederationEventAuthServlet,
  1025. FederationClientKeysQueryServlet,
  1026. FederationUserDevicesQueryServlet,
  1027. FederationClientKeysClaimServlet,
  1028. FederationThirdPartyInviteExchangeServlet,
  1029. On3pidBindServlet,
  1030. FederationVersionServlet,
  1031. RoomComplexityServlet,
  1032. ) # type: Tuple[Type[BaseFederationServlet], ...]
  1033. OPENID_SERVLET_CLASSES = (
  1034. OpenIdUserInfo,
  1035. ) # type: Tuple[Type[BaseFederationServlet], ...]
  1036. ROOM_LIST_CLASSES = (PublicRoomList,) # type: Tuple[Type[PublicRoomList], ...]
  1037. GROUP_SERVER_SERVLET_CLASSES = (
  1038. FederationGroupsProfileServlet,
  1039. FederationGroupsSummaryServlet,
  1040. FederationGroupsRoomsServlet,
  1041. FederationGroupsUsersServlet,
  1042. FederationGroupsInvitedUsersServlet,
  1043. FederationGroupsInviteServlet,
  1044. FederationGroupsAcceptInviteServlet,
  1045. FederationGroupsJoinServlet,
  1046. FederationGroupsRemoveUserServlet,
  1047. FederationGroupsSummaryRoomsServlet,
  1048. FederationGroupsCategoriesServlet,
  1049. FederationGroupsCategoryServlet,
  1050. FederationGroupsRolesServlet,
  1051. FederationGroupsRoleServlet,
  1052. FederationGroupsSummaryUsersServlet,
  1053. FederationGroupsAddRoomsServlet,
  1054. FederationGroupsAddRoomsConfigServlet,
  1055. FederationGroupsSettingJoinPolicyServlet,
  1056. ) # type: Tuple[Type[BaseFederationServlet], ...]
  1057. GROUP_LOCAL_SERVLET_CLASSES = (
  1058. FederationGroupsLocalInviteServlet,
  1059. FederationGroupsRemoveLocalUserServlet,
  1060. FederationGroupsBulkPublicisedServlet,
  1061. ) # type: Tuple[Type[BaseFederationServlet], ...]
  1062. GROUP_ATTESTATION_SERVLET_CLASSES = (
  1063. FederationGroupsRenewAttestaionServlet,
  1064. ) # type: Tuple[Type[BaseFederationServlet], ...]
  1065. DEFAULT_SERVLET_GROUPS = (
  1066. "federation",
  1067. "room_list",
  1068. "group_server",
  1069. "group_local",
  1070. "group_attestation",
  1071. "openid",
  1072. )
  1073. def register_servlets(hs, resource, authenticator, ratelimiter, servlet_groups=None):
  1074. """Initialize and register servlet classes.
  1075. Will by default register all servlets. For custom behaviour, pass in
  1076. a list of servlet_groups to register.
  1077. Args:
  1078. hs (synapse.server.HomeServer): homeserver
  1079. resource (TransportLayerServer): resource class to register to
  1080. authenticator (Authenticator): authenticator to use
  1081. ratelimiter (util.ratelimitutils.FederationRateLimiter): ratelimiter to use
  1082. servlet_groups (list[str], optional): List of servlet groups to register.
  1083. Defaults to ``DEFAULT_SERVLET_GROUPS``.
  1084. """
  1085. if not servlet_groups:
  1086. servlet_groups = DEFAULT_SERVLET_GROUPS
  1087. if "federation" in servlet_groups:
  1088. for servletclass in FEDERATION_SERVLET_CLASSES:
  1089. servletclass(
  1090. handler=hs.get_federation_server(),
  1091. authenticator=authenticator,
  1092. ratelimiter=ratelimiter,
  1093. server_name=hs.hostname,
  1094. ).register(resource)
  1095. if "openid" in servlet_groups:
  1096. for servletclass in OPENID_SERVLET_CLASSES:
  1097. servletclass(
  1098. handler=hs.get_federation_server(),
  1099. authenticator=authenticator,
  1100. ratelimiter=ratelimiter,
  1101. server_name=hs.hostname,
  1102. ).register(resource)
  1103. if "room_list" in servlet_groups:
  1104. for servletclass in ROOM_LIST_CLASSES:
  1105. servletclass(
  1106. handler=hs.get_room_list_handler(),
  1107. authenticator=authenticator,
  1108. ratelimiter=ratelimiter,
  1109. server_name=hs.hostname,
  1110. allow_access=hs.config.allow_public_rooms_over_federation,
  1111. ).register(resource)
  1112. if "group_server" in servlet_groups:
  1113. for servletclass in GROUP_SERVER_SERVLET_CLASSES:
  1114. servletclass(
  1115. handler=hs.get_groups_server_handler(),
  1116. authenticator=authenticator,
  1117. ratelimiter=ratelimiter,
  1118. server_name=hs.hostname,
  1119. ).register(resource)
  1120. if "group_local" in servlet_groups:
  1121. for servletclass in GROUP_LOCAL_SERVLET_CLASSES:
  1122. servletclass(
  1123. handler=hs.get_groups_local_handler(),
  1124. authenticator=authenticator,
  1125. ratelimiter=ratelimiter,
  1126. server_name=hs.hostname,
  1127. ).register(resource)
  1128. if "group_attestation" in servlet_groups:
  1129. for servletclass in GROUP_ATTESTATION_SERVLET_CLASSES:
  1130. servletclass(
  1131. handler=hs.get_groups_attestation_renewer(),
  1132. authenticator=authenticator,
  1133. ratelimiter=ratelimiter,
  1134. server_name=hs.hostname,
  1135. ).register(resource)