room_member.py 35 KB

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