room_member.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2018 New Vector Ltd
  4. # Copyright 2019 The Matrix.org Foundation C.I.C.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import abc
  18. import logging
  19. from six.moves import http_client
  20. from twisted.internet import defer
  21. from synapse import types
  22. from synapse.api.constants import EventTypes, Membership
  23. from synapse.api.errors import AuthError, Codes, SynapseError
  24. from synapse.types import RoomID, UserID
  25. from synapse.util.async_helpers import Linearizer
  26. from synapse.util.distributor import user_joined_room, user_left_room
  27. from ._base import BaseHandler
  28. logger = logging.getLogger(__name__)
  29. class RoomMemberHandler(object):
  30. # TODO(paul): This handler currently contains a messy conflation of
  31. # low-level API that works on UserID objects and so on, and REST-level
  32. # API that takes ID strings and returns pagination chunks. These concerns
  33. # ought to be separated out a lot better.
  34. __metaclass__ = abc.ABCMeta
  35. def __init__(self, hs):
  36. """
  37. Args:
  38. hs (synapse.server.HomeServer):
  39. """
  40. self.hs = hs
  41. self.store = hs.get_datastore()
  42. self.auth = hs.get_auth()
  43. self.state_handler = hs.get_state_handler()
  44. self.config = hs.config
  45. self.federation_handler = hs.get_handlers().federation_handler
  46. self.directory_handler = hs.get_handlers().directory_handler
  47. self.identity_handler = hs.get_handlers().identity_handler
  48. self.registration_handler = hs.get_registration_handler()
  49. self.profile_handler = hs.get_profile_handler()
  50. self.event_creation_handler = hs.get_event_creation_handler()
  51. self.member_linearizer = Linearizer(name="member")
  52. self.clock = hs.get_clock()
  53. self.spam_checker = hs.get_spam_checker()
  54. self.third_party_event_rules = hs.get_third_party_event_rules()
  55. self._server_notices_mxid = self.config.server_notices_mxid
  56. self._enable_lookup = hs.config.enable_3pid_lookup
  57. self.allow_per_room_profiles = self.config.allow_per_room_profiles
  58. # This is only used to get at ratelimit function, and
  59. # maybe_kick_guest_users. It's fine there are multiple of these as
  60. # it doesn't store state.
  61. self.base_handler = BaseHandler(hs)
  62. @abc.abstractmethod
  63. def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
  64. """Try and join a room that this server is not in
  65. Args:
  66. requester (Requester)
  67. remote_room_hosts (list[str]): List of servers that can be used
  68. to join via.
  69. room_id (str): Room that we are trying to join
  70. user (UserID): User who is trying to join
  71. content (dict): A dict that should be used as the content of the
  72. join event.
  73. Returns:
  74. Deferred
  75. """
  76. raise NotImplementedError()
  77. @abc.abstractmethod
  78. def _remote_reject_invite(
  79. self, requester, remote_room_hosts, room_id, target, content
  80. ):
  81. """Attempt to reject an invite for a room this server is not in. If we
  82. fail to do so we locally mark the invite as rejected.
  83. Args:
  84. requester (Requester)
  85. remote_room_hosts (list[str]): List of servers to use to try and
  86. reject invite
  87. room_id (str)
  88. target (UserID): The user rejecting the invite
  89. content (dict): The content for the rejection event
  90. Returns:
  91. Deferred[dict]: A dictionary to be returned to the client, may
  92. include event_id etc, or nothing if we locally rejected
  93. """
  94. raise NotImplementedError()
  95. @abc.abstractmethod
  96. def _user_joined_room(self, target, room_id):
  97. """Notifies distributor on master process that the user has joined the
  98. room.
  99. Args:
  100. target (UserID)
  101. room_id (str)
  102. Returns:
  103. Deferred|None
  104. """
  105. raise NotImplementedError()
  106. @abc.abstractmethod
  107. def _user_left_room(self, target, room_id):
  108. """Notifies distributor on master process that the user has left the
  109. room.
  110. Args:
  111. target (UserID)
  112. room_id (str)
  113. Returns:
  114. Deferred|None
  115. """
  116. raise NotImplementedError()
  117. @defer.inlineCallbacks
  118. def _local_membership_update(
  119. self,
  120. requester,
  121. target,
  122. room_id,
  123. membership,
  124. prev_events_and_hashes,
  125. txn_id=None,
  126. ratelimit=True,
  127. content=None,
  128. require_consent=True,
  129. ):
  130. user_id = target.to_string()
  131. if content is None:
  132. content = {}
  133. content["membership"] = membership
  134. if requester.is_guest:
  135. content["kind"] = "guest"
  136. event, context = yield self.event_creation_handler.create_event(
  137. requester,
  138. {
  139. "type": EventTypes.Member,
  140. "content": content,
  141. "room_id": room_id,
  142. "sender": requester.user.to_string(),
  143. "state_key": user_id,
  144. # For backwards compatibility:
  145. "membership": membership,
  146. },
  147. token_id=requester.access_token_id,
  148. txn_id=txn_id,
  149. prev_events_and_hashes=prev_events_and_hashes,
  150. require_consent=require_consent,
  151. )
  152. # Check if this event matches the previous membership event for the user.
  153. duplicate = yield self.event_creation_handler.deduplicate_state_event(
  154. event, context
  155. )
  156. if duplicate is not None:
  157. # Discard the new event since this membership change is a no-op.
  158. return duplicate
  159. yield self.event_creation_handler.handle_new_client_event(
  160. requester, event, context, extra_users=[target], ratelimit=ratelimit
  161. )
  162. prev_state_ids = yield context.get_prev_state_ids()
  163. prev_member_event_id = prev_state_ids.get((EventTypes.Member, user_id), None)
  164. if event.membership == Membership.JOIN:
  165. # Only fire user_joined_room if the user has actually joined the
  166. # room. Don't bother if the user is just changing their profile
  167. # info.
  168. newly_joined = True
  169. if prev_member_event_id:
  170. prev_member_event = yield self.store.get_event(prev_member_event_id)
  171. newly_joined = prev_member_event.membership != Membership.JOIN
  172. if newly_joined:
  173. yield self._user_joined_room(target, room_id)
  174. elif event.membership == Membership.LEAVE:
  175. if prev_member_event_id:
  176. prev_member_event = yield self.store.get_event(prev_member_event_id)
  177. if prev_member_event.membership == Membership.JOIN:
  178. yield self._user_left_room(target, room_id)
  179. return event
  180. @defer.inlineCallbacks
  181. def copy_room_tags_and_direct_to_room(self, old_room_id, new_room_id, user_id):
  182. """Copies the tags and direct room state from one room to another.
  183. Args:
  184. old_room_id (str)
  185. new_room_id (str)
  186. user_id (str)
  187. Returns:
  188. Deferred[None]
  189. """
  190. # Retrieve user account data for predecessor room
  191. user_account_data, _ = yield self.store.get_account_data_for_user(user_id)
  192. # Copy direct message state if applicable
  193. direct_rooms = user_account_data.get("m.direct", {})
  194. # Check which key this room is under
  195. if isinstance(direct_rooms, dict):
  196. for key, room_id_list in direct_rooms.items():
  197. if old_room_id in room_id_list and new_room_id not in room_id_list:
  198. # Add new room_id to this key
  199. direct_rooms[key].append(new_room_id)
  200. # Save back to user's m.direct account data
  201. yield self.store.add_account_data_for_user(
  202. user_id, "m.direct", direct_rooms
  203. )
  204. break
  205. # Copy room tags if applicable
  206. room_tags = yield self.store.get_tags_for_room(user_id, old_room_id)
  207. # Copy each room tag to the new room
  208. for tag, tag_content in room_tags.items():
  209. yield self.store.add_tag_to_room(user_id, new_room_id, tag, tag_content)
  210. @defer.inlineCallbacks
  211. def update_membership(
  212. self,
  213. requester,
  214. target,
  215. room_id,
  216. action,
  217. txn_id=None,
  218. remote_room_hosts=None,
  219. third_party_signed=None,
  220. ratelimit=True,
  221. content=None,
  222. require_consent=True,
  223. ):
  224. key = (room_id,)
  225. with (yield self.member_linearizer.queue(key)):
  226. result = yield self._update_membership(
  227. requester,
  228. target,
  229. room_id,
  230. action,
  231. txn_id=txn_id,
  232. remote_room_hosts=remote_room_hosts,
  233. third_party_signed=third_party_signed,
  234. ratelimit=ratelimit,
  235. content=content,
  236. require_consent=require_consent,
  237. )
  238. return result
  239. @defer.inlineCallbacks
  240. def _update_membership(
  241. self,
  242. requester,
  243. target,
  244. room_id,
  245. action,
  246. txn_id=None,
  247. remote_room_hosts=None,
  248. third_party_signed=None,
  249. ratelimit=True,
  250. content=None,
  251. require_consent=True,
  252. ):
  253. content_specified = bool(content)
  254. if content is None:
  255. content = {}
  256. else:
  257. # We do a copy here as we potentially change some keys
  258. # later on.
  259. content = dict(content)
  260. if not self.allow_per_room_profiles:
  261. # Strip profile data, knowing that new profile data will be added to the
  262. # event's content in event_creation_handler.create_event() using the target's
  263. # global profile.
  264. content.pop("displayname", None)
  265. content.pop("avatar_url", None)
  266. effective_membership_state = action
  267. if action in ["kick", "unban"]:
  268. effective_membership_state = "leave"
  269. # if this is a join with a 3pid signature, we may need to turn a 3pid
  270. # invite into a normal invite before we can handle the join.
  271. if third_party_signed is not None:
  272. yield self.federation_handler.exchange_third_party_invite(
  273. third_party_signed["sender"],
  274. target.to_string(),
  275. room_id,
  276. third_party_signed,
  277. )
  278. if not remote_room_hosts:
  279. remote_room_hosts = []
  280. if effective_membership_state not in ("leave", "ban"):
  281. is_blocked = yield self.store.is_room_blocked(room_id)
  282. if is_blocked:
  283. raise SynapseError(403, "This room has been blocked on this server")
  284. if effective_membership_state == Membership.INVITE:
  285. # block any attempts to invite the server notices mxid
  286. if target.to_string() == self._server_notices_mxid:
  287. raise SynapseError(http_client.FORBIDDEN, "Cannot invite this user")
  288. block_invite = False
  289. if (
  290. self._server_notices_mxid is not None
  291. and requester.user.to_string() == self._server_notices_mxid
  292. ):
  293. # allow the server notices mxid to send invites
  294. is_requester_admin = True
  295. else:
  296. is_requester_admin = yield self.auth.is_server_admin(requester.user)
  297. if not is_requester_admin:
  298. if self.config.block_non_admin_invites:
  299. logger.info(
  300. "Blocking invite: user is not admin and non-admin "
  301. "invites disabled"
  302. )
  303. block_invite = True
  304. if not self.spam_checker.user_may_invite(
  305. requester.user.to_string(), target.to_string(), room_id
  306. ):
  307. logger.info("Blocking invite due to spam checker")
  308. block_invite = True
  309. if block_invite:
  310. raise SynapseError(403, "Invites have been disabled on this server")
  311. prev_events_and_hashes = yield self.store.get_prev_events_for_room(room_id)
  312. latest_event_ids = (event_id for (event_id, _, _) in prev_events_and_hashes)
  313. current_state_ids = yield self.state_handler.get_current_state_ids(
  314. room_id, latest_event_ids=latest_event_ids
  315. )
  316. # TODO: Refactor into dictionary of explicitly allowed transitions
  317. # between old and new state, with specific error messages for some
  318. # transitions and generic otherwise
  319. old_state_id = current_state_ids.get((EventTypes.Member, target.to_string()))
  320. if old_state_id:
  321. old_state = yield self.store.get_event(old_state_id, allow_none=True)
  322. old_membership = old_state.content.get("membership") if old_state else None
  323. if action == "unban" and old_membership != "ban":
  324. raise SynapseError(
  325. 403,
  326. "Cannot unban user who was not banned"
  327. " (membership=%s)" % old_membership,
  328. errcode=Codes.BAD_STATE,
  329. )
  330. if old_membership == "ban" and action != "unban":
  331. raise SynapseError(
  332. 403,
  333. "Cannot %s user who was banned" % (action,),
  334. errcode=Codes.BAD_STATE,
  335. )
  336. if old_state:
  337. same_content = content == old_state.content
  338. same_membership = old_membership == effective_membership_state
  339. same_sender = requester.user.to_string() == old_state.sender
  340. if same_sender and same_membership and same_content:
  341. return old_state
  342. if old_membership in ["ban", "leave"] and action == "kick":
  343. raise AuthError(403, "The target user is not in the room")
  344. # we don't allow people to reject invites to the server notice
  345. # room, but they can leave it once they are joined.
  346. if (
  347. old_membership == Membership.INVITE
  348. and effective_membership_state == Membership.LEAVE
  349. ):
  350. is_blocked = yield self._is_server_notice_room(room_id)
  351. if is_blocked:
  352. raise SynapseError(
  353. http_client.FORBIDDEN,
  354. "You cannot reject this invite",
  355. errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
  356. )
  357. else:
  358. if action == "kick":
  359. raise AuthError(403, "The target user is not in the room")
  360. is_host_in_room = yield self._is_host_in_room(current_state_ids)
  361. if effective_membership_state == Membership.JOIN:
  362. if requester.is_guest:
  363. guest_can_join = yield self._can_guest_join(current_state_ids)
  364. if not guest_can_join:
  365. # This should be an auth check, but guests are a local concept,
  366. # so don't really fit into the general auth process.
  367. raise AuthError(403, "Guest access not allowed")
  368. if not is_host_in_room:
  369. inviter = yield self._get_inviter(target.to_string(), room_id)
  370. if inviter and not self.hs.is_mine(inviter):
  371. remote_room_hosts.append(inviter.domain)
  372. content["membership"] = Membership.JOIN
  373. profile = self.profile_handler
  374. if not content_specified:
  375. content["displayname"] = yield profile.get_displayname(target)
  376. content["avatar_url"] = yield profile.get_avatar_url(target)
  377. if requester.is_guest:
  378. content["kind"] = "guest"
  379. remote_join_response = yield self._remote_join(
  380. requester, remote_room_hosts, room_id, target, content
  381. )
  382. return remote_join_response
  383. elif effective_membership_state == Membership.LEAVE:
  384. if not is_host_in_room:
  385. # perhaps we've been invited
  386. inviter = yield self._get_inviter(target.to_string(), room_id)
  387. if not inviter:
  388. raise SynapseError(404, "Not a known room")
  389. if self.hs.is_mine(inviter):
  390. # the inviter was on our server, but has now left. Carry on
  391. # with the normal rejection codepath.
  392. #
  393. # This is a bit of a hack, because the room might still be
  394. # active on other servers.
  395. pass
  396. else:
  397. # send the rejection to the inviter's HS.
  398. remote_room_hosts = remote_room_hosts + [inviter.domain]
  399. res = yield self._remote_reject_invite(
  400. requester, remote_room_hosts, room_id, target, content,
  401. )
  402. return res
  403. res = yield self._local_membership_update(
  404. requester=requester,
  405. target=target,
  406. room_id=room_id,
  407. membership=effective_membership_state,
  408. txn_id=txn_id,
  409. ratelimit=ratelimit,
  410. prev_events_and_hashes=prev_events_and_hashes,
  411. content=content,
  412. require_consent=require_consent,
  413. )
  414. return res
  415. @defer.inlineCallbacks
  416. def transfer_room_state_on_room_upgrade(self, old_room_id, room_id):
  417. """Upon our server becoming aware of an upgraded room, either by upgrading a room
  418. ourselves or joining one, we can transfer over information from the previous room.
  419. Copies user state (tags/push rules) for every local user that was in the old room, as
  420. well as migrating the room directory state.
  421. Args:
  422. old_room_id (str): The ID of the old room
  423. room_id (str): The ID of the new room
  424. Returns:
  425. Deferred
  426. """
  427. # Find all local users that were in the old room and copy over each user's state
  428. users = yield self.store.get_users_in_room(old_room_id)
  429. yield self.copy_user_state_on_room_upgrade(old_room_id, room_id, users)
  430. # Add new room to the room directory if the old room was there
  431. # Remove old room from the room directory
  432. old_room = yield self.store.get_room(old_room_id)
  433. if old_room and old_room["is_public"]:
  434. yield self.store.set_room_is_public(old_room_id, False)
  435. yield self.store.set_room_is_public(room_id, True)
  436. # Check if any groups we own contain the predecessor room
  437. local_group_ids = yield self.store.get_local_groups_for_room(old_room_id)
  438. for group_id in local_group_ids:
  439. # Add new the new room to those groups
  440. yield self.store.add_room_to_group(group_id, room_id, old_room["is_public"])
  441. # Remove the old room from those groups
  442. yield self.store.remove_room_from_group(group_id, old_room_id)
  443. @defer.inlineCallbacks
  444. def copy_user_state_on_room_upgrade(self, old_room_id, new_room_id, user_ids):
  445. """Copy user-specific information when they join a new room when that new room is the
  446. result of a room upgrade
  447. Args:
  448. old_room_id (str): The ID of upgraded room
  449. new_room_id (str): The ID of the new room
  450. user_ids (Iterable[str]): User IDs to copy state for
  451. Returns:
  452. Deferred
  453. """
  454. logger.debug(
  455. "Copying over room tags and push rules from %s to %s for users %s",
  456. old_room_id,
  457. new_room_id,
  458. user_ids,
  459. )
  460. for user_id in user_ids:
  461. try:
  462. # It is an upgraded room. Copy over old tags
  463. yield self.copy_room_tags_and_direct_to_room(
  464. old_room_id, new_room_id, user_id
  465. )
  466. # Copy over push rules
  467. yield self.store.copy_push_rules_from_room_to_room_for_user(
  468. old_room_id, new_room_id, user_id
  469. )
  470. except Exception:
  471. logger.exception(
  472. "Error copying tags and/or push rules from rooms %s to %s for user %s. "
  473. "Skipping...",
  474. old_room_id,
  475. new_room_id,
  476. user_id,
  477. )
  478. continue
  479. @defer.inlineCallbacks
  480. def send_membership_event(self, requester, event, context, ratelimit=True):
  481. """
  482. Change the membership status of a user in a room.
  483. Args:
  484. requester (Requester): The local user who requested the membership
  485. event. If None, certain checks, like whether this homeserver can
  486. act as the sender, will be skipped.
  487. event (SynapseEvent): The membership event.
  488. context: The context of the event.
  489. ratelimit (bool): Whether to rate limit this request.
  490. Raises:
  491. SynapseError if there was a problem changing the membership.
  492. """
  493. target_user = UserID.from_string(event.state_key)
  494. room_id = event.room_id
  495. if requester is not None:
  496. sender = UserID.from_string(event.sender)
  497. assert (
  498. sender == requester.user
  499. ), "Sender (%s) must be same as requester (%s)" % (sender, requester.user)
  500. assert self.hs.is_mine(sender), "Sender must be our own: %s" % (sender,)
  501. else:
  502. requester = types.create_requester(target_user)
  503. prev_event = yield self.event_creation_handler.deduplicate_state_event(
  504. event, context
  505. )
  506. if prev_event is not None:
  507. return
  508. prev_state_ids = yield context.get_prev_state_ids()
  509. if event.membership == Membership.JOIN:
  510. if requester.is_guest:
  511. guest_can_join = yield self._can_guest_join(prev_state_ids)
  512. if not guest_can_join:
  513. # This should be an auth check, but guests are a local concept,
  514. # so don't really fit into the general auth process.
  515. raise AuthError(403, "Guest access not allowed")
  516. if event.membership not in (Membership.LEAVE, Membership.BAN):
  517. is_blocked = yield self.store.is_room_blocked(room_id)
  518. if is_blocked:
  519. raise SynapseError(403, "This room has been blocked on this server")
  520. yield self.event_creation_handler.handle_new_client_event(
  521. requester, event, context, extra_users=[target_user], ratelimit=ratelimit
  522. )
  523. prev_member_event_id = prev_state_ids.get(
  524. (EventTypes.Member, event.state_key), None
  525. )
  526. if event.membership == Membership.JOIN:
  527. # Only fire user_joined_room if the user has actually joined the
  528. # room. Don't bother if the user is just changing their profile
  529. # info.
  530. newly_joined = True
  531. if prev_member_event_id:
  532. prev_member_event = yield self.store.get_event(prev_member_event_id)
  533. newly_joined = prev_member_event.membership != Membership.JOIN
  534. if newly_joined:
  535. yield self._user_joined_room(target_user, room_id)
  536. elif event.membership == Membership.LEAVE:
  537. if prev_member_event_id:
  538. prev_member_event = yield self.store.get_event(prev_member_event_id)
  539. if prev_member_event.membership == Membership.JOIN:
  540. yield self._user_left_room(target_user, room_id)
  541. @defer.inlineCallbacks
  542. def _can_guest_join(self, current_state_ids):
  543. """
  544. Returns whether a guest can join a room based on its current state.
  545. """
  546. guest_access_id = current_state_ids.get((EventTypes.GuestAccess, ""), None)
  547. if not guest_access_id:
  548. return False
  549. guest_access = yield self.store.get_event(guest_access_id)
  550. return (
  551. guest_access
  552. and guest_access.content
  553. and "guest_access" in guest_access.content
  554. and guest_access.content["guest_access"] == "can_join"
  555. )
  556. @defer.inlineCallbacks
  557. def lookup_room_alias(self, room_alias):
  558. """
  559. Get the room ID associated with a room alias.
  560. Args:
  561. room_alias (RoomAlias): The alias to look up.
  562. Returns:
  563. A tuple of:
  564. The room ID as a RoomID object.
  565. Hosts likely to be participating in the room ([str]).
  566. Raises:
  567. SynapseError if room alias could not be found.
  568. """
  569. directory_handler = self.directory_handler
  570. mapping = yield directory_handler.get_association(room_alias)
  571. if not mapping:
  572. raise SynapseError(404, "No such room alias")
  573. room_id = mapping["room_id"]
  574. servers = mapping["servers"]
  575. # put the server which owns the alias at the front of the server list.
  576. if room_alias.domain in servers:
  577. servers.remove(room_alias.domain)
  578. servers.insert(0, room_alias.domain)
  579. return RoomID.from_string(room_id), servers
  580. @defer.inlineCallbacks
  581. def _get_inviter(self, user_id, room_id):
  582. invite = yield self.store.get_invite_for_user_in_room(
  583. user_id=user_id, room_id=room_id
  584. )
  585. if invite:
  586. return UserID.from_string(invite.sender)
  587. @defer.inlineCallbacks
  588. def do_3pid_invite(
  589. self,
  590. room_id,
  591. inviter,
  592. medium,
  593. address,
  594. id_server,
  595. requester,
  596. txn_id,
  597. id_access_token=None,
  598. ):
  599. if self.config.block_non_admin_invites:
  600. is_requester_admin = yield self.auth.is_server_admin(requester.user)
  601. if not is_requester_admin:
  602. raise SynapseError(
  603. 403, "Invites have been disabled on this server", Codes.FORBIDDEN
  604. )
  605. # We need to rate limit *before* we send out any 3PID invites, so we
  606. # can't just rely on the standard ratelimiting of events.
  607. yield self.base_handler.ratelimit(requester)
  608. can_invite = yield self.third_party_event_rules.check_threepid_can_be_invited(
  609. medium, address, room_id
  610. )
  611. if not can_invite:
  612. raise SynapseError(
  613. 403,
  614. "This third-party identifier can not be invited in this room",
  615. Codes.FORBIDDEN,
  616. )
  617. if not self._enable_lookup:
  618. raise SynapseError(
  619. 403, "Looking up third-party identifiers is denied from this server"
  620. )
  621. invitee = yield self.identity_handler.lookup_3pid(
  622. id_server, medium, address, id_access_token
  623. )
  624. if invitee:
  625. yield self.update_membership(
  626. requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id
  627. )
  628. else:
  629. yield self._make_and_store_3pid_invite(
  630. requester,
  631. id_server,
  632. medium,
  633. address,
  634. room_id,
  635. inviter,
  636. txn_id=txn_id,
  637. id_access_token=id_access_token,
  638. )
  639. @defer.inlineCallbacks
  640. def _make_and_store_3pid_invite(
  641. self,
  642. requester,
  643. id_server,
  644. medium,
  645. address,
  646. room_id,
  647. user,
  648. txn_id,
  649. id_access_token=None,
  650. ):
  651. room_state = yield self.state_handler.get_current_state(room_id)
  652. inviter_display_name = ""
  653. inviter_avatar_url = ""
  654. member_event = room_state.get((EventTypes.Member, user.to_string()))
  655. if member_event:
  656. inviter_display_name = member_event.content.get("displayname", "")
  657. inviter_avatar_url = member_event.content.get("avatar_url", "")
  658. # if user has no display name, default to their MXID
  659. if not inviter_display_name:
  660. inviter_display_name = user.to_string()
  661. canonical_room_alias = ""
  662. canonical_alias_event = room_state.get((EventTypes.CanonicalAlias, ""))
  663. if canonical_alias_event:
  664. canonical_room_alias = canonical_alias_event.content.get("alias", "")
  665. room_name = ""
  666. room_name_event = room_state.get((EventTypes.Name, ""))
  667. if room_name_event:
  668. room_name = room_name_event.content.get("name", "")
  669. room_join_rules = ""
  670. join_rules_event = room_state.get((EventTypes.JoinRules, ""))
  671. if join_rules_event:
  672. room_join_rules = join_rules_event.content.get("join_rule", "")
  673. room_avatar_url = ""
  674. room_avatar_event = room_state.get((EventTypes.RoomAvatar, ""))
  675. if room_avatar_event:
  676. room_avatar_url = room_avatar_event.content.get("url", "")
  677. (
  678. token,
  679. public_keys,
  680. fallback_public_key,
  681. display_name,
  682. ) = yield self.identity_handler.ask_id_server_for_third_party_invite(
  683. requester=requester,
  684. id_server=id_server,
  685. medium=medium,
  686. address=address,
  687. room_id=room_id,
  688. inviter_user_id=user.to_string(),
  689. room_alias=canonical_room_alias,
  690. room_avatar_url=room_avatar_url,
  691. room_join_rules=room_join_rules,
  692. room_name=room_name,
  693. inviter_display_name=inviter_display_name,
  694. inviter_avatar_url=inviter_avatar_url,
  695. id_access_token=id_access_token,
  696. )
  697. yield self.event_creation_handler.create_and_send_nonmember_event(
  698. requester,
  699. {
  700. "type": EventTypes.ThirdPartyInvite,
  701. "content": {
  702. "display_name": display_name,
  703. "public_keys": public_keys,
  704. # For backwards compatibility:
  705. "key_validity_url": fallback_public_key["key_validity_url"],
  706. "public_key": fallback_public_key["public_key"],
  707. },
  708. "room_id": room_id,
  709. "sender": user.to_string(),
  710. "state_key": token,
  711. },
  712. ratelimit=False,
  713. txn_id=txn_id,
  714. )
  715. @defer.inlineCallbacks
  716. def _is_host_in_room(self, current_state_ids):
  717. # Have we just created the room, and is this about to be the very
  718. # first member event?
  719. create_event_id = current_state_ids.get(("m.room.create", ""))
  720. if len(current_state_ids) == 1 and create_event_id:
  721. # We can only get here if we're in the process of creating the room
  722. return True
  723. for etype, state_key in current_state_ids:
  724. if etype != EventTypes.Member or not self.hs.is_mine_id(state_key):
  725. continue
  726. event_id = current_state_ids[(etype, state_key)]
  727. event = yield self.store.get_event(event_id, allow_none=True)
  728. if not event:
  729. continue
  730. if event.membership == Membership.JOIN:
  731. return True
  732. return False
  733. @defer.inlineCallbacks
  734. def _is_server_notice_room(self, room_id):
  735. if self._server_notices_mxid is None:
  736. return False
  737. user_ids = yield self.store.get_users_in_room(room_id)
  738. return self._server_notices_mxid in user_ids
  739. class RoomMemberMasterHandler(RoomMemberHandler):
  740. def __init__(self, hs):
  741. super(RoomMemberMasterHandler, self).__init__(hs)
  742. self.distributor = hs.get_distributor()
  743. self.distributor.declare("user_joined_room")
  744. self.distributor.declare("user_left_room")
  745. @defer.inlineCallbacks
  746. def _is_remote_room_too_complex(self, room_id, remote_room_hosts):
  747. """
  748. Check if complexity of a remote room is too great.
  749. Args:
  750. room_id (str)
  751. remote_room_hosts (list[str])
  752. Returns: bool of whether the complexity is too great, or None
  753. if unable to be fetched
  754. """
  755. max_complexity = self.hs.config.limit_remote_rooms.complexity
  756. complexity = yield self.federation_handler.get_room_complexity(
  757. remote_room_hosts, room_id
  758. )
  759. if complexity:
  760. return complexity["v1"] > max_complexity
  761. return None
  762. @defer.inlineCallbacks
  763. def _is_local_room_too_complex(self, room_id):
  764. """
  765. Check if the complexity of a local room is too great.
  766. Args:
  767. room_id (str)
  768. Returns: bool
  769. """
  770. max_complexity = self.hs.config.limit_remote_rooms.complexity
  771. complexity = yield self.store.get_room_complexity(room_id)
  772. return complexity["v1"] > max_complexity
  773. @defer.inlineCallbacks
  774. def _remote_join(self, requester, remote_room_hosts, room_id, user, content):
  775. """Implements RoomMemberHandler._remote_join
  776. """
  777. # filter ourselves out of remote_room_hosts: do_invite_join ignores it
  778. # and if it is the only entry we'd like to return a 404 rather than a
  779. # 500.
  780. remote_room_hosts = [
  781. host for host in remote_room_hosts if host != self.hs.hostname
  782. ]
  783. if len(remote_room_hosts) == 0:
  784. raise SynapseError(404, "No known servers")
  785. if self.hs.config.limit_remote_rooms.enabled:
  786. # Fetch the room complexity
  787. too_complex = yield self._is_remote_room_too_complex(
  788. room_id, remote_room_hosts
  789. )
  790. if too_complex is True:
  791. raise SynapseError(
  792. code=400,
  793. msg=self.hs.config.limit_remote_rooms.complexity_error,
  794. errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
  795. )
  796. # We don't do an auth check if we are doing an invite
  797. # join dance for now, since we're kinda implicitly checking
  798. # that we are allowed to join when we decide whether or not we
  799. # need to do the invite/join dance.
  800. yield self.federation_handler.do_invite_join(
  801. remote_room_hosts, room_id, user.to_string(), content
  802. )
  803. yield self._user_joined_room(user, room_id)
  804. # Check the room we just joined wasn't too large, if we didn't fetch the
  805. # complexity of it before.
  806. if self.hs.config.limit_remote_rooms.enabled:
  807. if too_complex is False:
  808. # We checked, and we're under the limit.
  809. return
  810. # Check again, but with the local state events
  811. too_complex = yield self._is_local_room_too_complex(room_id)
  812. if too_complex is False:
  813. # We're under the limit.
  814. return
  815. # The room is too large. Leave.
  816. requester = types.create_requester(user, None, False, None)
  817. yield self.update_membership(
  818. requester=requester, target=user, room_id=room_id, action="leave"
  819. )
  820. raise SynapseError(
  821. code=400,
  822. msg=self.hs.config.limit_remote_rooms.complexity_error,
  823. errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
  824. )
  825. @defer.inlineCallbacks
  826. def _remote_reject_invite(
  827. self, requester, remote_room_hosts, room_id, target, content
  828. ):
  829. """Implements RoomMemberHandler._remote_reject_invite
  830. """
  831. fed_handler = self.federation_handler
  832. try:
  833. ret = yield fed_handler.do_remotely_reject_invite(
  834. remote_room_hosts, room_id, target.to_string(), content=content,
  835. )
  836. return ret
  837. except Exception as e:
  838. # if we were unable to reject the exception, just mark
  839. # it as rejected on our end and plough ahead.
  840. #
  841. # The 'except' clause is very broad, but we need to
  842. # capture everything from DNS failures upwards
  843. #
  844. logger.warning("Failed to reject invite: %s", e)
  845. yield self.store.locally_reject_invite(target.to_string(), room_id)
  846. return {}
  847. def _user_joined_room(self, target, room_id):
  848. """Implements RoomMemberHandler._user_joined_room
  849. """
  850. return user_joined_room(self.distributor, target, room_id)
  851. def _user_left_room(self, target, room_id):
  852. """Implements RoomMemberHandler._user_left_room
  853. """
  854. return user_left_room(self.distributor, target, room_id)
  855. @defer.inlineCallbacks
  856. def forget(self, user, room_id):
  857. user_id = user.to_string()
  858. member = yield self.state_handler.get_current_state(
  859. room_id=room_id, event_type=EventTypes.Member, state_key=user_id
  860. )
  861. membership = member.membership if member else None
  862. if membership is not None and membership not in [
  863. Membership.LEAVE,
  864. Membership.BAN,
  865. ]:
  866. raise SynapseError(400, "User %s in room %s" % (user_id, room_id))
  867. if membership:
  868. yield self.store.forget(user_id, room_id)