server.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket 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. import functools
  17. import logging
  18. import re
  19. from twisted.internet import defer
  20. import synapse
  21. from synapse.api.errors import Codes, FederationDeniedError, SynapseError
  22. from synapse.api.urls import FEDERATION_PREFIX as PREFIX
  23. from synapse.http.endpoint import parse_and_validate_server_name
  24. from synapse.http.server import JsonResource
  25. from synapse.http.servlet import (
  26. parse_boolean_from_args,
  27. parse_integer_from_args,
  28. parse_json_object_from_request,
  29. parse_string_from_args,
  30. )
  31. from synapse.types import ThirdPartyInstanceID, get_domain_from_id
  32. from synapse.util.logcontext import run_in_background
  33. from synapse.util.ratelimitutils import FederationRateLimiter
  34. from synapse.util.versionstring import get_version_string
  35. logger = logging.getLogger(__name__)
  36. class TransportLayerServer(JsonResource):
  37. """Handles incoming federation HTTP requests"""
  38. def __init__(self, hs):
  39. self.hs = hs
  40. self.clock = hs.get_clock()
  41. super(TransportLayerServer, self).__init__(hs, canonical_json=False)
  42. self.authenticator = Authenticator(hs)
  43. self.ratelimiter = FederationRateLimiter(
  44. self.clock,
  45. window_size=hs.config.federation_rc_window_size,
  46. sleep_limit=hs.config.federation_rc_sleep_limit,
  47. sleep_msec=hs.config.federation_rc_sleep_delay,
  48. reject_limit=hs.config.federation_rc_reject_limit,
  49. concurrent_requests=hs.config.federation_rc_concurrent,
  50. )
  51. self.register_servlets()
  52. def register_servlets(self):
  53. register_servlets(
  54. self.hs,
  55. resource=self,
  56. ratelimiter=self.ratelimiter,
  57. authenticator=self.authenticator,
  58. )
  59. class AuthenticationError(SynapseError):
  60. """There was a problem authenticating the request"""
  61. pass
  62. class NoAuthenticationError(AuthenticationError):
  63. """The request had no authentication information"""
  64. pass
  65. class Authenticator(object):
  66. def __init__(self, hs):
  67. self.keyring = hs.get_keyring()
  68. self.server_name = hs.hostname
  69. self.store = hs.get_datastore()
  70. self.federation_domain_whitelist = hs.config.federation_domain_whitelist
  71. # A method just so we can pass 'self' as the authenticator to the Servlets
  72. @defer.inlineCallbacks
  73. def authenticate_request(self, request, content):
  74. json_request = {
  75. "method": request.method,
  76. "uri": request.uri,
  77. "destination": self.server_name,
  78. "signatures": {},
  79. }
  80. if content is not None:
  81. json_request["content"] = content
  82. origin = None
  83. auth_headers = request.requestHeaders.getRawHeaders(b"Authorization")
  84. if not auth_headers:
  85. raise NoAuthenticationError(
  86. 401, "Missing Authorization headers", Codes.UNAUTHORIZED,
  87. )
  88. for auth in auth_headers:
  89. if auth.startswith(b"X-Matrix"):
  90. (origin, key, sig) = _parse_auth_header(auth)
  91. json_request["origin"] = origin
  92. json_request["signatures"].setdefault(origin, {})[key] = sig
  93. if (
  94. self.federation_domain_whitelist is not None and
  95. origin not in self.federation_domain_whitelist
  96. ):
  97. raise FederationDeniedError(origin)
  98. if not json_request["signatures"]:
  99. raise NoAuthenticationError(
  100. 401, "Missing Authorization headers", Codes.UNAUTHORIZED,
  101. )
  102. yield self.keyring.verify_json_for_server(origin, json_request)
  103. logger.info("Request from %s", origin)
  104. request.authenticated_entity = origin
  105. # If we get a valid signed request from the other side, its probably
  106. # alive
  107. retry_timings = yield self.store.get_destination_retry_timings(origin)
  108. if retry_timings and retry_timings["retry_last_ts"]:
  109. run_in_background(self._reset_retry_timings, origin)
  110. defer.returnValue(origin)
  111. @defer.inlineCallbacks
  112. def _reset_retry_timings(self, origin):
  113. try:
  114. logger.info("Marking origin %r as up", origin)
  115. yield self.store.set_destination_retry_timings(origin, 0, 0)
  116. except Exception:
  117. logger.exception("Error resetting retry timings on %s", origin)
  118. def _parse_auth_header(header_bytes):
  119. """Parse an X-Matrix auth header
  120. Args:
  121. header_bytes (bytes): header value
  122. Returns:
  123. Tuple[str, str, str]: origin, key id, signature.
  124. Raises:
  125. AuthenticationError if the header could not be parsed
  126. """
  127. try:
  128. header_str = header_bytes.decode('utf-8')
  129. params = header_str.split(" ")[1].split(",")
  130. param_dict = dict(kv.split("=") for kv in params)
  131. def strip_quotes(value):
  132. if value.startswith(b"\""):
  133. return value[1:-1]
  134. else:
  135. return value
  136. origin = strip_quotes(param_dict["origin"])
  137. # ensure that the origin is a valid server name
  138. parse_and_validate_server_name(origin)
  139. key = strip_quotes(param_dict["key"])
  140. sig = strip_quotes(param_dict["sig"])
  141. return origin, key, sig
  142. except Exception as e:
  143. logger.warn(
  144. "Error parsing auth header '%s': %s",
  145. header_bytes.decode('ascii', 'replace'),
  146. e,
  147. )
  148. raise AuthenticationError(
  149. 400, "Malformed Authorization header", Codes.UNAUTHORIZED,
  150. )
  151. class BaseFederationServlet(object):
  152. REQUIRE_AUTH = True
  153. def __init__(self, handler, authenticator, ratelimiter, server_name):
  154. self.handler = handler
  155. self.authenticator = authenticator
  156. self.ratelimiter = ratelimiter
  157. def _wrap(self, func):
  158. authenticator = self.authenticator
  159. ratelimiter = self.ratelimiter
  160. @defer.inlineCallbacks
  161. @functools.wraps(func)
  162. def new_func(request, *args, **kwargs):
  163. content = None
  164. if request.method in ["PUT", "POST"]:
  165. # TODO: Handle other method types? other content types?
  166. content = parse_json_object_from_request(request)
  167. try:
  168. origin = yield authenticator.authenticate_request(request, content)
  169. except NoAuthenticationError:
  170. origin = None
  171. if self.REQUIRE_AUTH:
  172. logger.exception("authenticate_request failed")
  173. raise
  174. except Exception:
  175. logger.exception("authenticate_request failed")
  176. raise
  177. if origin:
  178. with ratelimiter.ratelimit(origin) as d:
  179. yield d
  180. response = yield func(
  181. origin, content, request.args, *args, **kwargs
  182. )
  183. else:
  184. response = yield func(
  185. origin, content, request.args, *args, **kwargs
  186. )
  187. defer.returnValue(response)
  188. # Extra logic that functools.wraps() doesn't finish
  189. new_func.__self__ = func.__self__
  190. return new_func
  191. def register(self, server):
  192. pattern = re.compile("^" + PREFIX + self.PATH + "$")
  193. for method in ("GET", "PUT", "POST"):
  194. code = getattr(self, "on_%s" % (method), None)
  195. if code is None:
  196. continue
  197. server.register_paths(method, (pattern,), self._wrap(code))
  198. class FederationSendServlet(BaseFederationServlet):
  199. PATH = "/send/(?P<transaction_id>[^/]*)/"
  200. def __init__(self, handler, server_name, **kwargs):
  201. super(FederationSendServlet, self).__init__(
  202. handler, server_name=server_name, **kwargs
  203. )
  204. self.server_name = server_name
  205. # This is when someone is trying to send us a bunch of data.
  206. @defer.inlineCallbacks
  207. def on_PUT(self, origin, content, query, transaction_id):
  208. """ Called on PUT /send/<transaction_id>/
  209. Args:
  210. request (twisted.web.http.Request): The HTTP request.
  211. transaction_id (str): The transaction_id associated with this
  212. request. This is *not* None.
  213. Returns:
  214. Deferred: Results in a tuple of `(code, response)`, where
  215. `response` is a python dict to be converted into JSON that is
  216. used as the response body.
  217. """
  218. # Parse the request
  219. try:
  220. transaction_data = content
  221. logger.debug(
  222. "Decoded %s: %s",
  223. transaction_id, str(transaction_data)
  224. )
  225. logger.info(
  226. "Received txn %s from %s. (PDUs: %d, EDUs: %d, failures: %d)",
  227. transaction_id, origin,
  228. len(transaction_data.get("pdus", [])),
  229. len(transaction_data.get("edus", [])),
  230. len(transaction_data.get("failures", [])),
  231. )
  232. # We should ideally be getting this from the security layer.
  233. # origin = body["origin"]
  234. # Add some extra data to the transaction dict that isn't included
  235. # in the request body.
  236. transaction_data.update(
  237. transaction_id=transaction_id,
  238. destination=self.server_name
  239. )
  240. except Exception as e:
  241. logger.exception(e)
  242. defer.returnValue((400, {"error": "Invalid transaction"}))
  243. return
  244. try:
  245. code, response = yield self.handler.on_incoming_transaction(
  246. transaction_data
  247. )
  248. except Exception:
  249. logger.exception("on_incoming_transaction failed")
  250. raise
  251. defer.returnValue((code, response))
  252. class FederationPullServlet(BaseFederationServlet):
  253. PATH = "/pull/"
  254. # This is for when someone asks us for everything since version X
  255. def on_GET(self, origin, content, query):
  256. return self.handler.on_pull_request(query["origin"][0], query["v"])
  257. class FederationEventServlet(BaseFederationServlet):
  258. PATH = "/event/(?P<event_id>[^/]*)/"
  259. # This is when someone asks for a data item for a given server data_id pair.
  260. def on_GET(self, origin, content, query, event_id):
  261. return self.handler.on_pdu_request(origin, event_id)
  262. class FederationStateServlet(BaseFederationServlet):
  263. PATH = "/state/(?P<context>[^/]*)/"
  264. # This is when someone asks for all data for a given context.
  265. def on_GET(self, origin, content, query, context):
  266. return self.handler.on_context_state_request(
  267. origin,
  268. context,
  269. query.get("event_id", [None])[0],
  270. )
  271. class FederationStateIdsServlet(BaseFederationServlet):
  272. PATH = "/state_ids/(?P<room_id>[^/]*)/"
  273. def on_GET(self, origin, content, query, room_id):
  274. return self.handler.on_state_ids_request(
  275. origin,
  276. room_id,
  277. query.get("event_id", [None])[0],
  278. )
  279. class FederationBackfillServlet(BaseFederationServlet):
  280. PATH = "/backfill/(?P<context>[^/]*)/"
  281. def on_GET(self, origin, content, query, context):
  282. versions = query["v"]
  283. limits = query["limit"]
  284. if not limits:
  285. return defer.succeed((400, {"error": "Did not include limit param"}))
  286. limit = int(limits[-1])
  287. return self.handler.on_backfill_request(origin, context, versions, limit)
  288. class FederationQueryServlet(BaseFederationServlet):
  289. PATH = "/query/(?P<query_type>[^/]*)"
  290. # This is when we receive a server-server Query
  291. def on_GET(self, origin, content, query, query_type):
  292. return self.handler.on_query_request(
  293. query_type,
  294. {k: v[0].decode("utf-8") for k, v in query.items()}
  295. )
  296. class FederationMakeJoinServlet(BaseFederationServlet):
  297. PATH = "/make_join/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
  298. @defer.inlineCallbacks
  299. def on_GET(self, origin, content, query, context, user_id):
  300. content = yield self.handler.on_make_join_request(
  301. origin, context, user_id,
  302. )
  303. defer.returnValue((200, content))
  304. class FederationMakeLeaveServlet(BaseFederationServlet):
  305. PATH = "/make_leave/(?P<context>[^/]*)/(?P<user_id>[^/]*)"
  306. @defer.inlineCallbacks
  307. def on_GET(self, origin, content, query, context, user_id):
  308. content = yield self.handler.on_make_leave_request(
  309. origin, context, user_id,
  310. )
  311. defer.returnValue((200, content))
  312. class FederationSendLeaveServlet(BaseFederationServlet):
  313. PATH = "/send_leave/(?P<room_id>[^/]*)/(?P<txid>[^/]*)"
  314. @defer.inlineCallbacks
  315. def on_PUT(self, origin, content, query, room_id, txid):
  316. content = yield self.handler.on_send_leave_request(origin, content)
  317. defer.returnValue((200, content))
  318. class FederationEventAuthServlet(BaseFederationServlet):
  319. PATH = "/event_auth/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  320. def on_GET(self, origin, content, query, context, event_id):
  321. return self.handler.on_event_auth(origin, context, event_id)
  322. class FederationSendJoinServlet(BaseFederationServlet):
  323. PATH = "/send_join/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  324. @defer.inlineCallbacks
  325. def on_PUT(self, origin, content, query, context, event_id):
  326. # TODO(paul): assert that context/event_id parsed from path actually
  327. # match those given in content
  328. content = yield self.handler.on_send_join_request(origin, content)
  329. defer.returnValue((200, content))
  330. class FederationInviteServlet(BaseFederationServlet):
  331. PATH = "/invite/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  332. @defer.inlineCallbacks
  333. def on_PUT(self, origin, content, query, context, event_id):
  334. # TODO(paul): assert that context/event_id parsed from path actually
  335. # match those given in content
  336. content = yield self.handler.on_invite_request(origin, content)
  337. defer.returnValue((200, content))
  338. class FederationThirdPartyInviteExchangeServlet(BaseFederationServlet):
  339. PATH = "/exchange_third_party_invite/(?P<room_id>[^/]*)"
  340. @defer.inlineCallbacks
  341. def on_PUT(self, origin, content, query, room_id):
  342. content = yield self.handler.on_exchange_third_party_invite_request(
  343. origin, room_id, content
  344. )
  345. defer.returnValue((200, content))
  346. class FederationClientKeysQueryServlet(BaseFederationServlet):
  347. PATH = "/user/keys/query"
  348. def on_POST(self, origin, content, query):
  349. return self.handler.on_query_client_keys(origin, content)
  350. class FederationUserDevicesQueryServlet(BaseFederationServlet):
  351. PATH = "/user/devices/(?P<user_id>[^/]*)"
  352. def on_GET(self, origin, content, query, user_id):
  353. return self.handler.on_query_user_devices(origin, user_id)
  354. class FederationClientKeysClaimServlet(BaseFederationServlet):
  355. PATH = "/user/keys/claim"
  356. @defer.inlineCallbacks
  357. def on_POST(self, origin, content, query):
  358. response = yield self.handler.on_claim_client_keys(origin, content)
  359. defer.returnValue((200, response))
  360. class FederationQueryAuthServlet(BaseFederationServlet):
  361. PATH = "/query_auth/(?P<context>[^/]*)/(?P<event_id>[^/]*)"
  362. @defer.inlineCallbacks
  363. def on_POST(self, origin, content, query, context, event_id):
  364. new_content = yield self.handler.on_query_auth_request(
  365. origin, content, context, event_id
  366. )
  367. defer.returnValue((200, new_content))
  368. class FederationGetMissingEventsServlet(BaseFederationServlet):
  369. # TODO(paul): Why does this path alone end with "/?" optional?
  370. PATH = "/get_missing_events/(?P<room_id>[^/]*)/?"
  371. @defer.inlineCallbacks
  372. def on_POST(self, origin, content, query, room_id):
  373. limit = int(content.get("limit", 10))
  374. min_depth = int(content.get("min_depth", 0))
  375. earliest_events = content.get("earliest_events", [])
  376. latest_events = content.get("latest_events", [])
  377. content = yield self.handler.on_get_missing_events(
  378. origin,
  379. room_id=room_id,
  380. earliest_events=earliest_events,
  381. latest_events=latest_events,
  382. min_depth=min_depth,
  383. limit=limit,
  384. )
  385. defer.returnValue((200, content))
  386. class On3pidBindServlet(BaseFederationServlet):
  387. PATH = "/3pid/onbind"
  388. REQUIRE_AUTH = False
  389. @defer.inlineCallbacks
  390. def on_POST(self, origin, content, query):
  391. if "invites" in content:
  392. last_exception = None
  393. for invite in content["invites"]:
  394. try:
  395. if "signed" not in invite or "token" not in invite["signed"]:
  396. message = ("Rejecting received notification of third-"
  397. "party invite without signed: %s" % (invite,))
  398. logger.info(message)
  399. raise SynapseError(400, message)
  400. yield self.handler.exchange_third_party_invite(
  401. invite["sender"],
  402. invite["mxid"],
  403. invite["room_id"],
  404. invite["signed"],
  405. )
  406. except Exception as e:
  407. last_exception = e
  408. if last_exception:
  409. raise last_exception
  410. defer.returnValue((200, {}))
  411. class OpenIdUserInfo(BaseFederationServlet):
  412. """
  413. Exchange a bearer token for information about a user.
  414. The response format should be compatible with:
  415. http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
  416. GET /openid/userinfo?access_token=ABDEFGH HTTP/1.1
  417. HTTP/1.1 200 OK
  418. Content-Type: application/json
  419. {
  420. "sub": "@userpart:example.org",
  421. }
  422. """
  423. PATH = "/openid/userinfo"
  424. REQUIRE_AUTH = False
  425. @defer.inlineCallbacks
  426. def on_GET(self, origin, content, query):
  427. token = query.get("access_token", [None])[0]
  428. if token is None:
  429. defer.returnValue((401, {
  430. "errcode": "M_MISSING_TOKEN", "error": "Access Token required"
  431. }))
  432. return
  433. user_id = yield self.handler.on_openid_userinfo(token)
  434. if user_id is None:
  435. defer.returnValue((401, {
  436. "errcode": "M_UNKNOWN_TOKEN",
  437. "error": "Access Token unknown or expired"
  438. }))
  439. defer.returnValue((200, {"sub": user_id}))
  440. class PublicRoomList(BaseFederationServlet):
  441. """
  442. Fetch the public room list for this server.
  443. This API returns information in the same format as /publicRooms on the
  444. client API, but will only ever include local public rooms and hence is
  445. intended for consumption by other home servers.
  446. GET /publicRooms HTTP/1.1
  447. HTTP/1.1 200 OK
  448. Content-Type: application/json
  449. {
  450. "chunk": [
  451. {
  452. "aliases": [
  453. "#test:localhost"
  454. ],
  455. "guest_can_join": false,
  456. "name": "test room",
  457. "num_joined_members": 3,
  458. "room_id": "!whkydVegtvatLfXmPN:localhost",
  459. "world_readable": false
  460. }
  461. ],
  462. "end": "END",
  463. "start": "START"
  464. }
  465. """
  466. PATH = "/publicRooms"
  467. @defer.inlineCallbacks
  468. def on_GET(self, origin, content, query):
  469. limit = parse_integer_from_args(query, "limit", 0)
  470. since_token = parse_string_from_args(query, "since", None)
  471. include_all_networks = parse_boolean_from_args(
  472. query, "include_all_networks", False
  473. )
  474. third_party_instance_id = parse_string_from_args(
  475. query, "third_party_instance_id", None
  476. )
  477. if include_all_networks:
  478. network_tuple = None
  479. elif third_party_instance_id:
  480. network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
  481. else:
  482. network_tuple = ThirdPartyInstanceID(None, None)
  483. data = yield self.handler.get_local_public_room_list(
  484. limit, since_token,
  485. network_tuple=network_tuple
  486. )
  487. defer.returnValue((200, data))
  488. class FederationVersionServlet(BaseFederationServlet):
  489. PATH = "/version"
  490. REQUIRE_AUTH = False
  491. def on_GET(self, origin, content, query):
  492. return defer.succeed((200, {
  493. "server": {
  494. "name": "Synapse",
  495. "version": get_version_string(synapse)
  496. },
  497. }))
  498. class FederationGroupsProfileServlet(BaseFederationServlet):
  499. """Get/set the basic profile of a group on behalf of a user
  500. """
  501. PATH = "/groups/(?P<group_id>[^/]*)/profile$"
  502. @defer.inlineCallbacks
  503. def on_GET(self, origin, content, query, group_id):
  504. requester_user_id = parse_string_from_args(query, "requester_user_id")
  505. if get_domain_from_id(requester_user_id) != origin:
  506. raise SynapseError(403, "requester_user_id doesn't match origin")
  507. new_content = yield self.handler.get_group_profile(
  508. group_id, requester_user_id
  509. )
  510. defer.returnValue((200, new_content))
  511. @defer.inlineCallbacks
  512. def on_POST(self, origin, content, query, group_id):
  513. requester_user_id = parse_string_from_args(query, "requester_user_id")
  514. if get_domain_from_id(requester_user_id) != origin:
  515. raise SynapseError(403, "requester_user_id doesn't match origin")
  516. new_content = yield self.handler.update_group_profile(
  517. group_id, requester_user_id, content
  518. )
  519. defer.returnValue((200, new_content))
  520. class FederationGroupsSummaryServlet(BaseFederationServlet):
  521. PATH = "/groups/(?P<group_id>[^/]*)/summary$"
  522. @defer.inlineCallbacks
  523. def on_GET(self, origin, content, query, group_id):
  524. requester_user_id = parse_string_from_args(query, "requester_user_id")
  525. if get_domain_from_id(requester_user_id) != origin:
  526. raise SynapseError(403, "requester_user_id doesn't match origin")
  527. new_content = yield self.handler.get_group_summary(
  528. group_id, requester_user_id
  529. )
  530. defer.returnValue((200, new_content))
  531. class FederationGroupsRoomsServlet(BaseFederationServlet):
  532. """Get the rooms in a group on behalf of a user
  533. """
  534. PATH = "/groups/(?P<group_id>[^/]*)/rooms$"
  535. @defer.inlineCallbacks
  536. def on_GET(self, origin, content, query, group_id):
  537. requester_user_id = parse_string_from_args(query, "requester_user_id")
  538. if get_domain_from_id(requester_user_id) != origin:
  539. raise SynapseError(403, "requester_user_id doesn't match origin")
  540. new_content = yield self.handler.get_rooms_in_group(
  541. group_id, requester_user_id
  542. )
  543. defer.returnValue((200, new_content))
  544. class FederationGroupsAddRoomsServlet(BaseFederationServlet):
  545. """Add/remove room from group
  546. """
  547. PATH = "/groups/(?P<group_id>[^/]*)/room/(?P<room_id>[^/]*)$"
  548. @defer.inlineCallbacks
  549. def on_POST(self, origin, content, query, group_id, room_id):
  550. requester_user_id = parse_string_from_args(query, "requester_user_id")
  551. if get_domain_from_id(requester_user_id) != origin:
  552. raise SynapseError(403, "requester_user_id doesn't match origin")
  553. new_content = yield self.handler.add_room_to_group(
  554. group_id, requester_user_id, room_id, content
  555. )
  556. defer.returnValue((200, new_content))
  557. @defer.inlineCallbacks
  558. def on_DELETE(self, origin, content, query, group_id, room_id):
  559. requester_user_id = parse_string_from_args(query, "requester_user_id")
  560. if get_domain_from_id(requester_user_id) != origin:
  561. raise SynapseError(403, "requester_user_id doesn't match origin")
  562. new_content = yield self.handler.remove_room_from_group(
  563. group_id, requester_user_id, room_id,
  564. )
  565. defer.returnValue((200, new_content))
  566. class FederationGroupsAddRoomsConfigServlet(BaseFederationServlet):
  567. """Update room config in group
  568. """
  569. PATH = (
  570. "/groups/(?P<group_id>[^/]*)/room/(?P<room_id>[^/]*)"
  571. "/config/(?P<config_key>[^/]*)$"
  572. )
  573. @defer.inlineCallbacks
  574. def on_POST(self, origin, content, query, group_id, room_id, config_key):
  575. requester_user_id = parse_string_from_args(query, "requester_user_id")
  576. if get_domain_from_id(requester_user_id) != origin:
  577. raise SynapseError(403, "requester_user_id doesn't match origin")
  578. result = yield self.groups_handler.update_room_in_group(
  579. group_id, requester_user_id, room_id, config_key, content,
  580. )
  581. defer.returnValue((200, result))
  582. class FederationGroupsUsersServlet(BaseFederationServlet):
  583. """Get the users in a group on behalf of a user
  584. """
  585. PATH = "/groups/(?P<group_id>[^/]*)/users$"
  586. @defer.inlineCallbacks
  587. def on_GET(self, origin, content, query, group_id):
  588. requester_user_id = parse_string_from_args(query, "requester_user_id")
  589. if get_domain_from_id(requester_user_id) != origin:
  590. raise SynapseError(403, "requester_user_id doesn't match origin")
  591. new_content = yield self.handler.get_users_in_group(
  592. group_id, requester_user_id
  593. )
  594. defer.returnValue((200, new_content))
  595. class FederationGroupsInvitedUsersServlet(BaseFederationServlet):
  596. """Get the users that have been invited to a group
  597. """
  598. PATH = "/groups/(?P<group_id>[^/]*)/invited_users$"
  599. @defer.inlineCallbacks
  600. def on_GET(self, origin, content, query, group_id):
  601. requester_user_id = parse_string_from_args(query, "requester_user_id")
  602. if get_domain_from_id(requester_user_id) != origin:
  603. raise SynapseError(403, "requester_user_id doesn't match origin")
  604. new_content = yield self.handler.get_invited_users_in_group(
  605. group_id, requester_user_id
  606. )
  607. defer.returnValue((200, new_content))
  608. class FederationGroupsInviteServlet(BaseFederationServlet):
  609. """Ask a group server to invite someone to the group
  610. """
  611. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/invite$"
  612. @defer.inlineCallbacks
  613. def on_POST(self, origin, content, query, group_id, user_id):
  614. requester_user_id = parse_string_from_args(query, "requester_user_id")
  615. if get_domain_from_id(requester_user_id) != origin:
  616. raise SynapseError(403, "requester_user_id doesn't match origin")
  617. new_content = yield self.handler.invite_to_group(
  618. group_id, user_id, requester_user_id, content,
  619. )
  620. defer.returnValue((200, new_content))
  621. class FederationGroupsAcceptInviteServlet(BaseFederationServlet):
  622. """Accept an invitation from the group server
  623. """
  624. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/accept_invite$"
  625. @defer.inlineCallbacks
  626. def on_POST(self, origin, content, query, group_id, user_id):
  627. if get_domain_from_id(user_id) != origin:
  628. raise SynapseError(403, "user_id doesn't match origin")
  629. new_content = yield self.handler.accept_invite(
  630. group_id, user_id, content,
  631. )
  632. defer.returnValue((200, new_content))
  633. class FederationGroupsJoinServlet(BaseFederationServlet):
  634. """Attempt to join a group
  635. """
  636. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/join$"
  637. @defer.inlineCallbacks
  638. def on_POST(self, origin, content, query, group_id, user_id):
  639. if get_domain_from_id(user_id) != origin:
  640. raise SynapseError(403, "user_id doesn't match origin")
  641. new_content = yield self.handler.join_group(
  642. group_id, user_id, content,
  643. )
  644. defer.returnValue((200, new_content))
  645. class FederationGroupsRemoveUserServlet(BaseFederationServlet):
  646. """Leave or kick a user from the group
  647. """
  648. PATH = "/groups/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/remove$"
  649. @defer.inlineCallbacks
  650. def on_POST(self, origin, content, query, group_id, user_id):
  651. requester_user_id = parse_string_from_args(query, "requester_user_id")
  652. if get_domain_from_id(requester_user_id) != origin:
  653. raise SynapseError(403, "requester_user_id doesn't match origin")
  654. new_content = yield self.handler.remove_user_from_group(
  655. group_id, user_id, requester_user_id, content,
  656. )
  657. defer.returnValue((200, new_content))
  658. class FederationGroupsLocalInviteServlet(BaseFederationServlet):
  659. """A group server has invited a local user
  660. """
  661. PATH = "/groups/local/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/invite$"
  662. @defer.inlineCallbacks
  663. def on_POST(self, origin, content, query, group_id, user_id):
  664. if get_domain_from_id(group_id) != origin:
  665. raise SynapseError(403, "group_id doesn't match origin")
  666. new_content = yield self.handler.on_invite(
  667. group_id, user_id, content,
  668. )
  669. defer.returnValue((200, new_content))
  670. class FederationGroupsRemoveLocalUserServlet(BaseFederationServlet):
  671. """A group server has removed a local user
  672. """
  673. PATH = "/groups/local/(?P<group_id>[^/]*)/users/(?P<user_id>[^/]*)/remove$"
  674. @defer.inlineCallbacks
  675. def on_POST(self, origin, content, query, group_id, user_id):
  676. if get_domain_from_id(group_id) != origin:
  677. raise SynapseError(403, "user_id doesn't match origin")
  678. new_content = yield self.handler.user_removed_from_group(
  679. group_id, user_id, content,
  680. )
  681. defer.returnValue((200, new_content))
  682. class FederationGroupsRenewAttestaionServlet(BaseFederationServlet):
  683. """A group or user's server renews their attestation
  684. """
  685. PATH = "/groups/(?P<group_id>[^/]*)/renew_attestation/(?P<user_id>[^/]*)$"
  686. @defer.inlineCallbacks
  687. def on_POST(self, origin, content, query, group_id, user_id):
  688. # We don't need to check auth here as we check the attestation signatures
  689. new_content = yield self.handler.on_renew_attestation(
  690. group_id, user_id, content
  691. )
  692. defer.returnValue((200, new_content))
  693. class FederationGroupsSummaryRoomsServlet(BaseFederationServlet):
  694. """Add/remove a room from the group summary, with optional category.
  695. Matches both:
  696. - /groups/:group/summary/rooms/:room_id
  697. - /groups/:group/summary/categories/:category/rooms/:room_id
  698. """
  699. PATH = (
  700. "/groups/(?P<group_id>[^/]*)/summary"
  701. "(/categories/(?P<category_id>[^/]+))?"
  702. "/rooms/(?P<room_id>[^/]*)$"
  703. )
  704. @defer.inlineCallbacks
  705. def on_POST(self, origin, content, query, group_id, category_id, room_id):
  706. requester_user_id = parse_string_from_args(query, "requester_user_id")
  707. if get_domain_from_id(requester_user_id) != origin:
  708. raise SynapseError(403, "requester_user_id doesn't match origin")
  709. if category_id == "":
  710. raise SynapseError(400, "category_id cannot be empty string")
  711. resp = yield self.handler.update_group_summary_room(
  712. group_id, requester_user_id,
  713. room_id=room_id,
  714. category_id=category_id,
  715. content=content,
  716. )
  717. defer.returnValue((200, resp))
  718. @defer.inlineCallbacks
  719. def on_DELETE(self, origin, content, query, group_id, category_id, room_id):
  720. requester_user_id = parse_string_from_args(query, "requester_user_id")
  721. if get_domain_from_id(requester_user_id) != origin:
  722. raise SynapseError(403, "requester_user_id doesn't match origin")
  723. if category_id == "":
  724. raise SynapseError(400, "category_id cannot be empty string")
  725. resp = yield self.handler.delete_group_summary_room(
  726. group_id, requester_user_id,
  727. room_id=room_id,
  728. category_id=category_id,
  729. )
  730. defer.returnValue((200, resp))
  731. class FederationGroupsCategoriesServlet(BaseFederationServlet):
  732. """Get all categories for a group
  733. """
  734. PATH = (
  735. "/groups/(?P<group_id>[^/]*)/categories/$"
  736. )
  737. @defer.inlineCallbacks
  738. def on_GET(self, origin, content, query, group_id):
  739. requester_user_id = parse_string_from_args(query, "requester_user_id")
  740. if get_domain_from_id(requester_user_id) != origin:
  741. raise SynapseError(403, "requester_user_id doesn't match origin")
  742. resp = yield self.handler.get_group_categories(
  743. group_id, requester_user_id,
  744. )
  745. defer.returnValue((200, resp))
  746. class FederationGroupsCategoryServlet(BaseFederationServlet):
  747. """Add/remove/get a category in a group
  748. """
  749. PATH = (
  750. "/groups/(?P<group_id>[^/]*)/categories/(?P<category_id>[^/]+)$"
  751. )
  752. @defer.inlineCallbacks
  753. def on_GET(self, origin, content, query, group_id, category_id):
  754. requester_user_id = parse_string_from_args(query, "requester_user_id")
  755. if get_domain_from_id(requester_user_id) != origin:
  756. raise SynapseError(403, "requester_user_id doesn't match origin")
  757. resp = yield self.handler.get_group_category(
  758. group_id, requester_user_id, category_id
  759. )
  760. defer.returnValue((200, resp))
  761. @defer.inlineCallbacks
  762. def on_POST(self, origin, content, query, group_id, category_id):
  763. requester_user_id = parse_string_from_args(query, "requester_user_id")
  764. if get_domain_from_id(requester_user_id) != origin:
  765. raise SynapseError(403, "requester_user_id doesn't match origin")
  766. if category_id == "":
  767. raise SynapseError(400, "category_id cannot be empty string")
  768. resp = yield self.handler.upsert_group_category(
  769. group_id, requester_user_id, category_id, content,
  770. )
  771. defer.returnValue((200, resp))
  772. @defer.inlineCallbacks
  773. def on_DELETE(self, origin, content, query, group_id, category_id):
  774. requester_user_id = parse_string_from_args(query, "requester_user_id")
  775. if get_domain_from_id(requester_user_id) != origin:
  776. raise SynapseError(403, "requester_user_id doesn't match origin")
  777. if category_id == "":
  778. raise SynapseError(400, "category_id cannot be empty string")
  779. resp = yield self.handler.delete_group_category(
  780. group_id, requester_user_id, category_id,
  781. )
  782. defer.returnValue((200, resp))
  783. class FederationGroupsRolesServlet(BaseFederationServlet):
  784. """Get roles in a group
  785. """
  786. PATH = (
  787. "/groups/(?P<group_id>[^/]*)/roles/$"
  788. )
  789. @defer.inlineCallbacks
  790. def on_GET(self, origin, content, query, group_id):
  791. requester_user_id = parse_string_from_args(query, "requester_user_id")
  792. if get_domain_from_id(requester_user_id) != origin:
  793. raise SynapseError(403, "requester_user_id doesn't match origin")
  794. resp = yield self.handler.get_group_roles(
  795. group_id, requester_user_id,
  796. )
  797. defer.returnValue((200, resp))
  798. class FederationGroupsRoleServlet(BaseFederationServlet):
  799. """Add/remove/get a role in a group
  800. """
  801. PATH = (
  802. "/groups/(?P<group_id>[^/]*)/roles/(?P<role_id>[^/]+)$"
  803. )
  804. @defer.inlineCallbacks
  805. def on_GET(self, origin, content, query, group_id, role_id):
  806. requester_user_id = parse_string_from_args(query, "requester_user_id")
  807. if get_domain_from_id(requester_user_id) != origin:
  808. raise SynapseError(403, "requester_user_id doesn't match origin")
  809. resp = yield self.handler.get_group_role(
  810. group_id, requester_user_id, role_id
  811. )
  812. defer.returnValue((200, resp))
  813. @defer.inlineCallbacks
  814. def on_POST(self, origin, content, query, group_id, role_id):
  815. requester_user_id = parse_string_from_args(query, "requester_user_id")
  816. if get_domain_from_id(requester_user_id) != origin:
  817. raise SynapseError(403, "requester_user_id doesn't match origin")
  818. if role_id == "":
  819. raise SynapseError(400, "role_id cannot be empty string")
  820. resp = yield self.handler.update_group_role(
  821. group_id, requester_user_id, role_id, content,
  822. )
  823. defer.returnValue((200, resp))
  824. @defer.inlineCallbacks
  825. def on_DELETE(self, origin, content, query, group_id, role_id):
  826. requester_user_id = parse_string_from_args(query, "requester_user_id")
  827. if get_domain_from_id(requester_user_id) != origin:
  828. raise SynapseError(403, "requester_user_id doesn't match origin")
  829. if role_id == "":
  830. raise SynapseError(400, "role_id cannot be empty string")
  831. resp = yield self.handler.delete_group_role(
  832. group_id, requester_user_id, role_id,
  833. )
  834. defer.returnValue((200, resp))
  835. class FederationGroupsSummaryUsersServlet(BaseFederationServlet):
  836. """Add/remove a user from the group summary, with optional role.
  837. Matches both:
  838. - /groups/:group/summary/users/:user_id
  839. - /groups/:group/summary/roles/:role/users/:user_id
  840. """
  841. PATH = (
  842. "/groups/(?P<group_id>[^/]*)/summary"
  843. "(/roles/(?P<role_id>[^/]+))?"
  844. "/users/(?P<user_id>[^/]*)$"
  845. )
  846. @defer.inlineCallbacks
  847. def on_POST(self, origin, content, query, group_id, role_id, user_id):
  848. requester_user_id = parse_string_from_args(query, "requester_user_id")
  849. if get_domain_from_id(requester_user_id) != origin:
  850. raise SynapseError(403, "requester_user_id doesn't match origin")
  851. if role_id == "":
  852. raise SynapseError(400, "role_id cannot be empty string")
  853. resp = yield self.handler.update_group_summary_user(
  854. group_id, requester_user_id,
  855. user_id=user_id,
  856. role_id=role_id,
  857. content=content,
  858. )
  859. defer.returnValue((200, resp))
  860. @defer.inlineCallbacks
  861. def on_DELETE(self, origin, content, query, group_id, role_id, user_id):
  862. requester_user_id = parse_string_from_args(query, "requester_user_id")
  863. if get_domain_from_id(requester_user_id) != origin:
  864. raise SynapseError(403, "requester_user_id doesn't match origin")
  865. if role_id == "":
  866. raise SynapseError(400, "role_id cannot be empty string")
  867. resp = yield self.handler.delete_group_summary_user(
  868. group_id, requester_user_id,
  869. user_id=user_id,
  870. role_id=role_id,
  871. )
  872. defer.returnValue((200, resp))
  873. class FederationGroupsBulkPublicisedServlet(BaseFederationServlet):
  874. """Get roles in a group
  875. """
  876. PATH = (
  877. "/get_groups_publicised$"
  878. )
  879. @defer.inlineCallbacks
  880. def on_POST(self, origin, content, query):
  881. resp = yield self.handler.bulk_get_publicised_groups(
  882. content["user_ids"], proxy=False,
  883. )
  884. defer.returnValue((200, resp))
  885. class FederationGroupsSettingJoinPolicyServlet(BaseFederationServlet):
  886. """Sets whether a group is joinable without an invite or knock
  887. """
  888. PATH = "/groups/(?P<group_id>[^/]*)/settings/m.join_policy$"
  889. @defer.inlineCallbacks
  890. def on_PUT(self, origin, content, query, group_id):
  891. requester_user_id = parse_string_from_args(query, "requester_user_id")
  892. if get_domain_from_id(requester_user_id) != origin:
  893. raise SynapseError(403, "requester_user_id doesn't match origin")
  894. new_content = yield self.handler.set_group_join_policy(
  895. group_id, requester_user_id, content
  896. )
  897. defer.returnValue((200, new_content))
  898. FEDERATION_SERVLET_CLASSES = (
  899. FederationSendServlet,
  900. FederationPullServlet,
  901. FederationEventServlet,
  902. FederationStateServlet,
  903. FederationStateIdsServlet,
  904. FederationBackfillServlet,
  905. FederationQueryServlet,
  906. FederationMakeJoinServlet,
  907. FederationMakeLeaveServlet,
  908. FederationEventServlet,
  909. FederationSendJoinServlet,
  910. FederationSendLeaveServlet,
  911. FederationInviteServlet,
  912. FederationQueryAuthServlet,
  913. FederationGetMissingEventsServlet,
  914. FederationEventAuthServlet,
  915. FederationClientKeysQueryServlet,
  916. FederationUserDevicesQueryServlet,
  917. FederationClientKeysClaimServlet,
  918. FederationThirdPartyInviteExchangeServlet,
  919. On3pidBindServlet,
  920. OpenIdUserInfo,
  921. FederationVersionServlet,
  922. )
  923. ROOM_LIST_CLASSES = (
  924. PublicRoomList,
  925. )
  926. GROUP_SERVER_SERVLET_CLASSES = (
  927. FederationGroupsProfileServlet,
  928. FederationGroupsSummaryServlet,
  929. FederationGroupsRoomsServlet,
  930. FederationGroupsUsersServlet,
  931. FederationGroupsInvitedUsersServlet,
  932. FederationGroupsInviteServlet,
  933. FederationGroupsAcceptInviteServlet,
  934. FederationGroupsJoinServlet,
  935. FederationGroupsRemoveUserServlet,
  936. FederationGroupsSummaryRoomsServlet,
  937. FederationGroupsCategoriesServlet,
  938. FederationGroupsCategoryServlet,
  939. FederationGroupsRolesServlet,
  940. FederationGroupsRoleServlet,
  941. FederationGroupsSummaryUsersServlet,
  942. FederationGroupsAddRoomsServlet,
  943. FederationGroupsAddRoomsConfigServlet,
  944. FederationGroupsSettingJoinPolicyServlet,
  945. )
  946. GROUP_LOCAL_SERVLET_CLASSES = (
  947. FederationGroupsLocalInviteServlet,
  948. FederationGroupsRemoveLocalUserServlet,
  949. FederationGroupsBulkPublicisedServlet,
  950. )
  951. GROUP_ATTESTATION_SERVLET_CLASSES = (
  952. FederationGroupsRenewAttestaionServlet,
  953. )
  954. def register_servlets(hs, resource, authenticator, ratelimiter):
  955. for servletclass in FEDERATION_SERVLET_CLASSES:
  956. servletclass(
  957. handler=hs.get_federation_server(),
  958. authenticator=authenticator,
  959. ratelimiter=ratelimiter,
  960. server_name=hs.hostname,
  961. ).register(resource)
  962. for servletclass in ROOM_LIST_CLASSES:
  963. servletclass(
  964. handler=hs.get_room_list_handler(),
  965. authenticator=authenticator,
  966. ratelimiter=ratelimiter,
  967. server_name=hs.hostname,
  968. ).register(resource)
  969. for servletclass in GROUP_SERVER_SERVLET_CLASSES:
  970. servletclass(
  971. handler=hs.get_groups_server_handler(),
  972. authenticator=authenticator,
  973. ratelimiter=ratelimiter,
  974. server_name=hs.hostname,
  975. ).register(resource)
  976. for servletclass in GROUP_LOCAL_SERVLET_CLASSES:
  977. servletclass(
  978. handler=hs.get_groups_local_handler(),
  979. authenticator=authenticator,
  980. ratelimiter=ratelimiter,
  981. server_name=hs.hostname,
  982. ).register(resource)
  983. for servletclass in GROUP_ATTESTATION_SERVLET_CLASSES:
  984. servletclass(
  985. handler=hs.get_groups_attestation_renewer(),
  986. authenticator=authenticator,
  987. ratelimiter=ratelimiter,
  988. server_name=hs.hostname,
  989. ).register(resource)