room.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  1. # Copyright 2014-2016 OpenMarket Ltd
  2. # Copyright 2018 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """ This module contains REST servlets to do with rooms: /rooms/<paths> """
  16. import logging
  17. import re
  18. from typing import TYPE_CHECKING, Awaitable, Dict, List, Optional, Tuple
  19. from urllib import parse as urlparse
  20. from twisted.web.server import Request
  21. from synapse.api.constants import EventTypes, Membership
  22. from synapse.api.errors import (
  23. AuthError,
  24. Codes,
  25. InvalidClientCredentialsError,
  26. MissingClientTokenError,
  27. ShadowBanError,
  28. SynapseError,
  29. )
  30. from synapse.api.filtering import Filter
  31. from synapse.events.utils import format_event_for_client_v2
  32. from synapse.http.server import HttpServer
  33. from synapse.http.servlet import (
  34. ResolveRoomIdMixin,
  35. RestServlet,
  36. assert_params_in_dict,
  37. parse_boolean,
  38. parse_integer,
  39. parse_json_object_from_request,
  40. parse_string,
  41. parse_strings_from_args,
  42. )
  43. from synapse.http.site import SynapseRequest
  44. from synapse.logging.opentracing import set_tag
  45. from synapse.rest.client._base import client_patterns
  46. from synapse.rest.client.transactions import HttpTransactionCache
  47. from synapse.storage.state import StateFilter
  48. from synapse.streams.config import PaginationConfig
  49. from synapse.types import JsonDict, StreamToken, ThirdPartyInstanceID, UserID
  50. from synapse.util import json_decoder
  51. from synapse.util.stringutils import parse_and_validate_server_name, random_string
  52. if TYPE_CHECKING:
  53. from synapse.server import HomeServer
  54. logger = logging.getLogger(__name__)
  55. class TransactionRestServlet(RestServlet):
  56. def __init__(self, hs: "HomeServer"):
  57. super().__init__()
  58. self.txns = HttpTransactionCache(hs)
  59. class RoomCreateRestServlet(TransactionRestServlet):
  60. # No PATTERN; we have custom dispatch rules here
  61. def __init__(self, hs: "HomeServer"):
  62. super().__init__(hs)
  63. self._room_creation_handler = hs.get_room_creation_handler()
  64. self.auth = hs.get_auth()
  65. def register(self, http_server: HttpServer) -> None:
  66. PATTERNS = "/createRoom"
  67. register_txn_path(self, PATTERNS, http_server)
  68. def on_PUT(
  69. self, request: SynapseRequest, txn_id: str
  70. ) -> Awaitable[Tuple[int, JsonDict]]:
  71. set_tag("txn_id", txn_id)
  72. return self.txns.fetch_or_execute_request(request, self.on_POST, request)
  73. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  74. requester = await self.auth.get_user_by_req(request)
  75. info, _ = await self._room_creation_handler.create_room(
  76. requester, self.get_room_config(request)
  77. )
  78. return 200, info
  79. def get_room_config(self, request: Request) -> JsonDict:
  80. user_supplied_config = parse_json_object_from_request(request)
  81. return user_supplied_config
  82. # TODO: Needs unit testing for generic events
  83. class RoomStateEventRestServlet(TransactionRestServlet):
  84. def __init__(self, hs: "HomeServer"):
  85. super().__init__(hs)
  86. self.event_creation_handler = hs.get_event_creation_handler()
  87. self.room_member_handler = hs.get_room_member_handler()
  88. self.message_handler = hs.get_message_handler()
  89. self.auth = hs.get_auth()
  90. def register(self, http_server: HttpServer) -> None:
  91. # /room/$roomid/state/$eventtype
  92. no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$"
  93. # /room/$roomid/state/$eventtype/$statekey
  94. state_key = (
  95. "/rooms/(?P<room_id>[^/]*)/state/"
  96. "(?P<event_type>[^/]*)/(?P<state_key>[^/]*)$"
  97. )
  98. http_server.register_paths(
  99. "GET",
  100. client_patterns(state_key, v1=True),
  101. self.on_GET,
  102. self.__class__.__name__,
  103. )
  104. http_server.register_paths(
  105. "PUT",
  106. client_patterns(state_key, v1=True),
  107. self.on_PUT,
  108. self.__class__.__name__,
  109. )
  110. http_server.register_paths(
  111. "GET",
  112. client_patterns(no_state_key, v1=True),
  113. self.on_GET_no_state_key,
  114. self.__class__.__name__,
  115. )
  116. http_server.register_paths(
  117. "PUT",
  118. client_patterns(no_state_key, v1=True),
  119. self.on_PUT_no_state_key,
  120. self.__class__.__name__,
  121. )
  122. def on_GET_no_state_key(
  123. self, request: SynapseRequest, room_id: str, event_type: str
  124. ) -> Awaitable[Tuple[int, JsonDict]]:
  125. return self.on_GET(request, room_id, event_type, "")
  126. def on_PUT_no_state_key(
  127. self, request: SynapseRequest, room_id: str, event_type: str
  128. ) -> Awaitable[Tuple[int, JsonDict]]:
  129. return self.on_PUT(request, room_id, event_type, "")
  130. async def on_GET(
  131. self, request: SynapseRequest, room_id: str, event_type: str, state_key: str
  132. ) -> Tuple[int, JsonDict]:
  133. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  134. format = parse_string(
  135. request, "format", default="content", allowed_values=["content", "event"]
  136. )
  137. msg_handler = self.message_handler
  138. data = await msg_handler.get_room_data(
  139. user_id=requester.user.to_string(),
  140. room_id=room_id,
  141. event_type=event_type,
  142. state_key=state_key,
  143. )
  144. if not data:
  145. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  146. if format == "event":
  147. event = format_event_for_client_v2(data.get_dict())
  148. return 200, event
  149. elif format == "content":
  150. return 200, data.get_dict()["content"]
  151. # Format must be event or content, per the parse_string call above.
  152. raise RuntimeError(f"Unknown format: {format:r}.")
  153. async def on_PUT(
  154. self,
  155. request: SynapseRequest,
  156. room_id: str,
  157. event_type: str,
  158. state_key: str,
  159. txn_id: Optional[str] = None,
  160. ) -> Tuple[int, JsonDict]:
  161. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  162. if txn_id:
  163. set_tag("txn_id", txn_id)
  164. content = parse_json_object_from_request(request)
  165. event_dict = {
  166. "type": event_type,
  167. "content": content,
  168. "room_id": room_id,
  169. "sender": requester.user.to_string(),
  170. }
  171. if state_key is not None:
  172. event_dict["state_key"] = state_key
  173. try:
  174. if event_type == EventTypes.Member:
  175. membership = content.get("membership", None)
  176. event_id, _ = await self.room_member_handler.update_membership(
  177. requester,
  178. target=UserID.from_string(state_key),
  179. room_id=room_id,
  180. action=membership,
  181. content=content,
  182. )
  183. else:
  184. (
  185. event,
  186. _,
  187. ) = await self.event_creation_handler.create_and_send_nonmember_event(
  188. requester, event_dict, txn_id=txn_id
  189. )
  190. event_id = event.event_id
  191. except ShadowBanError:
  192. event_id = "$" + random_string(43)
  193. set_tag("event_id", event_id)
  194. ret = {"event_id": event_id}
  195. return 200, ret
  196. # TODO: Needs unit testing for generic events + feedback
  197. class RoomSendEventRestServlet(TransactionRestServlet):
  198. def __init__(self, hs: "HomeServer"):
  199. super().__init__(hs)
  200. self.event_creation_handler = hs.get_event_creation_handler()
  201. self.auth = hs.get_auth()
  202. def register(self, http_server: HttpServer) -> None:
  203. # /rooms/$roomid/send/$event_type[/$txn_id]
  204. PATTERNS = "/rooms/(?P<room_id>[^/]*)/send/(?P<event_type>[^/]*)"
  205. register_txn_path(self, PATTERNS, http_server, with_get=True)
  206. async def on_POST(
  207. self,
  208. request: SynapseRequest,
  209. room_id: str,
  210. event_type: str,
  211. txn_id: Optional[str] = None,
  212. ) -> Tuple[int, JsonDict]:
  213. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  214. content = parse_json_object_from_request(request)
  215. event_dict: JsonDict = {
  216. "type": event_type,
  217. "content": content,
  218. "room_id": room_id,
  219. "sender": requester.user.to_string(),
  220. }
  221. # Twisted will have processed the args by now.
  222. assert request.args is not None
  223. if b"ts" in request.args and requester.app_service:
  224. event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
  225. try:
  226. (
  227. event,
  228. _,
  229. ) = await self.event_creation_handler.create_and_send_nonmember_event(
  230. requester, event_dict, txn_id=txn_id
  231. )
  232. event_id = event.event_id
  233. except ShadowBanError:
  234. event_id = "$" + random_string(43)
  235. set_tag("event_id", event_id)
  236. return 200, {"event_id": event_id}
  237. def on_GET(
  238. self, request: SynapseRequest, room_id: str, event_type: str, txn_id: str
  239. ) -> Tuple[int, str]:
  240. return 200, "Not implemented"
  241. def on_PUT(
  242. self, request: SynapseRequest, room_id: str, event_type: str, txn_id: str
  243. ) -> Awaitable[Tuple[int, JsonDict]]:
  244. set_tag("txn_id", txn_id)
  245. return self.txns.fetch_or_execute_request(
  246. request, self.on_POST, request, room_id, event_type, txn_id
  247. )
  248. # TODO: Needs unit testing for room ID + alias joins
  249. class JoinRoomAliasServlet(ResolveRoomIdMixin, TransactionRestServlet):
  250. def __init__(self, hs: "HomeServer"):
  251. super().__init__(hs)
  252. super(ResolveRoomIdMixin, self).__init__(hs) # ensure the Mixin is set up
  253. self.auth = hs.get_auth()
  254. def register(self, http_server: HttpServer) -> None:
  255. # /join/$room_identifier[/$txn_id]
  256. PATTERNS = "/join/(?P<room_identifier>[^/]*)"
  257. register_txn_path(self, PATTERNS, http_server)
  258. async def on_POST(
  259. self,
  260. request: SynapseRequest,
  261. room_identifier: str,
  262. txn_id: Optional[str] = None,
  263. ) -> Tuple[int, JsonDict]:
  264. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  265. try:
  266. content = parse_json_object_from_request(request)
  267. except Exception:
  268. # Turns out we used to ignore the body entirely, and some clients
  269. # cheekily send invalid bodies.
  270. content = {}
  271. # twisted.web.server.Request.args is incorrectly defined as Optional[Any]
  272. args: Dict[bytes, List[bytes]] = request.args # type: ignore
  273. remote_room_hosts = parse_strings_from_args(args, "server_name", required=False)
  274. room_id, remote_room_hosts = await self.resolve_room_id(
  275. room_identifier,
  276. remote_room_hosts,
  277. )
  278. await self.room_member_handler.update_membership(
  279. requester=requester,
  280. target=requester.user,
  281. room_id=room_id,
  282. action="join",
  283. txn_id=txn_id,
  284. remote_room_hosts=remote_room_hosts,
  285. content=content,
  286. third_party_signed=content.get("third_party_signed", None),
  287. )
  288. return 200, {"room_id": room_id}
  289. def on_PUT(
  290. self, request: SynapseRequest, room_identifier: str, txn_id: str
  291. ) -> Awaitable[Tuple[int, JsonDict]]:
  292. set_tag("txn_id", txn_id)
  293. return self.txns.fetch_or_execute_request(
  294. request, self.on_POST, request, room_identifier, txn_id
  295. )
  296. # TODO: Needs unit testing
  297. class PublicRoomListRestServlet(TransactionRestServlet):
  298. PATTERNS = client_patterns("/publicRooms$", v1=True)
  299. def __init__(self, hs: "HomeServer"):
  300. super().__init__(hs)
  301. self.hs = hs
  302. self.auth = hs.get_auth()
  303. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  304. server = parse_string(request, "server")
  305. try:
  306. await self.auth.get_user_by_req(request, allow_guest=True)
  307. except InvalidClientCredentialsError as e:
  308. # Option to allow servers to require auth when accessing
  309. # /publicRooms via CS API. This is especially helpful in private
  310. # federations.
  311. if not self.hs.config.server.allow_public_rooms_without_auth:
  312. raise
  313. # We allow people to not be authed if they're just looking at our
  314. # room list, but require auth when we proxy the request.
  315. # In both cases we call the auth function, as that has the side
  316. # effect of logging who issued this request if an access token was
  317. # provided.
  318. if server:
  319. raise e
  320. limit: Optional[int] = parse_integer(request, "limit", 0)
  321. since_token = parse_string(request, "since")
  322. if limit == 0:
  323. # zero is a special value which corresponds to no limit.
  324. limit = None
  325. handler = self.hs.get_room_list_handler()
  326. if server and server != self.hs.config.server.server_name:
  327. # Ensure the server is valid.
  328. try:
  329. parse_and_validate_server_name(server)
  330. except ValueError:
  331. raise SynapseError(
  332. 400,
  333. "Invalid server name: %s" % (server,),
  334. Codes.INVALID_PARAM,
  335. )
  336. data = await handler.get_remote_public_room_list(
  337. server, limit=limit, since_token=since_token
  338. )
  339. else:
  340. data = await handler.get_local_public_room_list(
  341. limit=limit, since_token=since_token
  342. )
  343. return 200, data
  344. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  345. await self.auth.get_user_by_req(request, allow_guest=True)
  346. server = parse_string(request, "server")
  347. content = parse_json_object_from_request(request)
  348. limit: Optional[int] = int(content.get("limit", 100))
  349. since_token = content.get("since", None)
  350. search_filter = content.get("filter", None)
  351. include_all_networks = content.get("include_all_networks", False)
  352. third_party_instance_id = content.get("third_party_instance_id", None)
  353. if include_all_networks:
  354. network_tuple = None
  355. if third_party_instance_id is not None:
  356. raise SynapseError(
  357. 400, "Can't use include_all_networks with an explicit network"
  358. )
  359. elif third_party_instance_id is None:
  360. network_tuple = ThirdPartyInstanceID(None, None)
  361. else:
  362. network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
  363. if limit == 0:
  364. # zero is a special value which corresponds to no limit.
  365. limit = None
  366. handler = self.hs.get_room_list_handler()
  367. if server and server != self.hs.config.server.server_name:
  368. # Ensure the server is valid.
  369. try:
  370. parse_and_validate_server_name(server)
  371. except ValueError:
  372. raise SynapseError(
  373. 400,
  374. "Invalid server name: %s" % (server,),
  375. Codes.INVALID_PARAM,
  376. )
  377. data = await handler.get_remote_public_room_list(
  378. server,
  379. limit=limit,
  380. since_token=since_token,
  381. search_filter=search_filter,
  382. include_all_networks=include_all_networks,
  383. third_party_instance_id=third_party_instance_id,
  384. )
  385. else:
  386. data = await handler.get_local_public_room_list(
  387. limit=limit,
  388. since_token=since_token,
  389. search_filter=search_filter,
  390. network_tuple=network_tuple,
  391. )
  392. return 200, data
  393. # TODO: Needs unit testing
  394. class RoomMemberListRestServlet(RestServlet):
  395. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/members$", v1=True)
  396. def __init__(self, hs: "HomeServer"):
  397. super().__init__()
  398. self.message_handler = hs.get_message_handler()
  399. self.auth = hs.get_auth()
  400. self.store = hs.get_datastores().main
  401. async def on_GET(
  402. self, request: SynapseRequest, room_id: str
  403. ) -> Tuple[int, JsonDict]:
  404. # TODO support Pagination stream API (limit/tokens)
  405. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  406. handler = self.message_handler
  407. # request the state as of a given event, as identified by a stream token,
  408. # for consistency with /messages etc.
  409. # useful for getting the membership in retrospect as of a given /sync
  410. # response.
  411. at_token_string = parse_string(request, "at")
  412. if at_token_string is None:
  413. at_token = None
  414. else:
  415. at_token = await StreamToken.from_string(self.store, at_token_string)
  416. # let you filter down on particular memberships.
  417. # XXX: this may not be the best shape for this API - we could pass in a filter
  418. # instead, except filters aren't currently aware of memberships.
  419. # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details.
  420. membership = parse_string(request, "membership")
  421. not_membership = parse_string(request, "not_membership")
  422. events = await handler.get_state_events(
  423. room_id=room_id,
  424. user_id=requester.user.to_string(),
  425. at_token=at_token,
  426. state_filter=StateFilter.from_types([(EventTypes.Member, None)]),
  427. )
  428. chunk = []
  429. for event in events:
  430. if (membership and event["content"].get("membership") != membership) or (
  431. not_membership and event["content"].get("membership") == not_membership
  432. ):
  433. continue
  434. chunk.append(event)
  435. return 200, {"chunk": chunk}
  436. # deprecated in favour of /members?membership=join?
  437. # except it does custom AS logic and has a simpler return format
  438. class JoinedRoomMemberListRestServlet(RestServlet):
  439. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$", v1=True)
  440. def __init__(self, hs: "HomeServer"):
  441. super().__init__()
  442. self.message_handler = hs.get_message_handler()
  443. self.auth = hs.get_auth()
  444. async def on_GET(
  445. self, request: SynapseRequest, room_id: str
  446. ) -> Tuple[int, JsonDict]:
  447. requester = await self.auth.get_user_by_req(request)
  448. users_with_profile = await self.message_handler.get_joined_members(
  449. requester, room_id
  450. )
  451. return 200, {"joined": users_with_profile}
  452. # TODO: Needs better unit testing
  453. class RoomMessageListRestServlet(RestServlet):
  454. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/messages$", v1=True)
  455. def __init__(self, hs: "HomeServer"):
  456. super().__init__()
  457. self._hs = hs
  458. self.pagination_handler = hs.get_pagination_handler()
  459. self.auth = hs.get_auth()
  460. self.store = hs.get_datastores().main
  461. async def on_GET(
  462. self, request: SynapseRequest, room_id: str
  463. ) -> Tuple[int, JsonDict]:
  464. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  465. pagination_config = await PaginationConfig.from_request(
  466. self.store, request, default_limit=10
  467. )
  468. # Twisted will have processed the args by now.
  469. assert request.args is not None
  470. as_client_event = b"raw" not in request.args
  471. filter_str = parse_string(request, "filter", encoding="utf-8")
  472. if filter_str:
  473. filter_json = urlparse.unquote(filter_str)
  474. event_filter: Optional[Filter] = Filter(
  475. self._hs, json_decoder.decode(filter_json)
  476. )
  477. if (
  478. event_filter
  479. and event_filter.filter_json.get("event_format", "client")
  480. == "federation"
  481. ):
  482. as_client_event = False
  483. else:
  484. event_filter = None
  485. msgs = await self.pagination_handler.get_messages(
  486. room_id=room_id,
  487. requester=requester,
  488. pagin_config=pagination_config,
  489. as_client_event=as_client_event,
  490. event_filter=event_filter,
  491. )
  492. return 200, msgs
  493. # TODO: Needs unit testing
  494. class RoomStateRestServlet(RestServlet):
  495. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/state$", v1=True)
  496. def __init__(self, hs: "HomeServer"):
  497. super().__init__()
  498. self.message_handler = hs.get_message_handler()
  499. self.auth = hs.get_auth()
  500. async def on_GET(
  501. self, request: SynapseRequest, room_id: str
  502. ) -> Tuple[int, List[JsonDict]]:
  503. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  504. # Get all the current state for this room
  505. events = await self.message_handler.get_state_events(
  506. room_id=room_id,
  507. user_id=requester.user.to_string(),
  508. is_guest=requester.is_guest,
  509. )
  510. return 200, events
  511. # TODO: Needs unit testing
  512. class RoomInitialSyncRestServlet(RestServlet):
  513. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$", v1=True)
  514. def __init__(self, hs: "HomeServer"):
  515. super().__init__()
  516. self.initial_sync_handler = hs.get_initial_sync_handler()
  517. self.auth = hs.get_auth()
  518. self.store = hs.get_datastores().main
  519. async def on_GET(
  520. self, request: SynapseRequest, room_id: str
  521. ) -> Tuple[int, JsonDict]:
  522. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  523. pagination_config = await PaginationConfig.from_request(self.store, request)
  524. content = await self.initial_sync_handler.room_initial_sync(
  525. room_id=room_id, requester=requester, pagin_config=pagination_config
  526. )
  527. return 200, content
  528. class RoomEventServlet(RestServlet):
  529. PATTERNS = client_patterns(
  530. "/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$", v1=True
  531. )
  532. def __init__(self, hs: "HomeServer"):
  533. super().__init__()
  534. self.clock = hs.get_clock()
  535. self._store = hs.get_datastores().main
  536. self.event_handler = hs.get_event_handler()
  537. self._event_serializer = hs.get_event_client_serializer()
  538. self.auth = hs.get_auth()
  539. async def on_GET(
  540. self, request: SynapseRequest, room_id: str, event_id: str
  541. ) -> Tuple[int, JsonDict]:
  542. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  543. try:
  544. event = await self.event_handler.get_event(
  545. requester.user, room_id, event_id
  546. )
  547. except AuthError:
  548. # This endpoint is supposed to return a 404 when the requester does
  549. # not have permission to access the event
  550. # https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-rooms-roomid-event-eventid
  551. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  552. if event:
  553. # Ensure there are bundled aggregations available.
  554. aggregations = await self._store.get_bundled_aggregations(
  555. [event], requester.user.to_string()
  556. )
  557. time_now = self.clock.time_msec()
  558. event_dict = self._event_serializer.serialize_event(
  559. event, time_now, bundle_aggregations=aggregations
  560. )
  561. return 200, event_dict
  562. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  563. class RoomEventContextServlet(RestServlet):
  564. PATTERNS = client_patterns(
  565. "/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$", v1=True
  566. )
  567. def __init__(self, hs: "HomeServer"):
  568. super().__init__()
  569. self._hs = hs
  570. self.clock = hs.get_clock()
  571. self.room_context_handler = hs.get_room_context_handler()
  572. self._event_serializer = hs.get_event_client_serializer()
  573. self.auth = hs.get_auth()
  574. async def on_GET(
  575. self, request: SynapseRequest, room_id: str, event_id: str
  576. ) -> Tuple[int, JsonDict]:
  577. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  578. limit = parse_integer(request, "limit", default=10)
  579. # picking the API shape for symmetry with /messages
  580. filter_str = parse_string(request, "filter", encoding="utf-8")
  581. if filter_str:
  582. filter_json = urlparse.unquote(filter_str)
  583. event_filter: Optional[Filter] = Filter(
  584. self._hs, json_decoder.decode(filter_json)
  585. )
  586. else:
  587. event_filter = None
  588. event_context = await self.room_context_handler.get_event_context(
  589. requester, room_id, event_id, limit, event_filter
  590. )
  591. if not event_context:
  592. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  593. time_now = self.clock.time_msec()
  594. results = {
  595. "events_before": self._event_serializer.serialize_events(
  596. event_context.events_before,
  597. time_now,
  598. bundle_aggregations=event_context.aggregations,
  599. ),
  600. "event": self._event_serializer.serialize_event(
  601. event_context.event,
  602. time_now,
  603. bundle_aggregations=event_context.aggregations,
  604. ),
  605. "events_after": self._event_serializer.serialize_events(
  606. event_context.events_after,
  607. time_now,
  608. bundle_aggregations=event_context.aggregations,
  609. ),
  610. "state": self._event_serializer.serialize_events(
  611. event_context.state, time_now
  612. ),
  613. "start": event_context.start,
  614. "end": event_context.end,
  615. }
  616. return 200, results
  617. class RoomForgetRestServlet(TransactionRestServlet):
  618. def __init__(self, hs: "HomeServer"):
  619. super().__init__(hs)
  620. self.room_member_handler = hs.get_room_member_handler()
  621. self.auth = hs.get_auth()
  622. def register(self, http_server: HttpServer) -> None:
  623. PATTERNS = "/rooms/(?P<room_id>[^/]*)/forget"
  624. register_txn_path(self, PATTERNS, http_server)
  625. async def on_POST(
  626. self, request: SynapseRequest, room_id: str, txn_id: Optional[str] = None
  627. ) -> Tuple[int, JsonDict]:
  628. requester = await self.auth.get_user_by_req(request, allow_guest=False)
  629. await self.room_member_handler.forget(user=requester.user, room_id=room_id)
  630. return 200, {}
  631. def on_PUT(
  632. self, request: SynapseRequest, room_id: str, txn_id: str
  633. ) -> Awaitable[Tuple[int, JsonDict]]:
  634. set_tag("txn_id", txn_id)
  635. return self.txns.fetch_or_execute_request(
  636. request, self.on_POST, request, room_id, txn_id
  637. )
  638. # TODO: Needs unit testing
  639. class RoomMembershipRestServlet(TransactionRestServlet):
  640. def __init__(self, hs: "HomeServer"):
  641. super().__init__(hs)
  642. self.room_member_handler = hs.get_room_member_handler()
  643. self.auth = hs.get_auth()
  644. def register(self, http_server: HttpServer) -> None:
  645. # /rooms/$roomid/[invite|join|leave]
  646. PATTERNS = (
  647. "/rooms/(?P<room_id>[^/]*)/"
  648. "(?P<membership_action>join|invite|leave|ban|unban|kick)"
  649. )
  650. register_txn_path(self, PATTERNS, http_server)
  651. async def on_POST(
  652. self,
  653. request: SynapseRequest,
  654. room_id: str,
  655. membership_action: str,
  656. txn_id: Optional[str] = None,
  657. ) -> Tuple[int, JsonDict]:
  658. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  659. if requester.is_guest and membership_action not in {
  660. Membership.JOIN,
  661. Membership.LEAVE,
  662. }:
  663. raise AuthError(403, "Guest access not allowed")
  664. try:
  665. content = parse_json_object_from_request(request)
  666. except Exception:
  667. # Turns out we used to ignore the body entirely, and some clients
  668. # cheekily send invalid bodies.
  669. content = {}
  670. if membership_action == "invite" and self._has_3pid_invite_keys(content):
  671. try:
  672. await self.room_member_handler.do_3pid_invite(
  673. room_id,
  674. requester.user,
  675. content["medium"],
  676. content["address"],
  677. content["id_server"],
  678. requester,
  679. txn_id,
  680. content.get("id_access_token"),
  681. )
  682. except ShadowBanError:
  683. # Pretend the request succeeded.
  684. pass
  685. return 200, {}
  686. target = requester.user
  687. if membership_action in ["invite", "ban", "unban", "kick"]:
  688. assert_params_in_dict(content, ["user_id"])
  689. target = UserID.from_string(content["user_id"])
  690. event_content = None
  691. if "reason" in content:
  692. event_content = {"reason": content["reason"]}
  693. try:
  694. await self.room_member_handler.update_membership(
  695. requester=requester,
  696. target=target,
  697. room_id=room_id,
  698. action=membership_action,
  699. txn_id=txn_id,
  700. third_party_signed=content.get("third_party_signed", None),
  701. content=event_content,
  702. )
  703. except ShadowBanError:
  704. # Pretend the request succeeded.
  705. pass
  706. return_value = {}
  707. if membership_action == "join":
  708. return_value["room_id"] = room_id
  709. return 200, return_value
  710. def _has_3pid_invite_keys(self, content: JsonDict) -> bool:
  711. for key in {"id_server", "medium", "address"}:
  712. if key not in content:
  713. return False
  714. return True
  715. def on_PUT(
  716. self, request: SynapseRequest, room_id: str, membership_action: str, txn_id: str
  717. ) -> Awaitable[Tuple[int, JsonDict]]:
  718. set_tag("txn_id", txn_id)
  719. return self.txns.fetch_or_execute_request(
  720. request, self.on_POST, request, room_id, membership_action, txn_id
  721. )
  722. class RoomRedactEventRestServlet(TransactionRestServlet):
  723. def __init__(self, hs: "HomeServer"):
  724. super().__init__(hs)
  725. self.event_creation_handler = hs.get_event_creation_handler()
  726. self.auth = hs.get_auth()
  727. def register(self, http_server: HttpServer) -> None:
  728. PATTERNS = "/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)"
  729. register_txn_path(self, PATTERNS, http_server)
  730. async def on_POST(
  731. self,
  732. request: SynapseRequest,
  733. room_id: str,
  734. event_id: str,
  735. txn_id: Optional[str] = None,
  736. ) -> Tuple[int, JsonDict]:
  737. requester = await self.auth.get_user_by_req(request)
  738. content = parse_json_object_from_request(request)
  739. try:
  740. (
  741. event,
  742. _,
  743. ) = await self.event_creation_handler.create_and_send_nonmember_event(
  744. requester,
  745. {
  746. "type": EventTypes.Redaction,
  747. "content": content,
  748. "room_id": room_id,
  749. "sender": requester.user.to_string(),
  750. "redacts": event_id,
  751. },
  752. txn_id=txn_id,
  753. )
  754. event_id = event.event_id
  755. except ShadowBanError:
  756. event_id = "$" + random_string(43)
  757. set_tag("event_id", event_id)
  758. return 200, {"event_id": event_id}
  759. def on_PUT(
  760. self, request: SynapseRequest, room_id: str, event_id: str, txn_id: str
  761. ) -> Awaitable[Tuple[int, JsonDict]]:
  762. set_tag("txn_id", txn_id)
  763. return self.txns.fetch_or_execute_request(
  764. request, self.on_POST, request, room_id, event_id, txn_id
  765. )
  766. class RoomTypingRestServlet(RestServlet):
  767. PATTERNS = client_patterns(
  768. "/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$", v1=True
  769. )
  770. def __init__(self, hs: "HomeServer"):
  771. super().__init__()
  772. self.hs = hs
  773. self.presence_handler = hs.get_presence_handler()
  774. self.auth = hs.get_auth()
  775. # If we're not on the typing writer instance we should scream if we get
  776. # requests.
  777. self._is_typing_writer = (
  778. hs.get_instance_name() in hs.config.worker.writers.typing
  779. )
  780. async def on_PUT(
  781. self, request: SynapseRequest, room_id: str, user_id: str
  782. ) -> Tuple[int, JsonDict]:
  783. requester = await self.auth.get_user_by_req(request)
  784. if not self._is_typing_writer:
  785. raise Exception("Got /typing request on instance that is not typing writer")
  786. room_id = urlparse.unquote(room_id)
  787. target_user = UserID.from_string(urlparse.unquote(user_id))
  788. content = parse_json_object_from_request(request)
  789. await self.presence_handler.bump_presence_active_time(requester.user)
  790. # Limit timeout to stop people from setting silly typing timeouts.
  791. timeout = min(content.get("timeout", 30000), 120000)
  792. # Defer getting the typing handler since it will raise on workers.
  793. typing_handler = self.hs.get_typing_writer_handler()
  794. try:
  795. if content["typing"]:
  796. await typing_handler.started_typing(
  797. target_user=target_user,
  798. requester=requester,
  799. room_id=room_id,
  800. timeout=timeout,
  801. )
  802. else:
  803. await typing_handler.stopped_typing(
  804. target_user=target_user, requester=requester, room_id=room_id
  805. )
  806. except ShadowBanError:
  807. # Pretend this worked without error.
  808. pass
  809. return 200, {}
  810. class RoomAliasListServlet(RestServlet):
  811. PATTERNS = [
  812. re.compile(
  813. r"^/_matrix/client/unstable/org\.matrix\.msc2432"
  814. r"/rooms/(?P<room_id>[^/]*)/aliases"
  815. ),
  816. ] + list(client_patterns("/rooms/(?P<room_id>[^/]*)/aliases$", unstable=False))
  817. def __init__(self, hs: "HomeServer"):
  818. super().__init__()
  819. self.auth = hs.get_auth()
  820. self.directory_handler = hs.get_directory_handler()
  821. async def on_GET(
  822. self, request: SynapseRequest, room_id: str
  823. ) -> Tuple[int, JsonDict]:
  824. requester = await self.auth.get_user_by_req(request)
  825. alias_list = await self.directory_handler.get_aliases_for_room(
  826. requester, room_id
  827. )
  828. return 200, {"aliases": alias_list}
  829. class SearchRestServlet(RestServlet):
  830. PATTERNS = client_patterns("/search$", v1=True)
  831. def __init__(self, hs: "HomeServer"):
  832. super().__init__()
  833. self.search_handler = hs.get_search_handler()
  834. self.auth = hs.get_auth()
  835. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  836. requester = await self.auth.get_user_by_req(request)
  837. content = parse_json_object_from_request(request)
  838. batch = parse_string(request, "next_batch")
  839. results = await self.search_handler.search(requester.user, content, batch)
  840. return 200, results
  841. class JoinedRoomsRestServlet(RestServlet):
  842. PATTERNS = client_patterns("/joined_rooms$", v1=True)
  843. def __init__(self, hs: "HomeServer"):
  844. super().__init__()
  845. self.store = hs.get_datastores().main
  846. self.auth = hs.get_auth()
  847. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  848. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  849. room_ids = await self.store.get_rooms_for_user(requester.user.to_string())
  850. return 200, {"joined_rooms": list(room_ids)}
  851. def register_txn_path(
  852. servlet: RestServlet,
  853. regex_string: str,
  854. http_server: HttpServer,
  855. with_get: bool = False,
  856. ) -> None:
  857. """Registers a transaction-based path.
  858. This registers two paths:
  859. PUT regex_string/$txnid
  860. POST regex_string
  861. Args:
  862. regex_string: The regex string to register. Must NOT have a
  863. trailing $ as this string will be appended to.
  864. http_server: The http_server to register paths with.
  865. with_get: True to also register respective GET paths for the PUTs.
  866. """
  867. on_POST = getattr(servlet, "on_POST", None)
  868. on_PUT = getattr(servlet, "on_PUT", None)
  869. if on_POST is None or on_PUT is None:
  870. raise RuntimeError("on_POST and on_PUT must exist when using register_txn_path")
  871. http_server.register_paths(
  872. "POST",
  873. client_patterns(regex_string + "$", v1=True),
  874. on_POST,
  875. servlet.__class__.__name__,
  876. )
  877. http_server.register_paths(
  878. "PUT",
  879. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  880. on_PUT,
  881. servlet.__class__.__name__,
  882. )
  883. on_GET = getattr(servlet, "on_GET", None)
  884. if with_get:
  885. if on_GET is None:
  886. raise RuntimeError(
  887. "register_txn_path called with with_get = True, but no on_GET method exists"
  888. )
  889. http_server.register_paths(
  890. "GET",
  891. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  892. on_GET,
  893. servlet.__class__.__name__,
  894. )
  895. class TimestampLookupRestServlet(RestServlet):
  896. """
  897. API endpoint to fetch the `event_id` of the closest event to the given
  898. timestamp (`ts` query parameter) in the given direction (`dir` query
  899. parameter).
  900. Useful for cases like jump to date so you can start paginating messages from
  901. a given date in the archive.
  902. `ts` is a timestamp in milliseconds where we will find the closest event in
  903. the given direction.
  904. `dir` can be `f` or `b` to indicate forwards and backwards in time from the
  905. given timestamp.
  906. GET /_matrix/client/unstable/org.matrix.msc3030/rooms/<roomID>/timestamp_to_event?ts=<timestamp>&dir=<direction>
  907. {
  908. "event_id": ...
  909. }
  910. """
  911. PATTERNS = (
  912. re.compile(
  913. "^/_matrix/client/unstable/org.matrix.msc3030"
  914. "/rooms/(?P<room_id>[^/]*)/timestamp_to_event$"
  915. ),
  916. )
  917. def __init__(self, hs: "HomeServer"):
  918. super().__init__()
  919. self._auth = hs.get_auth()
  920. self._store = hs.get_datastores().main
  921. self.timestamp_lookup_handler = hs.get_timestamp_lookup_handler()
  922. async def on_GET(
  923. self, request: SynapseRequest, room_id: str
  924. ) -> Tuple[int, JsonDict]:
  925. requester = await self._auth.get_user_by_req(request)
  926. await self._auth.check_user_in_room(room_id, requester.user.to_string())
  927. timestamp = parse_integer(request, "ts", required=True)
  928. direction = parse_string(request, "dir", default="f", allowed_values=["f", "b"])
  929. (
  930. event_id,
  931. origin_server_ts,
  932. ) = await self.timestamp_lookup_handler.get_event_for_timestamp(
  933. requester, room_id, timestamp, direction
  934. )
  935. return 200, {
  936. "event_id": event_id,
  937. "origin_server_ts": origin_server_ts,
  938. }
  939. class RoomHierarchyRestServlet(RestServlet):
  940. PATTERNS = (
  941. re.compile(
  942. "^/_matrix/client/(v1|unstable/org.matrix.msc2946)"
  943. "/rooms/(?P<room_id>[^/]*)/hierarchy$"
  944. ),
  945. )
  946. def __init__(self, hs: "HomeServer"):
  947. super().__init__()
  948. self._auth = hs.get_auth()
  949. self._room_summary_handler = hs.get_room_summary_handler()
  950. async def on_GET(
  951. self, request: SynapseRequest, room_id: str
  952. ) -> Tuple[int, JsonDict]:
  953. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  954. max_depth = parse_integer(request, "max_depth")
  955. if max_depth is not None and max_depth < 0:
  956. raise SynapseError(
  957. 400, "'max_depth' must be a non-negative integer", Codes.BAD_JSON
  958. )
  959. limit = parse_integer(request, "limit")
  960. if limit is not None and limit <= 0:
  961. raise SynapseError(
  962. 400, "'limit' must be a positive integer", Codes.BAD_JSON
  963. )
  964. return 200, await self._room_summary_handler.get_room_hierarchy(
  965. requester,
  966. room_id,
  967. suggested_only=parse_boolean(request, "suggested_only", default=False),
  968. max_depth=max_depth,
  969. limit=limit,
  970. from_token=parse_string(request, "from"),
  971. )
  972. class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet):
  973. PATTERNS = (
  974. re.compile(
  975. "^/_matrix/client/unstable/im.nheko.summary"
  976. "/rooms/(?P<room_identifier>[^/]*)/summary$"
  977. ),
  978. )
  979. def __init__(self, hs: "HomeServer"):
  980. super().__init__(hs)
  981. self._auth = hs.get_auth()
  982. self._room_summary_handler = hs.get_room_summary_handler()
  983. async def on_GET(
  984. self, request: SynapseRequest, room_identifier: str
  985. ) -> Tuple[int, JsonDict]:
  986. try:
  987. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  988. requester_user_id: Optional[str] = requester.user.to_string()
  989. except MissingClientTokenError:
  990. # auth is optional
  991. requester_user_id = None
  992. # twisted.web.server.Request.args is incorrectly defined as Optional[Any]
  993. args: Dict[bytes, List[bytes]] = request.args # type: ignore
  994. remote_room_hosts = parse_strings_from_args(args, "via", required=False)
  995. room_id, remote_room_hosts = await self.resolve_room_id(
  996. room_identifier,
  997. remote_room_hosts,
  998. )
  999. return 200, await self._room_summary_handler.get_room_summary(
  1000. requester_user_id,
  1001. room_id,
  1002. remote_room_hosts,
  1003. )
  1004. def register_servlets(
  1005. hs: "HomeServer", http_server: HttpServer, is_worker: bool = False
  1006. ) -> None:
  1007. RoomStateEventRestServlet(hs).register(http_server)
  1008. RoomMemberListRestServlet(hs).register(http_server)
  1009. JoinedRoomMemberListRestServlet(hs).register(http_server)
  1010. RoomMessageListRestServlet(hs).register(http_server)
  1011. JoinRoomAliasServlet(hs).register(http_server)
  1012. RoomMembershipRestServlet(hs).register(http_server)
  1013. RoomSendEventRestServlet(hs).register(http_server)
  1014. PublicRoomListRestServlet(hs).register(http_server)
  1015. RoomStateRestServlet(hs).register(http_server)
  1016. RoomRedactEventRestServlet(hs).register(http_server)
  1017. RoomTypingRestServlet(hs).register(http_server)
  1018. RoomEventContextServlet(hs).register(http_server)
  1019. RoomHierarchyRestServlet(hs).register(http_server)
  1020. if hs.config.experimental.msc3266_enabled:
  1021. RoomSummaryRestServlet(hs).register(http_server)
  1022. RoomEventServlet(hs).register(http_server)
  1023. JoinedRoomsRestServlet(hs).register(http_server)
  1024. RoomAliasListServlet(hs).register(http_server)
  1025. SearchRestServlet(hs).register(http_server)
  1026. RoomCreateRestServlet(hs).register(http_server)
  1027. if hs.config.experimental.msc3030_enabled:
  1028. TimestampLookupRestServlet(hs).register(http_server)
  1029. # Some servlets only get registered for the main process.
  1030. if not is_worker:
  1031. RoomForgetRestServlet(hs).register(http_server)
  1032. def register_deprecated_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
  1033. RoomInitialSyncRestServlet(hs).register(http_server)