room_member.py 36 KB

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