room.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243
  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)
  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_datastore()
  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.pagination_handler = hs.get_pagination_handler()
  458. self.auth = hs.get_auth()
  459. self.store = hs.get_datastore()
  460. async def on_GET(
  461. self, request: SynapseRequest, room_id: str
  462. ) -> Tuple[int, JsonDict]:
  463. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  464. pagination_config = await PaginationConfig.from_request(
  465. self.store, request, default_limit=10
  466. )
  467. # Twisted will have processed the args by now.
  468. assert request.args is not None
  469. as_client_event = b"raw" not in request.args
  470. filter_str = parse_string(request, "filter", encoding="utf-8")
  471. if filter_str:
  472. filter_json = urlparse.unquote(filter_str)
  473. event_filter: Optional[Filter] = Filter(json_decoder.decode(filter_json))
  474. if (
  475. event_filter
  476. and event_filter.filter_json.get("event_format", "client")
  477. == "federation"
  478. ):
  479. as_client_event = False
  480. else:
  481. event_filter = None
  482. msgs = await self.pagination_handler.get_messages(
  483. room_id=room_id,
  484. requester=requester,
  485. pagin_config=pagination_config,
  486. as_client_event=as_client_event,
  487. event_filter=event_filter,
  488. )
  489. return 200, msgs
  490. # TODO: Needs unit testing
  491. class RoomStateRestServlet(RestServlet):
  492. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/state$", v1=True)
  493. def __init__(self, hs: "HomeServer"):
  494. super().__init__()
  495. self.message_handler = hs.get_message_handler()
  496. self.auth = hs.get_auth()
  497. async def on_GET(
  498. self, request: SynapseRequest, room_id: str
  499. ) -> Tuple[int, List[JsonDict]]:
  500. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  501. # Get all the current state for this room
  502. events = await self.message_handler.get_state_events(
  503. room_id=room_id,
  504. user_id=requester.user.to_string(),
  505. is_guest=requester.is_guest,
  506. )
  507. return 200, events
  508. # TODO: Needs unit testing
  509. class RoomInitialSyncRestServlet(RestServlet):
  510. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$", v1=True)
  511. def __init__(self, hs: "HomeServer"):
  512. super().__init__()
  513. self.initial_sync_handler = hs.get_initial_sync_handler()
  514. self.auth = hs.get_auth()
  515. self.store = hs.get_datastore()
  516. async def on_GET(
  517. self, request: SynapseRequest, room_id: str
  518. ) -> Tuple[int, JsonDict]:
  519. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  520. pagination_config = await PaginationConfig.from_request(self.store, request)
  521. content = await self.initial_sync_handler.room_initial_sync(
  522. room_id=room_id, requester=requester, pagin_config=pagination_config
  523. )
  524. return 200, content
  525. class RoomEventServlet(RestServlet):
  526. PATTERNS = client_patterns(
  527. "/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$", v1=True
  528. )
  529. def __init__(self, hs: "HomeServer"):
  530. super().__init__()
  531. self.clock = hs.get_clock()
  532. self.event_handler = hs.get_event_handler()
  533. self._event_serializer = hs.get_event_client_serializer()
  534. self.auth = hs.get_auth()
  535. async def on_GET(
  536. self, request: SynapseRequest, room_id: str, event_id: str
  537. ) -> Tuple[int, JsonDict]:
  538. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  539. try:
  540. event = await self.event_handler.get_event(
  541. requester.user, room_id, event_id
  542. )
  543. except AuthError:
  544. # This endpoint is supposed to return a 404 when the requester does
  545. # not have permission to access the event
  546. # https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-rooms-roomid-event-eventid
  547. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  548. time_now = self.clock.time_msec()
  549. if event:
  550. event_dict = await self._event_serializer.serialize_event(event, time_now)
  551. return 200, event_dict
  552. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  553. class RoomEventContextServlet(RestServlet):
  554. PATTERNS = client_patterns(
  555. "/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$", v1=True
  556. )
  557. def __init__(self, hs: "HomeServer"):
  558. super().__init__()
  559. self.clock = hs.get_clock()
  560. self.room_context_handler = hs.get_room_context_handler()
  561. self._event_serializer = hs.get_event_client_serializer()
  562. self.auth = hs.get_auth()
  563. async def on_GET(
  564. self, request: SynapseRequest, room_id: str, event_id: str
  565. ) -> Tuple[int, JsonDict]:
  566. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  567. limit = parse_integer(request, "limit", default=10)
  568. # picking the API shape for symmetry with /messages
  569. filter_str = parse_string(request, "filter", encoding="utf-8")
  570. if filter_str:
  571. filter_json = urlparse.unquote(filter_str)
  572. event_filter: Optional[Filter] = Filter(json_decoder.decode(filter_json))
  573. else:
  574. event_filter = None
  575. results = await self.room_context_handler.get_event_context(
  576. requester, room_id, event_id, limit, event_filter
  577. )
  578. if not results:
  579. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  580. time_now = self.clock.time_msec()
  581. results["events_before"] = await self._event_serializer.serialize_events(
  582. results["events_before"], time_now
  583. )
  584. results["event"] = await self._event_serializer.serialize_event(
  585. results["event"], time_now
  586. )
  587. results["events_after"] = await self._event_serializer.serialize_events(
  588. results["events_after"], time_now
  589. )
  590. results["state"] = await self._event_serializer.serialize_events(
  591. results["state"],
  592. time_now,
  593. # No need to bundle aggregations for state events
  594. bundle_aggregations=False,
  595. )
  596. return 200, results
  597. class RoomForgetRestServlet(TransactionRestServlet):
  598. def __init__(self, hs: "HomeServer"):
  599. super().__init__(hs)
  600. self.room_member_handler = hs.get_room_member_handler()
  601. self.auth = hs.get_auth()
  602. def register(self, http_server: HttpServer) -> None:
  603. PATTERNS = "/rooms/(?P<room_id>[^/]*)/forget"
  604. register_txn_path(self, PATTERNS, http_server)
  605. async def on_POST(
  606. self, request: SynapseRequest, room_id: str, txn_id: Optional[str] = None
  607. ) -> Tuple[int, JsonDict]:
  608. requester = await self.auth.get_user_by_req(request, allow_guest=False)
  609. await self.room_member_handler.forget(user=requester.user, room_id=room_id)
  610. return 200, {}
  611. def on_PUT(
  612. self, request: SynapseRequest, room_id: str, txn_id: str
  613. ) -> Awaitable[Tuple[int, JsonDict]]:
  614. set_tag("txn_id", txn_id)
  615. return self.txns.fetch_or_execute_request(
  616. request, self.on_POST, request, room_id, txn_id
  617. )
  618. # TODO: Needs unit testing
  619. class RoomMembershipRestServlet(TransactionRestServlet):
  620. def __init__(self, hs: "HomeServer"):
  621. super().__init__(hs)
  622. self.room_member_handler = hs.get_room_member_handler()
  623. self.auth = hs.get_auth()
  624. def register(self, http_server: HttpServer) -> None:
  625. # /rooms/$roomid/[invite|join|leave]
  626. PATTERNS = (
  627. "/rooms/(?P<room_id>[^/]*)/"
  628. "(?P<membership_action>join|invite|leave|ban|unban|kick)"
  629. )
  630. register_txn_path(self, PATTERNS, http_server)
  631. async def on_POST(
  632. self,
  633. request: SynapseRequest,
  634. room_id: str,
  635. membership_action: str,
  636. txn_id: Optional[str] = None,
  637. ) -> Tuple[int, JsonDict]:
  638. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  639. if requester.is_guest and membership_action not in {
  640. Membership.JOIN,
  641. Membership.LEAVE,
  642. }:
  643. raise AuthError(403, "Guest access not allowed")
  644. try:
  645. content = parse_json_object_from_request(request)
  646. except Exception:
  647. # Turns out we used to ignore the body entirely, and some clients
  648. # cheekily send invalid bodies.
  649. content = {}
  650. if membership_action == "invite" and self._has_3pid_invite_keys(content):
  651. try:
  652. await self.room_member_handler.do_3pid_invite(
  653. room_id,
  654. requester.user,
  655. content["medium"],
  656. content["address"],
  657. content["id_server"],
  658. requester,
  659. txn_id,
  660. content.get("id_access_token"),
  661. )
  662. except ShadowBanError:
  663. # Pretend the request succeeded.
  664. pass
  665. return 200, {}
  666. target = requester.user
  667. if membership_action in ["invite", "ban", "unban", "kick"]:
  668. assert_params_in_dict(content, ["user_id"])
  669. target = UserID.from_string(content["user_id"])
  670. event_content = None
  671. if "reason" in content:
  672. event_content = {"reason": content["reason"]}
  673. try:
  674. await self.room_member_handler.update_membership(
  675. requester=requester,
  676. target=target,
  677. room_id=room_id,
  678. action=membership_action,
  679. txn_id=txn_id,
  680. third_party_signed=content.get("third_party_signed", None),
  681. content=event_content,
  682. )
  683. except ShadowBanError:
  684. # Pretend the request succeeded.
  685. pass
  686. return_value = {}
  687. if membership_action == "join":
  688. return_value["room_id"] = room_id
  689. return 200, return_value
  690. def _has_3pid_invite_keys(self, content: JsonDict) -> bool:
  691. for key in {"id_server", "medium", "address"}:
  692. if key not in content:
  693. return False
  694. return True
  695. def on_PUT(
  696. self, request: SynapseRequest, room_id: str, membership_action: str, txn_id: str
  697. ) -> Awaitable[Tuple[int, JsonDict]]:
  698. set_tag("txn_id", txn_id)
  699. return self.txns.fetch_or_execute_request(
  700. request, self.on_POST, request, room_id, membership_action, txn_id
  701. )
  702. class RoomRedactEventRestServlet(TransactionRestServlet):
  703. def __init__(self, hs: "HomeServer"):
  704. super().__init__(hs)
  705. self.event_creation_handler = hs.get_event_creation_handler()
  706. self.auth = hs.get_auth()
  707. def register(self, http_server: HttpServer) -> None:
  708. PATTERNS = "/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)"
  709. register_txn_path(self, PATTERNS, http_server)
  710. async def on_POST(
  711. self,
  712. request: SynapseRequest,
  713. room_id: str,
  714. event_id: str,
  715. txn_id: Optional[str] = None,
  716. ) -> Tuple[int, JsonDict]:
  717. requester = await self.auth.get_user_by_req(request)
  718. content = parse_json_object_from_request(request)
  719. try:
  720. (
  721. event,
  722. _,
  723. ) = await self.event_creation_handler.create_and_send_nonmember_event(
  724. requester,
  725. {
  726. "type": EventTypes.Redaction,
  727. "content": content,
  728. "room_id": room_id,
  729. "sender": requester.user.to_string(),
  730. "redacts": event_id,
  731. },
  732. txn_id=txn_id,
  733. )
  734. event_id = event.event_id
  735. except ShadowBanError:
  736. event_id = "$" + random_string(43)
  737. set_tag("event_id", event_id)
  738. return 200, {"event_id": event_id}
  739. def on_PUT(
  740. self, request: SynapseRequest, room_id: str, event_id: str, txn_id: str
  741. ) -> Awaitable[Tuple[int, JsonDict]]:
  742. set_tag("txn_id", txn_id)
  743. return self.txns.fetch_or_execute_request(
  744. request, self.on_POST, request, room_id, event_id, txn_id
  745. )
  746. class RoomTypingRestServlet(RestServlet):
  747. PATTERNS = client_patterns(
  748. "/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$", v1=True
  749. )
  750. def __init__(self, hs: "HomeServer"):
  751. super().__init__()
  752. self.hs = hs
  753. self.presence_handler = hs.get_presence_handler()
  754. self.auth = hs.get_auth()
  755. # If we're not on the typing writer instance we should scream if we get
  756. # requests.
  757. self._is_typing_writer = (
  758. hs.get_instance_name() in hs.config.worker.writers.typing
  759. )
  760. async def on_PUT(
  761. self, request: SynapseRequest, room_id: str, user_id: str
  762. ) -> Tuple[int, JsonDict]:
  763. requester = await self.auth.get_user_by_req(request)
  764. if not self._is_typing_writer:
  765. raise Exception("Got /typing request on instance that is not typing writer")
  766. room_id = urlparse.unquote(room_id)
  767. target_user = UserID.from_string(urlparse.unquote(user_id))
  768. content = parse_json_object_from_request(request)
  769. await self.presence_handler.bump_presence_active_time(requester.user)
  770. # Limit timeout to stop people from setting silly typing timeouts.
  771. timeout = min(content.get("timeout", 30000), 120000)
  772. # Defer getting the typing handler since it will raise on workers.
  773. typing_handler = self.hs.get_typing_writer_handler()
  774. try:
  775. if content["typing"]:
  776. await typing_handler.started_typing(
  777. target_user=target_user,
  778. requester=requester,
  779. room_id=room_id,
  780. timeout=timeout,
  781. )
  782. else:
  783. await typing_handler.stopped_typing(
  784. target_user=target_user, requester=requester, room_id=room_id
  785. )
  786. except ShadowBanError:
  787. # Pretend this worked without error.
  788. pass
  789. return 200, {}
  790. class RoomAliasListServlet(RestServlet):
  791. PATTERNS = [
  792. re.compile(
  793. r"^/_matrix/client/unstable/org\.matrix\.msc2432"
  794. r"/rooms/(?P<room_id>[^/]*)/aliases"
  795. ),
  796. ] + list(client_patterns("/rooms/(?P<room_id>[^/]*)/aliases$", unstable=False))
  797. def __init__(self, hs: "HomeServer"):
  798. super().__init__()
  799. self.auth = hs.get_auth()
  800. self.directory_handler = hs.get_directory_handler()
  801. async def on_GET(
  802. self, request: SynapseRequest, room_id: str
  803. ) -> Tuple[int, JsonDict]:
  804. requester = await self.auth.get_user_by_req(request)
  805. alias_list = await self.directory_handler.get_aliases_for_room(
  806. requester, room_id
  807. )
  808. return 200, {"aliases": alias_list}
  809. class SearchRestServlet(RestServlet):
  810. PATTERNS = client_patterns("/search$", v1=True)
  811. def __init__(self, hs: "HomeServer"):
  812. super().__init__()
  813. self.search_handler = hs.get_search_handler()
  814. self.auth = hs.get_auth()
  815. async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  816. requester = await self.auth.get_user_by_req(request)
  817. content = parse_json_object_from_request(request)
  818. batch = parse_string(request, "next_batch")
  819. results = await self.search_handler.search(requester.user, content, batch)
  820. return 200, results
  821. class JoinedRoomsRestServlet(RestServlet):
  822. PATTERNS = client_patterns("/joined_rooms$", v1=True)
  823. def __init__(self, hs: "HomeServer"):
  824. super().__init__()
  825. self.store = hs.get_datastore()
  826. self.auth = hs.get_auth()
  827. async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
  828. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  829. room_ids = await self.store.get_rooms_for_user(requester.user.to_string())
  830. return 200, {"joined_rooms": list(room_ids)}
  831. def register_txn_path(
  832. servlet: RestServlet,
  833. regex_string: str,
  834. http_server: HttpServer,
  835. with_get: bool = False,
  836. ) -> None:
  837. """Registers a transaction-based path.
  838. This registers two paths:
  839. PUT regex_string/$txnid
  840. POST regex_string
  841. Args:
  842. regex_string: The regex string to register. Must NOT have a
  843. trailing $ as this string will be appended to.
  844. http_server: The http_server to register paths with.
  845. with_get: True to also register respective GET paths for the PUTs.
  846. """
  847. on_POST = getattr(servlet, "on_POST", None)
  848. on_PUT = getattr(servlet, "on_PUT", None)
  849. if on_POST is None or on_PUT is None:
  850. raise RuntimeError("on_POST and on_PUT must exist when using register_txn_path")
  851. http_server.register_paths(
  852. "POST",
  853. client_patterns(regex_string + "$", v1=True),
  854. on_POST,
  855. servlet.__class__.__name__,
  856. )
  857. http_server.register_paths(
  858. "PUT",
  859. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  860. on_PUT,
  861. servlet.__class__.__name__,
  862. )
  863. on_GET = getattr(servlet, "on_GET", None)
  864. if with_get:
  865. if on_GET is None:
  866. raise RuntimeError(
  867. "register_txn_path called with with_get = True, but no on_GET method exists"
  868. )
  869. http_server.register_paths(
  870. "GET",
  871. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  872. on_GET,
  873. servlet.__class__.__name__,
  874. )
  875. class RoomSpaceSummaryRestServlet(RestServlet):
  876. PATTERNS = (
  877. re.compile(
  878. "^/_matrix/client/unstable/org.matrix.msc2946"
  879. "/rooms/(?P<room_id>[^/]*)/spaces$"
  880. ),
  881. )
  882. def __init__(self, hs: "HomeServer"):
  883. super().__init__()
  884. self._auth = hs.get_auth()
  885. self._room_summary_handler = hs.get_room_summary_handler()
  886. async def on_GET(
  887. self, request: SynapseRequest, room_id: str
  888. ) -> Tuple[int, JsonDict]:
  889. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  890. max_rooms_per_space = parse_integer(request, "max_rooms_per_space")
  891. if max_rooms_per_space is not None and max_rooms_per_space < 0:
  892. raise SynapseError(
  893. 400,
  894. "Value for 'max_rooms_per_space' must be a non-negative integer",
  895. Codes.BAD_JSON,
  896. )
  897. return 200, await self._room_summary_handler.get_space_summary(
  898. requester.user.to_string(),
  899. room_id,
  900. suggested_only=parse_boolean(request, "suggested_only", default=False),
  901. max_rooms_per_space=max_rooms_per_space,
  902. )
  903. # TODO When switching to the stable endpoint, remove the POST handler.
  904. async def on_POST(
  905. self, request: SynapseRequest, room_id: str
  906. ) -> Tuple[int, JsonDict]:
  907. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  908. content = parse_json_object_from_request(request)
  909. suggested_only = content.get("suggested_only", False)
  910. if not isinstance(suggested_only, bool):
  911. raise SynapseError(
  912. 400, "'suggested_only' must be a boolean", Codes.BAD_JSON
  913. )
  914. max_rooms_per_space = content.get("max_rooms_per_space")
  915. if max_rooms_per_space is not None:
  916. if not isinstance(max_rooms_per_space, int):
  917. raise SynapseError(
  918. 400, "'max_rooms_per_space' must be an integer", Codes.BAD_JSON
  919. )
  920. if max_rooms_per_space < 0:
  921. raise SynapseError(
  922. 400,
  923. "Value for 'max_rooms_per_space' must be a non-negative integer",
  924. Codes.BAD_JSON,
  925. )
  926. return 200, await self._room_summary_handler.get_space_summary(
  927. requester.user.to_string(),
  928. room_id,
  929. suggested_only=suggested_only,
  930. max_rooms_per_space=max_rooms_per_space,
  931. )
  932. class RoomHierarchyRestServlet(RestServlet):
  933. PATTERNS = (
  934. re.compile(
  935. "^/_matrix/client/unstable/org.matrix.msc2946"
  936. "/rooms/(?P<room_id>[^/]*)/hierarchy$"
  937. ),
  938. )
  939. def __init__(self, hs: "HomeServer"):
  940. super().__init__()
  941. self._auth = hs.get_auth()
  942. self._room_summary_handler = hs.get_room_summary_handler()
  943. async def on_GET(
  944. self, request: SynapseRequest, room_id: str
  945. ) -> Tuple[int, JsonDict]:
  946. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  947. max_depth = parse_integer(request, "max_depth")
  948. if max_depth is not None and max_depth < 0:
  949. raise SynapseError(
  950. 400, "'max_depth' must be a non-negative integer", Codes.BAD_JSON
  951. )
  952. limit = parse_integer(request, "limit")
  953. if limit is not None and limit <= 0:
  954. raise SynapseError(
  955. 400, "'limit' must be a positive integer", Codes.BAD_JSON
  956. )
  957. return 200, await self._room_summary_handler.get_room_hierarchy(
  958. requester.user.to_string(),
  959. room_id,
  960. suggested_only=parse_boolean(request, "suggested_only", default=False),
  961. max_depth=max_depth,
  962. limit=limit,
  963. from_token=parse_string(request, "from"),
  964. )
  965. class RoomSummaryRestServlet(ResolveRoomIdMixin, RestServlet):
  966. PATTERNS = (
  967. re.compile(
  968. "^/_matrix/client/unstable/im.nheko.summary"
  969. "/rooms/(?P<room_identifier>[^/]*)/summary$"
  970. ),
  971. )
  972. def __init__(self, hs: "HomeServer"):
  973. super().__init__(hs)
  974. self._auth = hs.get_auth()
  975. self._room_summary_handler = hs.get_room_summary_handler()
  976. async def on_GET(
  977. self, request: SynapseRequest, room_identifier: str
  978. ) -> Tuple[int, JsonDict]:
  979. try:
  980. requester = await self._auth.get_user_by_req(request, allow_guest=True)
  981. requester_user_id: Optional[str] = requester.user.to_string()
  982. except MissingClientTokenError:
  983. # auth is optional
  984. requester_user_id = None
  985. # twisted.web.server.Request.args is incorrectly defined as Optional[Any]
  986. args: Dict[bytes, List[bytes]] = request.args # type: ignore
  987. remote_room_hosts = parse_strings_from_args(args, "via", required=False)
  988. room_id, remote_room_hosts = await self.resolve_room_id(
  989. room_identifier,
  990. remote_room_hosts,
  991. )
  992. return 200, await self._room_summary_handler.get_room_summary(
  993. requester_user_id,
  994. room_id,
  995. remote_room_hosts,
  996. )
  997. def register_servlets(
  998. hs: "HomeServer", http_server: HttpServer, is_worker: bool = False
  999. ) -> None:
  1000. RoomStateEventRestServlet(hs).register(http_server)
  1001. RoomMemberListRestServlet(hs).register(http_server)
  1002. JoinedRoomMemberListRestServlet(hs).register(http_server)
  1003. RoomMessageListRestServlet(hs).register(http_server)
  1004. JoinRoomAliasServlet(hs).register(http_server)
  1005. RoomMembershipRestServlet(hs).register(http_server)
  1006. RoomSendEventRestServlet(hs).register(http_server)
  1007. PublicRoomListRestServlet(hs).register(http_server)
  1008. RoomStateRestServlet(hs).register(http_server)
  1009. RoomRedactEventRestServlet(hs).register(http_server)
  1010. RoomTypingRestServlet(hs).register(http_server)
  1011. RoomEventContextServlet(hs).register(http_server)
  1012. RoomSpaceSummaryRestServlet(hs).register(http_server)
  1013. RoomHierarchyRestServlet(hs).register(http_server)
  1014. if hs.config.experimental.msc3266_enabled:
  1015. RoomSummaryRestServlet(hs).register(http_server)
  1016. RoomEventServlet(hs).register(http_server)
  1017. JoinedRoomsRestServlet(hs).register(http_server)
  1018. RoomAliasListServlet(hs).register(http_server)
  1019. SearchRestServlet(hs).register(http_server)
  1020. RoomCreateRestServlet(hs).register(http_server)
  1021. # Some servlets only get registered for the main process.
  1022. if not is_worker:
  1023. RoomForgetRestServlet(hs).register(http_server)
  1024. def register_deprecated_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
  1025. RoomInitialSyncRestServlet(hs).register(http_server)