room_member.py 40 KB

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