room.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. # Copyright 2018 New Vector Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """ This module contains REST servlets to do with rooms: /rooms/<paths> """
  17. import logging
  18. from six.moves.urllib import parse as urlparse
  19. from canonicaljson import json
  20. from twisted.internet import defer
  21. from synapse.api.constants import EventTypes, Membership
  22. from synapse.api.errors import (
  23. AuthError,
  24. Codes,
  25. InvalidClientCredentialsError,
  26. SynapseError,
  27. )
  28. from synapse.api.filtering import Filter
  29. from synapse.events.utils import format_event_for_client_v2
  30. from synapse.http.servlet import (
  31. RestServlet,
  32. assert_params_in_dict,
  33. parse_integer,
  34. parse_json_object_from_request,
  35. parse_string,
  36. )
  37. from synapse.rest.client.transactions import HttpTransactionCache
  38. from synapse.rest.client.v2_alpha._base import client_patterns
  39. from synapse.storage.state import StateFilter
  40. from synapse.streams.config import PaginationConfig
  41. from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID, UserID
  42. logger = logging.getLogger(__name__)
  43. class TransactionRestServlet(RestServlet):
  44. def __init__(self, hs):
  45. super(TransactionRestServlet, self).__init__()
  46. self.txns = HttpTransactionCache(hs)
  47. class RoomCreateRestServlet(TransactionRestServlet):
  48. # No PATTERN; we have custom dispatch rules here
  49. def __init__(self, hs):
  50. super(RoomCreateRestServlet, self).__init__(hs)
  51. self._room_creation_handler = hs.get_room_creation_handler()
  52. self.auth = hs.get_auth()
  53. def register(self, http_server):
  54. PATTERNS = "/createRoom"
  55. register_txn_path(self, PATTERNS, http_server)
  56. # define CORS for all of /rooms in RoomCreateRestServlet for simplicity
  57. http_server.register_paths(
  58. "OPTIONS", client_patterns("/rooms(?:/.*)?$", v1=True), self.on_OPTIONS
  59. )
  60. # define CORS for /createRoom[/txnid]
  61. http_server.register_paths(
  62. "OPTIONS", client_patterns("/createRoom(?:/.*)?$", v1=True), self.on_OPTIONS
  63. )
  64. def on_PUT(self, request, txn_id):
  65. return self.txns.fetch_or_execute_request(request, self.on_POST, request)
  66. @defer.inlineCallbacks
  67. def on_POST(self, request):
  68. requester = yield self.auth.get_user_by_req(request)
  69. info = yield self._room_creation_handler.create_room(
  70. requester, self.get_room_config(request)
  71. )
  72. return (200, info)
  73. def get_room_config(self, request):
  74. user_supplied_config = parse_json_object_from_request(request)
  75. return user_supplied_config
  76. def on_OPTIONS(self, request):
  77. return (200, {})
  78. # TODO: Needs unit testing for generic events
  79. class RoomStateEventRestServlet(TransactionRestServlet):
  80. def __init__(self, hs):
  81. super(RoomStateEventRestServlet, self).__init__(hs)
  82. self.handlers = hs.get_handlers()
  83. self.event_creation_handler = hs.get_event_creation_handler()
  84. self.room_member_handler = hs.get_room_member_handler()
  85. self.message_handler = hs.get_message_handler()
  86. self.auth = hs.get_auth()
  87. def register(self, http_server):
  88. # /room/$roomid/state/$eventtype
  89. no_state_key = "/rooms/(?P<room_id>[^/]*)/state/(?P<event_type>[^/]*)$"
  90. # /room/$roomid/state/$eventtype/$statekey
  91. state_key = (
  92. "/rooms/(?P<room_id>[^/]*)/state/"
  93. "(?P<event_type>[^/]*)/(?P<state_key>[^/]*)$"
  94. )
  95. http_server.register_paths(
  96. "GET", client_patterns(state_key, v1=True), self.on_GET
  97. )
  98. http_server.register_paths(
  99. "PUT", client_patterns(state_key, v1=True), self.on_PUT
  100. )
  101. http_server.register_paths(
  102. "GET", client_patterns(no_state_key, v1=True), self.on_GET_no_state_key
  103. )
  104. http_server.register_paths(
  105. "PUT", client_patterns(no_state_key, v1=True), self.on_PUT_no_state_key
  106. )
  107. def on_GET_no_state_key(self, request, room_id, event_type):
  108. return self.on_GET(request, room_id, event_type, "")
  109. def on_PUT_no_state_key(self, request, room_id, event_type):
  110. return self.on_PUT(request, room_id, event_type, "")
  111. @defer.inlineCallbacks
  112. def on_GET(self, request, room_id, event_type, state_key):
  113. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  114. format = parse_string(
  115. request, "format", default="content", allowed_values=["content", "event"]
  116. )
  117. msg_handler = self.message_handler
  118. data = yield msg_handler.get_room_data(
  119. user_id=requester.user.to_string(),
  120. room_id=room_id,
  121. event_type=event_type,
  122. state_key=state_key,
  123. is_guest=requester.is_guest,
  124. )
  125. if not data:
  126. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  127. if format == "event":
  128. event = format_event_for_client_v2(data.get_dict())
  129. return (200, event)
  130. elif format == "content":
  131. return (200, data.get_dict()["content"])
  132. @defer.inlineCallbacks
  133. def on_PUT(self, request, room_id, event_type, state_key, txn_id=None):
  134. requester = yield self.auth.get_user_by_req(request)
  135. content = parse_json_object_from_request(request)
  136. event_dict = {
  137. "type": event_type,
  138. "content": content,
  139. "room_id": room_id,
  140. "sender": requester.user.to_string(),
  141. }
  142. if state_key is not None:
  143. event_dict["state_key"] = state_key
  144. if event_type == EventTypes.Member:
  145. membership = content.get("membership", None)
  146. event = yield self.room_member_handler.update_membership(
  147. requester,
  148. target=UserID.from_string(state_key),
  149. room_id=room_id,
  150. action=membership,
  151. content=content,
  152. )
  153. else:
  154. event = yield self.event_creation_handler.create_and_send_nonmember_event(
  155. requester, event_dict, txn_id=txn_id
  156. )
  157. ret = {}
  158. if event:
  159. ret = {"event_id": event.event_id}
  160. return (200, ret)
  161. # TODO: Needs unit testing for generic events + feedback
  162. class RoomSendEventRestServlet(TransactionRestServlet):
  163. def __init__(self, hs):
  164. super(RoomSendEventRestServlet, self).__init__(hs)
  165. self.event_creation_handler = hs.get_event_creation_handler()
  166. self.auth = hs.get_auth()
  167. def register(self, http_server):
  168. # /rooms/$roomid/send/$event_type[/$txn_id]
  169. PATTERNS = "/rooms/(?P<room_id>[^/]*)/send/(?P<event_type>[^/]*)"
  170. register_txn_path(self, PATTERNS, http_server, with_get=True)
  171. @defer.inlineCallbacks
  172. def on_POST(self, request, room_id, event_type, txn_id=None):
  173. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  174. content = parse_json_object_from_request(request)
  175. event_dict = {
  176. "type": event_type,
  177. "content": content,
  178. "room_id": room_id,
  179. "sender": requester.user.to_string(),
  180. }
  181. if b"ts" in request.args and requester.app_service:
  182. event_dict["origin_server_ts"] = parse_integer(request, "ts", 0)
  183. event = yield self.event_creation_handler.create_and_send_nonmember_event(
  184. requester, event_dict, txn_id=txn_id
  185. )
  186. return (200, {"event_id": event.event_id})
  187. def on_GET(self, request, room_id, event_type, txn_id):
  188. return (200, "Not implemented")
  189. def on_PUT(self, request, room_id, event_type, txn_id):
  190. return self.txns.fetch_or_execute_request(
  191. request, self.on_POST, request, room_id, event_type, txn_id
  192. )
  193. # TODO: Needs unit testing for room ID + alias joins
  194. class JoinRoomAliasServlet(TransactionRestServlet):
  195. def __init__(self, hs):
  196. super(JoinRoomAliasServlet, self).__init__(hs)
  197. self.room_member_handler = hs.get_room_member_handler()
  198. self.auth = hs.get_auth()
  199. def register(self, http_server):
  200. # /join/$room_identifier[/$txn_id]
  201. PATTERNS = "/join/(?P<room_identifier>[^/]*)"
  202. register_txn_path(self, PATTERNS, http_server)
  203. @defer.inlineCallbacks
  204. def on_POST(self, request, room_identifier, txn_id=None):
  205. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  206. try:
  207. content = parse_json_object_from_request(request)
  208. except Exception:
  209. # Turns out we used to ignore the body entirely, and some clients
  210. # cheekily send invalid bodies.
  211. content = {}
  212. if RoomID.is_valid(room_identifier):
  213. room_id = room_identifier
  214. try:
  215. remote_room_hosts = [
  216. x.decode("ascii") for x in request.args[b"server_name"]
  217. ]
  218. except Exception:
  219. remote_room_hosts = None
  220. elif RoomAlias.is_valid(room_identifier):
  221. handler = self.room_member_handler
  222. room_alias = RoomAlias.from_string(room_identifier)
  223. room_id, remote_room_hosts = yield handler.lookup_room_alias(room_alias)
  224. room_id = room_id.to_string()
  225. else:
  226. raise SynapseError(
  227. 400, "%s was not legal room ID or room alias" % (room_identifier,)
  228. )
  229. yield self.room_member_handler.update_membership(
  230. requester=requester,
  231. target=requester.user,
  232. room_id=room_id,
  233. action="join",
  234. txn_id=txn_id,
  235. remote_room_hosts=remote_room_hosts,
  236. content=content,
  237. third_party_signed=content.get("third_party_signed", None),
  238. )
  239. return (200, {"room_id": room_id})
  240. def on_PUT(self, request, room_identifier, txn_id):
  241. return self.txns.fetch_or_execute_request(
  242. request, self.on_POST, request, room_identifier, txn_id
  243. )
  244. # TODO: Needs unit testing
  245. class PublicRoomListRestServlet(TransactionRestServlet):
  246. PATTERNS = client_patterns("/publicRooms$", v1=True)
  247. def __init__(self, hs):
  248. super(PublicRoomListRestServlet, self).__init__(hs)
  249. self.hs = hs
  250. self.auth = hs.get_auth()
  251. @defer.inlineCallbacks
  252. def on_GET(self, request):
  253. server = parse_string(request, "server", default=None)
  254. try:
  255. yield self.auth.get_user_by_req(request, allow_guest=True)
  256. except InvalidClientCredentialsError as e:
  257. # Option to allow servers to require auth when accessing
  258. # /publicRooms via CS API. This is especially helpful in private
  259. # federations.
  260. if not self.hs.config.allow_public_rooms_without_auth:
  261. raise
  262. # We allow people to not be authed if they're just looking at our
  263. # room list, but require auth when we proxy the request.
  264. # In both cases we call the auth function, as that has the side
  265. # effect of logging who issued this request if an access token was
  266. # provided.
  267. if server:
  268. raise e
  269. else:
  270. pass
  271. limit = parse_integer(request, "limit", 0)
  272. since_token = parse_string(request, "since", None)
  273. handler = self.hs.get_room_list_handler()
  274. if server:
  275. data = yield handler.get_remote_public_room_list(
  276. server, limit=limit, since_token=since_token
  277. )
  278. else:
  279. data = yield handler.get_local_public_room_list(
  280. limit=limit, since_token=since_token
  281. )
  282. return (200, data)
  283. @defer.inlineCallbacks
  284. def on_POST(self, request):
  285. yield self.auth.get_user_by_req(request, allow_guest=True)
  286. server = parse_string(request, "server", default=None)
  287. content = parse_json_object_from_request(request)
  288. limit = int(content.get("limit", 100))
  289. since_token = content.get("since", None)
  290. search_filter = content.get("filter", None)
  291. include_all_networks = content.get("include_all_networks", False)
  292. third_party_instance_id = content.get("third_party_instance_id", None)
  293. if include_all_networks:
  294. network_tuple = None
  295. if third_party_instance_id is not None:
  296. raise SynapseError(
  297. 400, "Can't use include_all_networks with an explicit network"
  298. )
  299. elif third_party_instance_id is None:
  300. network_tuple = ThirdPartyInstanceID(None, None)
  301. else:
  302. network_tuple = ThirdPartyInstanceID.from_string(third_party_instance_id)
  303. handler = self.hs.get_room_list_handler()
  304. if server:
  305. data = yield handler.get_remote_public_room_list(
  306. server,
  307. limit=limit,
  308. since_token=since_token,
  309. search_filter=search_filter,
  310. include_all_networks=include_all_networks,
  311. third_party_instance_id=third_party_instance_id,
  312. )
  313. else:
  314. data = yield handler.get_local_public_room_list(
  315. limit=limit,
  316. since_token=since_token,
  317. search_filter=search_filter,
  318. network_tuple=network_tuple,
  319. )
  320. return (200, data)
  321. # TODO: Needs unit testing
  322. class RoomMemberListRestServlet(RestServlet):
  323. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/members$", v1=True)
  324. def __init__(self, hs):
  325. super(RoomMemberListRestServlet, self).__init__()
  326. self.message_handler = hs.get_message_handler()
  327. self.auth = hs.get_auth()
  328. @defer.inlineCallbacks
  329. def on_GET(self, request, room_id):
  330. # TODO support Pagination stream API (limit/tokens)
  331. requester = yield self.auth.get_user_by_req(request)
  332. handler = self.message_handler
  333. # request the state as of a given event, as identified by a stream token,
  334. # for consistency with /messages etc.
  335. # useful for getting the membership in retrospect as of a given /sync
  336. # response.
  337. at_token_string = parse_string(request, "at")
  338. if at_token_string is None:
  339. at_token = None
  340. else:
  341. at_token = StreamToken.from_string(at_token_string)
  342. # let you filter down on particular memberships.
  343. # XXX: this may not be the best shape for this API - we could pass in a filter
  344. # instead, except filters aren't currently aware of memberships.
  345. # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details.
  346. membership = parse_string(request, "membership")
  347. not_membership = parse_string(request, "not_membership")
  348. events = yield handler.get_state_events(
  349. room_id=room_id,
  350. user_id=requester.user.to_string(),
  351. at_token=at_token,
  352. state_filter=StateFilter.from_types([(EventTypes.Member, None)]),
  353. )
  354. chunk = []
  355. for event in events:
  356. if (membership and event["content"].get("membership") != membership) or (
  357. not_membership and event["content"].get("membership") == not_membership
  358. ):
  359. continue
  360. chunk.append(event)
  361. return (200, {"chunk": chunk})
  362. # deprecated in favour of /members?membership=join?
  363. # except it does custom AS logic and has a simpler return format
  364. class JoinedRoomMemberListRestServlet(RestServlet):
  365. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/joined_members$", v1=True)
  366. def __init__(self, hs):
  367. super(JoinedRoomMemberListRestServlet, self).__init__()
  368. self.message_handler = hs.get_message_handler()
  369. self.auth = hs.get_auth()
  370. @defer.inlineCallbacks
  371. def on_GET(self, request, room_id):
  372. requester = yield self.auth.get_user_by_req(request)
  373. users_with_profile = yield self.message_handler.get_joined_members(
  374. requester, room_id
  375. )
  376. return (200, {"joined": users_with_profile})
  377. # TODO: Needs better unit testing
  378. class RoomMessageListRestServlet(RestServlet):
  379. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/messages$", v1=True)
  380. def __init__(self, hs):
  381. super(RoomMessageListRestServlet, self).__init__()
  382. self.pagination_handler = hs.get_pagination_handler()
  383. self.auth = hs.get_auth()
  384. @defer.inlineCallbacks
  385. def on_GET(self, request, room_id):
  386. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  387. pagination_config = PaginationConfig.from_request(request, default_limit=10)
  388. as_client_event = b"raw" not in request.args
  389. filter_bytes = parse_string(request, b"filter", encoding=None)
  390. if filter_bytes:
  391. filter_json = urlparse.unquote(filter_bytes.decode("UTF-8"))
  392. event_filter = Filter(json.loads(filter_json))
  393. if event_filter.filter_json.get("event_format", "client") == "federation":
  394. as_client_event = False
  395. else:
  396. event_filter = None
  397. msgs = yield self.pagination_handler.get_messages(
  398. room_id=room_id,
  399. requester=requester,
  400. pagin_config=pagination_config,
  401. as_client_event=as_client_event,
  402. event_filter=event_filter,
  403. )
  404. return (200, msgs)
  405. # TODO: Needs unit testing
  406. class RoomStateRestServlet(RestServlet):
  407. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/state$", v1=True)
  408. def __init__(self, hs):
  409. super(RoomStateRestServlet, self).__init__()
  410. self.message_handler = hs.get_message_handler()
  411. self.auth = hs.get_auth()
  412. @defer.inlineCallbacks
  413. def on_GET(self, request, room_id):
  414. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  415. # Get all the current state for this room
  416. events = yield self.message_handler.get_state_events(
  417. room_id=room_id,
  418. user_id=requester.user.to_string(),
  419. is_guest=requester.is_guest,
  420. )
  421. return (200, events)
  422. # TODO: Needs unit testing
  423. class RoomInitialSyncRestServlet(RestServlet):
  424. PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/initialSync$", v1=True)
  425. def __init__(self, hs):
  426. super(RoomInitialSyncRestServlet, self).__init__()
  427. self.initial_sync_handler = hs.get_initial_sync_handler()
  428. self.auth = hs.get_auth()
  429. @defer.inlineCallbacks
  430. def on_GET(self, request, room_id):
  431. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  432. pagination_config = PaginationConfig.from_request(request)
  433. content = yield self.initial_sync_handler.room_initial_sync(
  434. room_id=room_id, requester=requester, pagin_config=pagination_config
  435. )
  436. return (200, content)
  437. class RoomEventServlet(RestServlet):
  438. PATTERNS = client_patterns(
  439. "/rooms/(?P<room_id>[^/]*)/event/(?P<event_id>[^/]*)$", v1=True
  440. )
  441. def __init__(self, hs):
  442. super(RoomEventServlet, self).__init__()
  443. self.clock = hs.get_clock()
  444. self.event_handler = hs.get_event_handler()
  445. self._event_serializer = hs.get_event_client_serializer()
  446. self.auth = hs.get_auth()
  447. @defer.inlineCallbacks
  448. def on_GET(self, request, room_id, event_id):
  449. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  450. event = yield self.event_handler.get_event(requester.user, room_id, event_id)
  451. time_now = self.clock.time_msec()
  452. if event:
  453. event = yield self._event_serializer.serialize_event(event, time_now)
  454. return (200, event)
  455. else:
  456. return (404, "Event not found.")
  457. class RoomEventContextServlet(RestServlet):
  458. PATTERNS = client_patterns(
  459. "/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$", v1=True
  460. )
  461. def __init__(self, hs):
  462. super(RoomEventContextServlet, self).__init__()
  463. self.clock = hs.get_clock()
  464. self.room_context_handler = hs.get_room_context_handler()
  465. self._event_serializer = hs.get_event_client_serializer()
  466. self.auth = hs.get_auth()
  467. @defer.inlineCallbacks
  468. def on_GET(self, request, room_id, event_id):
  469. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  470. limit = parse_integer(request, "limit", default=10)
  471. # picking the API shape for symmetry with /messages
  472. filter_bytes = parse_string(request, "filter")
  473. if filter_bytes:
  474. filter_json = urlparse.unquote(filter_bytes)
  475. event_filter = Filter(json.loads(filter_json))
  476. else:
  477. event_filter = None
  478. results = yield self.room_context_handler.get_event_context(
  479. requester.user, room_id, event_id, limit, event_filter
  480. )
  481. if not results:
  482. raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND)
  483. time_now = self.clock.time_msec()
  484. results["events_before"] = yield self._event_serializer.serialize_events(
  485. results["events_before"], time_now
  486. )
  487. results["event"] = yield self._event_serializer.serialize_event(
  488. results["event"], time_now
  489. )
  490. results["events_after"] = yield self._event_serializer.serialize_events(
  491. results["events_after"], time_now
  492. )
  493. results["state"] = yield self._event_serializer.serialize_events(
  494. results["state"], time_now
  495. )
  496. return (200, results)
  497. class RoomForgetRestServlet(TransactionRestServlet):
  498. def __init__(self, hs):
  499. super(RoomForgetRestServlet, self).__init__(hs)
  500. self.room_member_handler = hs.get_room_member_handler()
  501. self.auth = hs.get_auth()
  502. def register(self, http_server):
  503. PATTERNS = "/rooms/(?P<room_id>[^/]*)/forget"
  504. register_txn_path(self, PATTERNS, http_server)
  505. @defer.inlineCallbacks
  506. def on_POST(self, request, room_id, txn_id=None):
  507. requester = yield self.auth.get_user_by_req(request, allow_guest=False)
  508. yield self.room_member_handler.forget(user=requester.user, room_id=room_id)
  509. return (200, {})
  510. def on_PUT(self, request, room_id, txn_id):
  511. return self.txns.fetch_or_execute_request(
  512. request, self.on_POST, request, room_id, txn_id
  513. )
  514. # TODO: Needs unit testing
  515. class RoomMembershipRestServlet(TransactionRestServlet):
  516. def __init__(self, hs):
  517. super(RoomMembershipRestServlet, self).__init__(hs)
  518. self.room_member_handler = hs.get_room_member_handler()
  519. self.auth = hs.get_auth()
  520. def register(self, http_server):
  521. # /rooms/$roomid/[invite|join|leave]
  522. PATTERNS = (
  523. "/rooms/(?P<room_id>[^/]*)/"
  524. "(?P<membership_action>join|invite|leave|ban|unban|kick)"
  525. )
  526. register_txn_path(self, PATTERNS, http_server)
  527. @defer.inlineCallbacks
  528. def on_POST(self, request, room_id, membership_action, txn_id=None):
  529. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  530. if requester.is_guest and membership_action not in {
  531. Membership.JOIN,
  532. Membership.LEAVE,
  533. }:
  534. raise AuthError(403, "Guest access not allowed")
  535. try:
  536. content = parse_json_object_from_request(request)
  537. except Exception:
  538. # Turns out we used to ignore the body entirely, and some clients
  539. # cheekily send invalid bodies.
  540. content = {}
  541. if membership_action == "invite" and self._has_3pid_invite_keys(content):
  542. yield self.room_member_handler.do_3pid_invite(
  543. room_id,
  544. requester.user,
  545. content["medium"],
  546. content["address"],
  547. content["id_server"],
  548. requester,
  549. txn_id,
  550. )
  551. return (200, {})
  552. return
  553. target = requester.user
  554. if membership_action in ["invite", "ban", "unban", "kick"]:
  555. assert_params_in_dict(content, ["user_id"])
  556. target = UserID.from_string(content["user_id"])
  557. event_content = None
  558. if "reason" in content and membership_action in ["kick", "ban"]:
  559. event_content = {"reason": content["reason"]}
  560. yield self.room_member_handler.update_membership(
  561. requester=requester,
  562. target=target,
  563. room_id=room_id,
  564. action=membership_action,
  565. txn_id=txn_id,
  566. third_party_signed=content.get("third_party_signed", None),
  567. content=event_content,
  568. )
  569. return_value = {}
  570. if membership_action == "join":
  571. return_value["room_id"] = room_id
  572. return (200, return_value)
  573. def _has_3pid_invite_keys(self, content):
  574. for key in {"id_server", "medium", "address"}:
  575. if key not in content:
  576. return False
  577. return True
  578. def on_PUT(self, request, room_id, membership_action, txn_id):
  579. return self.txns.fetch_or_execute_request(
  580. request, self.on_POST, request, room_id, membership_action, txn_id
  581. )
  582. class RoomRedactEventRestServlet(TransactionRestServlet):
  583. def __init__(self, hs):
  584. super(RoomRedactEventRestServlet, self).__init__(hs)
  585. self.handlers = hs.get_handlers()
  586. self.event_creation_handler = hs.get_event_creation_handler()
  587. self.auth = hs.get_auth()
  588. def register(self, http_server):
  589. PATTERNS = "/rooms/(?P<room_id>[^/]*)/redact/(?P<event_id>[^/]*)"
  590. register_txn_path(self, PATTERNS, http_server)
  591. @defer.inlineCallbacks
  592. def on_POST(self, request, room_id, event_id, txn_id=None):
  593. requester = yield self.auth.get_user_by_req(request)
  594. content = parse_json_object_from_request(request)
  595. event = yield self.event_creation_handler.create_and_send_nonmember_event(
  596. requester,
  597. {
  598. "type": EventTypes.Redaction,
  599. "content": content,
  600. "room_id": room_id,
  601. "sender": requester.user.to_string(),
  602. "redacts": event_id,
  603. },
  604. txn_id=txn_id,
  605. )
  606. return (200, {"event_id": event.event_id})
  607. def on_PUT(self, request, room_id, event_id, txn_id):
  608. return self.txns.fetch_or_execute_request(
  609. request, self.on_POST, request, room_id, event_id, txn_id
  610. )
  611. class RoomTypingRestServlet(RestServlet):
  612. PATTERNS = client_patterns(
  613. "/rooms/(?P<room_id>[^/]*)/typing/(?P<user_id>[^/]*)$", v1=True
  614. )
  615. def __init__(self, hs):
  616. super(RoomTypingRestServlet, self).__init__()
  617. self.presence_handler = hs.get_presence_handler()
  618. self.typing_handler = hs.get_typing_handler()
  619. self.auth = hs.get_auth()
  620. @defer.inlineCallbacks
  621. def on_PUT(self, request, room_id, user_id):
  622. requester = yield self.auth.get_user_by_req(request)
  623. room_id = urlparse.unquote(room_id)
  624. target_user = UserID.from_string(urlparse.unquote(user_id))
  625. content = parse_json_object_from_request(request)
  626. yield self.presence_handler.bump_presence_active_time(requester.user)
  627. # Limit timeout to stop people from setting silly typing timeouts.
  628. timeout = min(content.get("timeout", 30000), 120000)
  629. if content["typing"]:
  630. yield self.typing_handler.started_typing(
  631. target_user=target_user,
  632. auth_user=requester.user,
  633. room_id=room_id,
  634. timeout=timeout,
  635. )
  636. else:
  637. yield self.typing_handler.stopped_typing(
  638. target_user=target_user, auth_user=requester.user, room_id=room_id
  639. )
  640. return (200, {})
  641. class SearchRestServlet(RestServlet):
  642. PATTERNS = client_patterns("/search$", v1=True)
  643. def __init__(self, hs):
  644. super(SearchRestServlet, self).__init__()
  645. self.handlers = hs.get_handlers()
  646. self.auth = hs.get_auth()
  647. @defer.inlineCallbacks
  648. def on_POST(self, request):
  649. requester = yield self.auth.get_user_by_req(request)
  650. content = parse_json_object_from_request(request)
  651. batch = parse_string(request, "next_batch")
  652. results = yield self.handlers.search_handler.search(
  653. requester.user, content, batch
  654. )
  655. return (200, results)
  656. class JoinedRoomsRestServlet(RestServlet):
  657. PATTERNS = client_patterns("/joined_rooms$", v1=True)
  658. def __init__(self, hs):
  659. super(JoinedRoomsRestServlet, self).__init__()
  660. self.store = hs.get_datastore()
  661. self.auth = hs.get_auth()
  662. @defer.inlineCallbacks
  663. def on_GET(self, request):
  664. requester = yield self.auth.get_user_by_req(request, allow_guest=True)
  665. room_ids = yield self.store.get_rooms_for_user(requester.user.to_string())
  666. return (200, {"joined_rooms": list(room_ids)})
  667. def register_txn_path(servlet, regex_string, http_server, with_get=False):
  668. """Registers a transaction-based path.
  669. This registers two paths:
  670. PUT regex_string/$txnid
  671. POST regex_string
  672. Args:
  673. regex_string (str): The regex string to register. Must NOT have a
  674. trailing $ as this string will be appended to.
  675. http_server : The http_server to register paths with.
  676. with_get: True to also register respective GET paths for the PUTs.
  677. """
  678. http_server.register_paths(
  679. "POST", client_patterns(regex_string + "$", v1=True), servlet.on_POST
  680. )
  681. http_server.register_paths(
  682. "PUT",
  683. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  684. servlet.on_PUT,
  685. )
  686. if with_get:
  687. http_server.register_paths(
  688. "GET",
  689. client_patterns(regex_string + "/(?P<txn_id>[^/]*)$", v1=True),
  690. servlet.on_GET,
  691. )
  692. def register_servlets(hs, http_server):
  693. RoomStateEventRestServlet(hs).register(http_server)
  694. RoomCreateRestServlet(hs).register(http_server)
  695. RoomMemberListRestServlet(hs).register(http_server)
  696. JoinedRoomMemberListRestServlet(hs).register(http_server)
  697. RoomMessageListRestServlet(hs).register(http_server)
  698. JoinRoomAliasServlet(hs).register(http_server)
  699. RoomForgetRestServlet(hs).register(http_server)
  700. RoomMembershipRestServlet(hs).register(http_server)
  701. RoomSendEventRestServlet(hs).register(http_server)
  702. PublicRoomListRestServlet(hs).register(http_server)
  703. RoomStateRestServlet(hs).register(http_server)
  704. RoomRedactEventRestServlet(hs).register(http_server)
  705. RoomTypingRestServlet(hs).register(http_server)
  706. SearchRestServlet(hs).register(http_server)
  707. JoinedRoomsRestServlet(hs).register(http_server)
  708. RoomEventServlet(hs).register(http_server)
  709. RoomEventContextServlet(hs).register(http_server)
  710. def register_deprecated_servlets(hs, http_server):
  711. RoomInitialSyncRestServlet(hs).register(http_server)