room_member.py 35 KB

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