1
0

room.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 - 2016 OpenMarket 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. """Contains functions for performing events on rooms."""
  16. from twisted.internet import defer
  17. from ._base import BaseHandler
  18. from synapse.types import UserID, RoomAlias, RoomID, RoomStreamToken
  19. from synapse.api.constants import (
  20. EventTypes, JoinRules, RoomCreationPreset,
  21. )
  22. from synapse.api.errors import AuthError, StoreError, SynapseError
  23. from synapse.util import stringutils
  24. from synapse.util.async import concurrently_execute
  25. from synapse.util.caches.response_cache import ResponseCache
  26. from collections import OrderedDict
  27. import logging
  28. import math
  29. import string
  30. logger = logging.getLogger(__name__)
  31. id_server_scheme = "https://"
  32. class RoomCreationHandler(BaseHandler):
  33. PRESETS_DICT = {
  34. RoomCreationPreset.PRIVATE_CHAT: {
  35. "join_rules": JoinRules.INVITE,
  36. "history_visibility": "shared",
  37. "original_invitees_have_ops": False,
  38. },
  39. RoomCreationPreset.TRUSTED_PRIVATE_CHAT: {
  40. "join_rules": JoinRules.INVITE,
  41. "history_visibility": "shared",
  42. "original_invitees_have_ops": True,
  43. },
  44. RoomCreationPreset.PUBLIC_CHAT: {
  45. "join_rules": JoinRules.PUBLIC,
  46. "history_visibility": "shared",
  47. "original_invitees_have_ops": False,
  48. },
  49. }
  50. @defer.inlineCallbacks
  51. def create_room(self, requester, config):
  52. """ Creates a new room.
  53. Args:
  54. requester (Requester): The user who requested the room creation.
  55. config (dict) : A dict of configuration options.
  56. Returns:
  57. The new room ID.
  58. Raises:
  59. SynapseError if the room ID couldn't be stored, or something went
  60. horribly wrong.
  61. """
  62. user_id = requester.user.to_string()
  63. self.ratelimit(requester)
  64. if "room_alias_name" in config:
  65. for wchar in string.whitespace:
  66. if wchar in config["room_alias_name"]:
  67. raise SynapseError(400, "Invalid characters in room alias")
  68. room_alias = RoomAlias.create(
  69. config["room_alias_name"],
  70. self.hs.hostname,
  71. )
  72. mapping = yield self.store.get_association_from_room_alias(
  73. room_alias
  74. )
  75. if mapping:
  76. raise SynapseError(400, "Room alias already taken")
  77. else:
  78. room_alias = None
  79. invite_list = config.get("invite", [])
  80. for i in invite_list:
  81. try:
  82. UserID.from_string(i)
  83. except:
  84. raise SynapseError(400, "Invalid user_id: %s" % (i,))
  85. invite_3pid_list = config.get("invite_3pid", [])
  86. visibility = config.get("visibility", None)
  87. is_public = visibility == "public"
  88. # autogen room IDs and try to create it. We may clash, so just
  89. # try a few times till one goes through, giving up eventually.
  90. attempts = 0
  91. room_id = None
  92. while attempts < 5:
  93. try:
  94. random_string = stringutils.random_string(18)
  95. gen_room_id = RoomID.create(
  96. random_string,
  97. self.hs.hostname,
  98. )
  99. yield self.store.store_room(
  100. room_id=gen_room_id.to_string(),
  101. room_creator_user_id=user_id,
  102. is_public=is_public
  103. )
  104. room_id = gen_room_id.to_string()
  105. break
  106. except StoreError:
  107. attempts += 1
  108. if not room_id:
  109. raise StoreError(500, "Couldn't generate a room ID.")
  110. if room_alias:
  111. directory_handler = self.hs.get_handlers().directory_handler
  112. yield directory_handler.create_association(
  113. user_id=user_id,
  114. room_id=room_id,
  115. room_alias=room_alias,
  116. servers=[self.hs.hostname],
  117. )
  118. preset_config = config.get(
  119. "preset",
  120. RoomCreationPreset.PRIVATE_CHAT
  121. if visibility == "private"
  122. else RoomCreationPreset.PUBLIC_CHAT
  123. )
  124. raw_initial_state = config.get("initial_state", [])
  125. initial_state = OrderedDict()
  126. for val in raw_initial_state:
  127. initial_state[(val["type"], val.get("state_key", ""))] = val["content"]
  128. creation_content = config.get("creation_content", {})
  129. msg_handler = self.hs.get_handlers().message_handler
  130. room_member_handler = self.hs.get_handlers().room_member_handler
  131. yield self._send_events_for_new_room(
  132. requester,
  133. room_id,
  134. msg_handler,
  135. room_member_handler,
  136. preset_config=preset_config,
  137. invite_list=invite_list,
  138. initial_state=initial_state,
  139. creation_content=creation_content,
  140. room_alias=room_alias,
  141. )
  142. if "name" in config:
  143. name = config["name"]
  144. yield msg_handler.create_and_send_nonmember_event(
  145. requester,
  146. {
  147. "type": EventTypes.Name,
  148. "room_id": room_id,
  149. "sender": user_id,
  150. "state_key": "",
  151. "content": {"name": name},
  152. },
  153. ratelimit=False)
  154. if "topic" in config:
  155. topic = config["topic"]
  156. yield msg_handler.create_and_send_nonmember_event(
  157. requester,
  158. {
  159. "type": EventTypes.Topic,
  160. "room_id": room_id,
  161. "sender": user_id,
  162. "state_key": "",
  163. "content": {"topic": topic},
  164. },
  165. ratelimit=False)
  166. for invitee in invite_list:
  167. yield room_member_handler.update_membership(
  168. requester,
  169. UserID.from_string(invitee),
  170. room_id,
  171. "invite",
  172. ratelimit=False,
  173. )
  174. for invite_3pid in invite_3pid_list:
  175. id_server = invite_3pid["id_server"]
  176. address = invite_3pid["address"]
  177. medium = invite_3pid["medium"]
  178. yield self.hs.get_handlers().room_member_handler.do_3pid_invite(
  179. room_id,
  180. requester.user,
  181. medium,
  182. address,
  183. id_server,
  184. requester,
  185. txn_id=None,
  186. )
  187. result = {"room_id": room_id}
  188. if room_alias:
  189. result["room_alias"] = room_alias.to_string()
  190. yield directory_handler.send_room_alias_update_event(
  191. requester, user_id, room_id
  192. )
  193. defer.returnValue(result)
  194. @defer.inlineCallbacks
  195. def _send_events_for_new_room(
  196. self,
  197. creator, # A Requester object.
  198. room_id,
  199. msg_handler,
  200. room_member_handler,
  201. preset_config,
  202. invite_list,
  203. initial_state,
  204. creation_content,
  205. room_alias
  206. ):
  207. def create(etype, content, **kwargs):
  208. e = {
  209. "type": etype,
  210. "content": content,
  211. }
  212. e.update(event_keys)
  213. e.update(kwargs)
  214. return e
  215. @defer.inlineCallbacks
  216. def send(etype, content, **kwargs):
  217. event = create(etype, content, **kwargs)
  218. yield msg_handler.create_and_send_nonmember_event(
  219. creator,
  220. event,
  221. ratelimit=False
  222. )
  223. config = RoomCreationHandler.PRESETS_DICT[preset_config]
  224. creator_id = creator.user.to_string()
  225. event_keys = {
  226. "room_id": room_id,
  227. "sender": creator_id,
  228. "state_key": "",
  229. }
  230. creation_content.update({"creator": creator_id})
  231. yield send(
  232. etype=EventTypes.Create,
  233. content=creation_content,
  234. )
  235. yield room_member_handler.update_membership(
  236. creator,
  237. creator.user,
  238. room_id,
  239. "join",
  240. ratelimit=False,
  241. )
  242. if (EventTypes.PowerLevels, '') not in initial_state:
  243. power_level_content = {
  244. "users": {
  245. creator_id: 100,
  246. },
  247. "users_default": 0,
  248. "events": {
  249. EventTypes.Name: 50,
  250. EventTypes.PowerLevels: 100,
  251. EventTypes.RoomHistoryVisibility: 100,
  252. EventTypes.CanonicalAlias: 50,
  253. EventTypes.RoomAvatar: 50,
  254. },
  255. "events_default": 0,
  256. "state_default": 50,
  257. "ban": 50,
  258. "kick": 50,
  259. "redact": 50,
  260. "invite": 0,
  261. }
  262. if config["original_invitees_have_ops"]:
  263. for invitee in invite_list:
  264. power_level_content["users"][invitee] = 100
  265. yield send(
  266. etype=EventTypes.PowerLevels,
  267. content=power_level_content,
  268. )
  269. if room_alias and (EventTypes.CanonicalAlias, '') not in initial_state:
  270. yield send(
  271. etype=EventTypes.CanonicalAlias,
  272. content={"alias": room_alias.to_string()},
  273. )
  274. if (EventTypes.JoinRules, '') not in initial_state:
  275. yield send(
  276. etype=EventTypes.JoinRules,
  277. content={"join_rule": config["join_rules"]},
  278. )
  279. if (EventTypes.RoomHistoryVisibility, '') not in initial_state:
  280. yield send(
  281. etype=EventTypes.RoomHistoryVisibility,
  282. content={"history_visibility": config["history_visibility"]}
  283. )
  284. for (etype, state_key), content in initial_state.items():
  285. yield send(
  286. etype=etype,
  287. state_key=state_key,
  288. content=content,
  289. )
  290. class RoomListHandler(BaseHandler):
  291. def __init__(self, hs):
  292. super(RoomListHandler, self).__init__(hs)
  293. self.response_cache = ResponseCache()
  294. def get_public_room_list(self):
  295. result = self.response_cache.get(())
  296. if not result:
  297. result = self.response_cache.set((), self._get_public_room_list())
  298. return result
  299. @defer.inlineCallbacks
  300. def _get_public_room_list(self):
  301. room_ids = yield self.store.get_public_room_ids()
  302. results = []
  303. @defer.inlineCallbacks
  304. def handle_room(room_id):
  305. aliases = yield self.store.get_aliases_for_room(room_id)
  306. # We pull each bit of state out indvidually to avoid pulling the
  307. # full state into memory. Due to how the caching works this should
  308. # be fairly quick, even if not originally in the cache.
  309. def get_state(etype, state_key):
  310. return self.state_handler.get_current_state(room_id, etype, state_key)
  311. # Double check that this is actually a public room.
  312. join_rules_event = yield get_state(EventTypes.JoinRules, "")
  313. if join_rules_event:
  314. join_rule = join_rules_event.content.get("join_rule", None)
  315. if join_rule and join_rule != JoinRules.PUBLIC:
  316. defer.returnValue(None)
  317. result = {"room_id": room_id}
  318. if aliases:
  319. result["aliases"] = aliases
  320. name_event = yield get_state(EventTypes.Name, "")
  321. if name_event:
  322. name = name_event.content.get("name", None)
  323. if name:
  324. result["name"] = name
  325. topic_event = yield get_state(EventTypes.Topic, "")
  326. if topic_event:
  327. topic = topic_event.content.get("topic", None)
  328. if topic:
  329. result["topic"] = topic
  330. canonical_event = yield get_state(EventTypes.CanonicalAlias, "")
  331. if canonical_event:
  332. canonical_alias = canonical_event.content.get("alias", None)
  333. if canonical_alias:
  334. result["canonical_alias"] = canonical_alias
  335. visibility_event = yield get_state(EventTypes.RoomHistoryVisibility, "")
  336. visibility = None
  337. if visibility_event:
  338. visibility = visibility_event.content.get("history_visibility", None)
  339. result["world_readable"] = visibility == "world_readable"
  340. guest_event = yield get_state(EventTypes.GuestAccess, "")
  341. guest = None
  342. if guest_event:
  343. guest = guest_event.content.get("guest_access", None)
  344. result["guest_can_join"] = guest == "can_join"
  345. avatar_event = yield get_state("m.room.avatar", "")
  346. if avatar_event:
  347. avatar_url = avatar_event.content.get("url", None)
  348. if avatar_url:
  349. result["avatar_url"] = avatar_url
  350. joined_users = yield self.store.get_users_in_room(room_id)
  351. result["num_joined_members"] = len(joined_users)
  352. results.append(result)
  353. yield concurrently_execute(handle_room, room_ids, 10)
  354. # FIXME (erikj): START is no longer a valid value
  355. defer.returnValue({"start": "START", "end": "END", "chunk": results})
  356. class RoomContextHandler(BaseHandler):
  357. @defer.inlineCallbacks
  358. def get_event_context(self, user, room_id, event_id, limit, is_guest):
  359. """Retrieves events, pagination tokens and state around a given event
  360. in a room.
  361. Args:
  362. user (UserID)
  363. room_id (str)
  364. event_id (str)
  365. limit (int): The maximum number of events to return in total
  366. (excluding state).
  367. Returns:
  368. dict, or None if the event isn't found
  369. """
  370. before_limit = math.floor(limit / 2.)
  371. after_limit = limit - before_limit
  372. now_token = yield self.hs.get_event_sources().get_current_token()
  373. def filter_evts(events):
  374. return self._filter_events_for_client(
  375. user.to_string(),
  376. events,
  377. is_peeking=is_guest)
  378. event = yield self.store.get_event(event_id, get_prev_content=True,
  379. allow_none=True)
  380. if not event:
  381. defer.returnValue(None)
  382. return
  383. filtered = yield(filter_evts([event]))
  384. if not filtered:
  385. raise AuthError(
  386. 403,
  387. "You don't have permission to access that event."
  388. )
  389. results = yield self.store.get_events_around(
  390. room_id, event_id, before_limit, after_limit
  391. )
  392. results["events_before"] = yield filter_evts(results["events_before"])
  393. results["events_after"] = yield filter_evts(results["events_after"])
  394. results["event"] = event
  395. if results["events_after"]:
  396. last_event_id = results["events_after"][-1].event_id
  397. else:
  398. last_event_id = event_id
  399. state = yield self.store.get_state_for_events(
  400. [last_event_id], None
  401. )
  402. results["state"] = state[last_event_id].values()
  403. results["start"] = now_token.copy_and_replace(
  404. "room_key", results["start"]
  405. ).to_string()
  406. results["end"] = now_token.copy_and_replace(
  407. "room_key", results["end"]
  408. ).to_string()
  409. defer.returnValue(results)
  410. class RoomEventSource(object):
  411. def __init__(self, hs):
  412. self.store = hs.get_datastore()
  413. @defer.inlineCallbacks
  414. def get_new_events(
  415. self,
  416. user,
  417. from_key,
  418. limit,
  419. room_ids,
  420. is_guest,
  421. ):
  422. # We just ignore the key for now.
  423. to_key = yield self.get_current_key()
  424. from_token = RoomStreamToken.parse(from_key)
  425. if from_token.topological:
  426. logger.warn("Stream has topological part!!!! %r", from_key)
  427. from_key = "s%s" % (from_token.stream,)
  428. app_service = yield self.store.get_app_service_by_user_id(
  429. user.to_string()
  430. )
  431. if app_service:
  432. events, end_key = yield self.store.get_appservice_room_stream(
  433. service=app_service,
  434. from_key=from_key,
  435. to_key=to_key,
  436. limit=limit,
  437. )
  438. else:
  439. room_events = yield self.store.get_membership_changes_for_user(
  440. user.to_string(), from_key, to_key
  441. )
  442. room_to_events = yield self.store.get_room_events_stream_for_rooms(
  443. room_ids=room_ids,
  444. from_key=from_key,
  445. to_key=to_key,
  446. limit=limit or 10,
  447. order='ASC',
  448. )
  449. events = list(room_events)
  450. events.extend(e for evs, _ in room_to_events.values() for e in evs)
  451. events.sort(key=lambda e: e.internal_metadata.order)
  452. if limit:
  453. events[:] = events[:limit]
  454. if events:
  455. end_key = events[-1].internal_metadata.after
  456. else:
  457. end_key = to_key
  458. defer.returnValue((events, end_key))
  459. def get_current_key(self, direction='f'):
  460. return self.store.get_room_events_max_id(direction)
  461. @defer.inlineCallbacks
  462. def get_pagination_rows(self, user, config, key):
  463. events, next_key = yield self.store.paginate_room_events(
  464. room_id=key,
  465. from_key=config.from_key,
  466. to_key=config.to_key,
  467. direction=config.direction,
  468. limit=config.limit,
  469. )
  470. defer.returnValue((events, next_key))