You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

973 lines
32 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 Vector Creations Ltd
  3. # Copyright 2018 New Vector Ltd
  4. # Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
  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 logging
  18. from six import string_types
  19. from twisted.internet import defer
  20. from synapse.api.errors import Codes, SynapseError
  21. from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
  22. from synapse.util.async_helpers import concurrently_execute
  23. logger = logging.getLogger(__name__)
  24. # TODO: Allow users to "knock" or simply join depending on rules
  25. # TODO: Federation admin APIs
  26. # TODO: is_privileged flag to users and is_public to users and rooms
  27. # TODO: Audit log for admins (profile updates, membership changes, users who tried
  28. # to join but were rejected, etc)
  29. # TODO: Flairs
  30. class GroupsServerHandler(object):
  31. def __init__(self, hs):
  32. self.hs = hs
  33. self.store = hs.get_datastore()
  34. self.room_list_handler = hs.get_room_list_handler()
  35. self.auth = hs.get_auth()
  36. self.clock = hs.get_clock()
  37. self.keyring = hs.get_keyring()
  38. self.is_mine_id = hs.is_mine_id
  39. self.signing_key = hs.config.signing_key[0]
  40. self.server_name = hs.hostname
  41. self.attestations = hs.get_groups_attestation_signing()
  42. self.transport_client = hs.get_federation_transport_client()
  43. self.profile_handler = hs.get_profile_handler()
  44. # Ensure attestations get renewed
  45. hs.get_groups_attestation_renewer()
  46. @defer.inlineCallbacks
  47. def check_group_is_ours(
  48. self, group_id, requester_user_id, and_exists=False, and_is_admin=None
  49. ):
  50. """Check that the group is ours, and optionally if it exists.
  51. If group does exist then return group.
  52. Args:
  53. group_id (str)
  54. and_exists (bool): whether to also check if group exists
  55. and_is_admin (str): whether to also check if given str is a user_id
  56. that is an admin
  57. """
  58. if not self.is_mine_id(group_id):
  59. raise SynapseError(400, "Group not on this server")
  60. group = yield self.store.get_group(group_id)
  61. if and_exists and not group:
  62. raise SynapseError(404, "Unknown group")
  63. is_user_in_group = yield self.store.is_user_in_group(
  64. requester_user_id, group_id
  65. )
  66. if group and not is_user_in_group and not group["is_public"]:
  67. raise SynapseError(404, "Unknown group")
  68. if and_is_admin:
  69. is_admin = yield self.store.is_user_admin_in_group(group_id, and_is_admin)
  70. if not is_admin:
  71. raise SynapseError(403, "User is not admin in group")
  72. return group
  73. @defer.inlineCallbacks
  74. def get_group_summary(self, group_id, requester_user_id):
  75. """Get the summary for a group as seen by requester_user_id.
  76. The group summary consists of the profile of the room, and a curated
  77. list of users and rooms. These list *may* be organised by role/category.
  78. The roles/categories are ordered, and so are the users/rooms within them.
  79. A user/room may appear in multiple roles/categories.
  80. """
  81. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  82. is_user_in_group = yield self.store.is_user_in_group(
  83. requester_user_id, group_id
  84. )
  85. profile = yield self.get_group_profile(group_id, requester_user_id)
  86. users, roles = yield self.store.get_users_for_summary_by_role(
  87. group_id, include_private=is_user_in_group
  88. )
  89. # TODO: Add profiles to users
  90. rooms, categories = yield self.store.get_rooms_for_summary_by_category(
  91. group_id, include_private=is_user_in_group
  92. )
  93. for room_entry in rooms:
  94. room_id = room_entry["room_id"]
  95. joined_users = yield self.store.get_users_in_room(room_id)
  96. entry = yield self.room_list_handler.generate_room_entry(
  97. room_id, len(joined_users), with_alias=False, allow_private=True
  98. )
  99. entry = dict(entry) # so we don't change whats cached
  100. entry.pop("room_id", None)
  101. room_entry["profile"] = entry
  102. rooms.sort(key=lambda e: e.get("order", 0))
  103. for entry in users:
  104. user_id = entry["user_id"]
  105. if not self.is_mine_id(requester_user_id):
  106. attestation = yield self.store.get_remote_attestation(group_id, user_id)
  107. if not attestation:
  108. continue
  109. entry["attestation"] = attestation
  110. else:
  111. entry["attestation"] = self.attestations.create_attestation(
  112. group_id, user_id
  113. )
  114. user_profile = yield self.profile_handler.get_profile_from_cache(user_id)
  115. entry.update(user_profile)
  116. users.sort(key=lambda e: e.get("order", 0))
  117. membership_info = yield self.store.get_users_membership_info_in_group(
  118. group_id, requester_user_id
  119. )
  120. return {
  121. "profile": profile,
  122. "users_section": {
  123. "users": users,
  124. "roles": roles,
  125. "total_user_count_estimate": 0, # TODO
  126. },
  127. "rooms_section": {
  128. "rooms": rooms,
  129. "categories": categories,
  130. "total_room_count_estimate": 0, # TODO
  131. },
  132. "user": membership_info,
  133. }
  134. @defer.inlineCallbacks
  135. def update_group_summary_room(
  136. self, group_id, requester_user_id, room_id, category_id, content
  137. ):
  138. """Add/update a room to the group summary
  139. """
  140. yield self.check_group_is_ours(
  141. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  142. )
  143. RoomID.from_string(room_id) # Ensure valid room id
  144. order = content.get("order", None)
  145. is_public = _parse_visibility_from_contents(content)
  146. yield self.store.add_room_to_summary(
  147. group_id=group_id,
  148. room_id=room_id,
  149. category_id=category_id,
  150. order=order,
  151. is_public=is_public,
  152. )
  153. return {}
  154. @defer.inlineCallbacks
  155. def delete_group_summary_room(
  156. self, group_id, requester_user_id, room_id, category_id
  157. ):
  158. """Remove a room from the summary
  159. """
  160. yield self.check_group_is_ours(
  161. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  162. )
  163. yield self.store.remove_room_from_summary(
  164. group_id=group_id, room_id=room_id, category_id=category_id
  165. )
  166. return {}
  167. @defer.inlineCallbacks
  168. def set_group_join_policy(self, group_id, requester_user_id, content):
  169. """Sets the group join policy.
  170. Currently supported policies are:
  171. - "invite": an invite must be received and accepted in order to join.
  172. - "open": anyone can join.
  173. """
  174. yield self.check_group_is_ours(
  175. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  176. )
  177. join_policy = _parse_join_policy_from_contents(content)
  178. if join_policy is None:
  179. raise SynapseError(400, "No value specified for 'm.join_policy'")
  180. yield self.store.set_group_join_policy(group_id, join_policy=join_policy)
  181. return {}
  182. @defer.inlineCallbacks
  183. def get_group_categories(self, group_id, requester_user_id):
  184. """Get all categories in a group (as seen by user)
  185. """
  186. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  187. categories = yield self.store.get_group_categories(group_id=group_id)
  188. return {"categories": categories}
  189. @defer.inlineCallbacks
  190. def get_group_category(self, group_id, requester_user_id, category_id):
  191. """Get a specific category in a group (as seen by user)
  192. """
  193. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  194. res = yield self.store.get_group_category(
  195. group_id=group_id, category_id=category_id
  196. )
  197. return res
  198. @defer.inlineCallbacks
  199. def update_group_category(self, group_id, requester_user_id, category_id, content):
  200. """Add/Update a group category
  201. """
  202. yield self.check_group_is_ours(
  203. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  204. )
  205. is_public = _parse_visibility_from_contents(content)
  206. profile = content.get("profile")
  207. yield self.store.upsert_group_category(
  208. group_id=group_id,
  209. category_id=category_id,
  210. is_public=is_public,
  211. profile=profile,
  212. )
  213. return {}
  214. @defer.inlineCallbacks
  215. def delete_group_category(self, group_id, requester_user_id, category_id):
  216. """Delete a group category
  217. """
  218. yield self.check_group_is_ours(
  219. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  220. )
  221. yield self.store.remove_group_category(
  222. group_id=group_id, category_id=category_id
  223. )
  224. return {}
  225. @defer.inlineCallbacks
  226. def get_group_roles(self, group_id, requester_user_id):
  227. """Get all roles in a group (as seen by user)
  228. """
  229. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  230. roles = yield self.store.get_group_roles(group_id=group_id)
  231. return {"roles": roles}
  232. @defer.inlineCallbacks
  233. def get_group_role(self, group_id, requester_user_id, role_id):
  234. """Get a specific role in a group (as seen by user)
  235. """
  236. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  237. res = yield self.store.get_group_role(group_id=group_id, role_id=role_id)
  238. return res
  239. @defer.inlineCallbacks
  240. def update_group_role(self, group_id, requester_user_id, role_id, content):
  241. """Add/update a role in a group
  242. """
  243. yield self.check_group_is_ours(
  244. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  245. )
  246. is_public = _parse_visibility_from_contents(content)
  247. profile = content.get("profile")
  248. yield self.store.upsert_group_role(
  249. group_id=group_id, role_id=role_id, is_public=is_public, profile=profile
  250. )
  251. return {}
  252. @defer.inlineCallbacks
  253. def delete_group_role(self, group_id, requester_user_id, role_id):
  254. """Remove role from group
  255. """
  256. yield self.check_group_is_ours(
  257. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  258. )
  259. yield self.store.remove_group_role(group_id=group_id, role_id=role_id)
  260. return {}
  261. @defer.inlineCallbacks
  262. def update_group_summary_user(
  263. self, group_id, requester_user_id, user_id, role_id, content
  264. ):
  265. """Add/update a users entry in the group summary
  266. """
  267. yield self.check_group_is_ours(
  268. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  269. )
  270. order = content.get("order", None)
  271. is_public = _parse_visibility_from_contents(content)
  272. yield self.store.add_user_to_summary(
  273. group_id=group_id,
  274. user_id=user_id,
  275. role_id=role_id,
  276. order=order,
  277. is_public=is_public,
  278. )
  279. return {}
  280. @defer.inlineCallbacks
  281. def delete_group_summary_user(self, group_id, requester_user_id, user_id, role_id):
  282. """Remove a user from the group summary
  283. """
  284. yield self.check_group_is_ours(
  285. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  286. )
  287. yield self.store.remove_user_from_summary(
  288. group_id=group_id, user_id=user_id, role_id=role_id
  289. )
  290. return {}
  291. @defer.inlineCallbacks
  292. def get_group_profile(self, group_id, requester_user_id):
  293. """Get the group profile as seen by requester_user_id
  294. """
  295. yield self.check_group_is_ours(group_id, requester_user_id)
  296. group = yield self.store.get_group(group_id)
  297. if group:
  298. cols = [
  299. "name",
  300. "short_description",
  301. "long_description",
  302. "avatar_url",
  303. "is_public",
  304. ]
  305. group_description = {key: group[key] for key in cols}
  306. group_description["is_openly_joinable"] = group["join_policy"] == "open"
  307. return group_description
  308. else:
  309. raise SynapseError(404, "Unknown group")
  310. @defer.inlineCallbacks
  311. def update_group_profile(self, group_id, requester_user_id, content):
  312. """Update the group profile
  313. """
  314. yield self.check_group_is_ours(
  315. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  316. )
  317. profile = {}
  318. for keyname in ("name", "avatar_url", "short_description", "long_description"):
  319. if keyname in content:
  320. value = content[keyname]
  321. if not isinstance(value, string_types):
  322. raise SynapseError(400, "%r value is not a string" % (keyname,))
  323. profile[keyname] = value
  324. yield self.store.update_group_profile(group_id, profile)
  325. @defer.inlineCallbacks
  326. def get_users_in_group(self, group_id, requester_user_id):
  327. """Get the users in group as seen by requester_user_id.
  328. The ordering is arbitrary at the moment
  329. """
  330. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  331. is_user_in_group = yield self.store.is_user_in_group(
  332. requester_user_id, group_id
  333. )
  334. user_results = yield self.store.get_users_in_group(
  335. group_id, include_private=is_user_in_group
  336. )
  337. chunk = []
  338. for user_result in user_results:
  339. g_user_id = user_result["user_id"]
  340. is_public = user_result["is_public"]
  341. is_privileged = user_result["is_admin"]
  342. entry = {"user_id": g_user_id}
  343. profile = yield self.profile_handler.get_profile_from_cache(g_user_id)
  344. entry.update(profile)
  345. entry["is_public"] = bool(is_public)
  346. entry["is_privileged"] = bool(is_privileged)
  347. if not self.is_mine_id(g_user_id):
  348. attestation = yield self.store.get_remote_attestation(
  349. group_id, g_user_id
  350. )
  351. if not attestation:
  352. continue
  353. entry["attestation"] = attestation
  354. else:
  355. entry["attestation"] = self.attestations.create_attestation(
  356. group_id, g_user_id
  357. )
  358. chunk.append(entry)
  359. # TODO: If admin add lists of users whose attestations have timed out
  360. return {"chunk": chunk, "total_user_count_estimate": len(user_results)}
  361. @defer.inlineCallbacks
  362. def get_invited_users_in_group(self, group_id, requester_user_id):
  363. """Get the users that have been invited to a group as seen by requester_user_id.
  364. The ordering is arbitrary at the moment
  365. """
  366. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  367. is_user_in_group = yield self.store.is_user_in_group(
  368. requester_user_id, group_id
  369. )
  370. if not is_user_in_group:
  371. raise SynapseError(403, "User not in group")
  372. invited_users = yield self.store.get_invited_users_in_group(group_id)
  373. user_profiles = []
  374. for user_id in invited_users:
  375. user_profile = {"user_id": user_id}
  376. try:
  377. profile = yield self.profile_handler.get_profile_from_cache(user_id)
  378. user_profile.update(profile)
  379. except Exception as e:
  380. logger.warning("Error getting profile for %s: %s", user_id, e)
  381. user_profiles.append(user_profile)
  382. return {"chunk": user_profiles, "total_user_count_estimate": len(invited_users)}
  383. @defer.inlineCallbacks
  384. def get_rooms_in_group(self, group_id, requester_user_id):
  385. """Get the rooms in group as seen by requester_user_id
  386. This returns rooms in order of decreasing number of joined users
  387. """
  388. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  389. is_user_in_group = yield self.store.is_user_in_group(
  390. requester_user_id, group_id
  391. )
  392. room_results = yield self.store.get_rooms_in_group(
  393. group_id, include_private=is_user_in_group
  394. )
  395. chunk = []
  396. for room_result in room_results:
  397. room_id = room_result["room_id"]
  398. joined_users = yield self.store.get_users_in_room(room_id)
  399. entry = yield self.room_list_handler.generate_room_entry(
  400. room_id, len(joined_users), with_alias=False, allow_private=True
  401. )
  402. if not entry:
  403. continue
  404. entry["is_public"] = bool(room_result["is_public"])
  405. chunk.append(entry)
  406. chunk.sort(key=lambda e: -e["num_joined_members"])
  407. return {"chunk": chunk, "total_room_count_estimate": len(room_results)}
  408. @defer.inlineCallbacks
  409. def add_room_to_group(self, group_id, requester_user_id, room_id, content):
  410. """Add room to group
  411. """
  412. RoomID.from_string(room_id) # Ensure valid room id
  413. yield self.check_group_is_ours(
  414. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  415. )
  416. is_public = _parse_visibility_from_contents(content)
  417. yield self.store.add_room_to_group(group_id, room_id, is_public=is_public)
  418. return {}
  419. @defer.inlineCallbacks
  420. def update_room_in_group(
  421. self, group_id, requester_user_id, room_id, config_key, content
  422. ):
  423. """Update room in group
  424. """
  425. RoomID.from_string(room_id) # Ensure valid room id
  426. yield self.check_group_is_ours(
  427. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  428. )
  429. if config_key == "m.visibility":
  430. is_public = _parse_visibility_dict(content)
  431. yield self.store.update_room_in_group_visibility(
  432. group_id, room_id, is_public=is_public
  433. )
  434. else:
  435. raise SynapseError(400, "Uknown config option")
  436. return {}
  437. @defer.inlineCallbacks
  438. def remove_room_from_group(self, group_id, requester_user_id, room_id):
  439. """Remove room from group
  440. """
  441. yield self.check_group_is_ours(
  442. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  443. )
  444. yield self.store.remove_room_from_group(group_id, room_id)
  445. return {}
  446. @defer.inlineCallbacks
  447. def invite_to_group(self, group_id, user_id, requester_user_id, content):
  448. """Invite user to group
  449. """
  450. group = yield self.check_group_is_ours(
  451. group_id, requester_user_id, and_exists=True, and_is_admin=requester_user_id
  452. )
  453. # TODO: Check if user knocked
  454. invited_users = yield self.store.get_invited_users_in_group(group_id)
  455. if user_id in invited_users:
  456. raise SynapseError(
  457. 400, "User already invited to group", errcode=Codes.BAD_STATE
  458. )
  459. user_results = yield self.store.get_users_in_group(
  460. group_id, include_private=True
  461. )
  462. if user_id in [user_result["user_id"] for user_result in user_results]:
  463. raise SynapseError(400, "User already in group")
  464. content = {
  465. "profile": {"name": group["name"], "avatar_url": group["avatar_url"]},
  466. "inviter": requester_user_id,
  467. }
  468. if self.hs.is_mine_id(user_id):
  469. groups_local = self.hs.get_groups_local_handler()
  470. res = yield groups_local.on_invite(group_id, user_id, content)
  471. local_attestation = None
  472. else:
  473. local_attestation = self.attestations.create_attestation(group_id, user_id)
  474. content.update({"attestation": local_attestation})
  475. res = yield self.transport_client.invite_to_group_notification(
  476. get_domain_from_id(user_id), group_id, user_id, content
  477. )
  478. user_profile = res.get("user_profile", {})
  479. yield self.store.add_remote_profile_cache(
  480. user_id,
  481. displayname=user_profile.get("displayname"),
  482. avatar_url=user_profile.get("avatar_url"),
  483. )
  484. if res["state"] == "join":
  485. if not self.hs.is_mine_id(user_id):
  486. remote_attestation = res["attestation"]
  487. yield self.attestations.verify_attestation(
  488. remote_attestation, user_id=user_id, group_id=group_id
  489. )
  490. else:
  491. remote_attestation = None
  492. yield self.store.add_user_to_group(
  493. group_id,
  494. user_id,
  495. is_admin=False,
  496. is_public=False, # TODO
  497. local_attestation=local_attestation,
  498. remote_attestation=remote_attestation,
  499. )
  500. elif res["state"] == "invite":
  501. yield self.store.add_group_invite(group_id, user_id)
  502. return {"state": "invite"}
  503. elif res["state"] == "reject":
  504. return {"state": "reject"}
  505. else:
  506. raise SynapseError(502, "Unknown state returned by HS")
  507. @defer.inlineCallbacks
  508. def _add_user(self, group_id, user_id, content):
  509. """Add a user to a group based on a content dict.
  510. See accept_invite, join_group.
  511. """
  512. if not self.hs.is_mine_id(user_id):
  513. local_attestation = self.attestations.create_attestation(group_id, user_id)
  514. remote_attestation = content["attestation"]
  515. yield self.attestations.verify_attestation(
  516. remote_attestation, user_id=user_id, group_id=group_id
  517. )
  518. else:
  519. local_attestation = None
  520. remote_attestation = None
  521. is_public = _parse_visibility_from_contents(content)
  522. yield self.store.add_user_to_group(
  523. group_id,
  524. user_id,
  525. is_admin=False,
  526. is_public=is_public,
  527. local_attestation=local_attestation,
  528. remote_attestation=remote_attestation,
  529. )
  530. return local_attestation
  531. @defer.inlineCallbacks
  532. def accept_invite(self, group_id, requester_user_id, content):
  533. """User tries to accept an invite to the group.
  534. This is different from them asking to join, and so should error if no
  535. invite exists (and they're not a member of the group)
  536. """
  537. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  538. is_invited = yield self.store.is_user_invited_to_local_group(
  539. group_id, requester_user_id
  540. )
  541. if not is_invited:
  542. raise SynapseError(403, "User not invited to group")
  543. local_attestation = yield self._add_user(group_id, requester_user_id, content)
  544. return {"state": "join", "attestation": local_attestation}
  545. @defer.inlineCallbacks
  546. def join_group(self, group_id, requester_user_id, content):
  547. """User tries to join the group.
  548. This will error if the group requires an invite/knock to join
  549. """
  550. group_info = yield self.check_group_is_ours(
  551. group_id, requester_user_id, and_exists=True
  552. )
  553. if group_info["join_policy"] != "open":
  554. raise SynapseError(403, "Group is not publicly joinable")
  555. local_attestation = yield self._add_user(group_id, requester_user_id, content)
  556. return {"state": "join", "attestation": local_attestation}
  557. @defer.inlineCallbacks
  558. def knock(self, group_id, requester_user_id, content):
  559. """A user requests becoming a member of the group
  560. """
  561. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  562. raise NotImplementedError()
  563. @defer.inlineCallbacks
  564. def accept_knock(self, group_id, requester_user_id, content):
  565. """Accept a users knock to the room.
  566. Errors if the user hasn't knocked, rather than inviting them.
  567. """
  568. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  569. raise NotImplementedError()
  570. @defer.inlineCallbacks
  571. def remove_user_from_group(self, group_id, user_id, requester_user_id, content):
  572. """Remove a user from the group; either a user is leaving or an admin
  573. kicked them.
  574. """
  575. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  576. is_kick = False
  577. if requester_user_id != user_id:
  578. is_admin = yield self.store.is_user_admin_in_group(
  579. group_id, requester_user_id
  580. )
  581. if not is_admin:
  582. raise SynapseError(403, "User is not admin in group")
  583. is_kick = True
  584. yield self.store.remove_user_from_group(group_id, user_id)
  585. if is_kick:
  586. if self.hs.is_mine_id(user_id):
  587. groups_local = self.hs.get_groups_local_handler()
  588. yield groups_local.user_removed_from_group(group_id, user_id, {})
  589. else:
  590. yield self.transport_client.remove_user_from_group_notification(
  591. get_domain_from_id(user_id), group_id, user_id, {}
  592. )
  593. if not self.hs.is_mine_id(user_id):
  594. yield self.store.maybe_delete_remote_profile_cache(user_id)
  595. # Delete group if the last user has left
  596. users = yield self.store.get_users_in_group(group_id, include_private=True)
  597. if not users:
  598. yield self.store.delete_group(group_id)
  599. return {}
  600. @defer.inlineCallbacks
  601. def create_group(self, group_id, requester_user_id, content):
  602. group = yield self.check_group_is_ours(group_id, requester_user_id)
  603. logger.info("Attempting to create group with ID: %r", group_id)
  604. # parsing the id into a GroupID validates it.
  605. group_id_obj = GroupID.from_string(group_id)
  606. if group:
  607. raise SynapseError(400, "Group already exists")
  608. is_admin = yield self.auth.is_server_admin(
  609. UserID.from_string(requester_user_id)
  610. )
  611. if not is_admin:
  612. if not self.hs.config.enable_group_creation:
  613. raise SynapseError(
  614. 403, "Only a server admin can create groups on this server"
  615. )
  616. localpart = group_id_obj.localpart
  617. if not localpart.startswith(self.hs.config.group_creation_prefix):
  618. raise SynapseError(
  619. 400,
  620. "Can only create groups with prefix %r on this server"
  621. % (self.hs.config.group_creation_prefix,),
  622. )
  623. profile = content.get("profile", {})
  624. name = profile.get("name")
  625. avatar_url = profile.get("avatar_url")
  626. short_description = profile.get("short_description")
  627. long_description = profile.get("long_description")
  628. user_profile = content.get("user_profile", {})
  629. yield self.store.create_group(
  630. group_id,
  631. requester_user_id,
  632. name=name,
  633. avatar_url=avatar_url,
  634. short_description=short_description,
  635. long_description=long_description,
  636. )
  637. if not self.hs.is_mine_id(requester_user_id):
  638. remote_attestation = content["attestation"]
  639. yield self.attestations.verify_attestation(
  640. remote_attestation, user_id=requester_user_id, group_id=group_id
  641. )
  642. local_attestation = self.attestations.create_attestation(
  643. group_id, requester_user_id
  644. )
  645. else:
  646. local_attestation = None
  647. remote_attestation = None
  648. yield self.store.add_user_to_group(
  649. group_id,
  650. requester_user_id,
  651. is_admin=True,
  652. is_public=True, # TODO
  653. local_attestation=local_attestation,
  654. remote_attestation=remote_attestation,
  655. )
  656. if not self.hs.is_mine_id(requester_user_id):
  657. yield self.store.add_remote_profile_cache(
  658. requester_user_id,
  659. displayname=user_profile.get("displayname"),
  660. avatar_url=user_profile.get("avatar_url"),
  661. )
  662. return {"group_id": group_id}
  663. @defer.inlineCallbacks
  664. def delete_group(self, group_id, requester_user_id):
  665. """Deletes a group, kicking out all current members.
  666. Only group admins or server admins can call this request
  667. Args:
  668. group_id (str)
  669. request_user_id (str)
  670. Returns:
  671. Deferred
  672. """
  673. yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)
  674. # Only server admins or group admins can delete groups.
  675. is_admin = yield self.store.is_user_admin_in_group(group_id, requester_user_id)
  676. if not is_admin:
  677. is_admin = yield self.auth.is_server_admin(
  678. UserID.from_string(requester_user_id)
  679. )
  680. if not is_admin:
  681. raise SynapseError(403, "User is not an admin")
  682. # Before deleting the group lets kick everyone out of it
  683. users = yield self.store.get_users_in_group(group_id, include_private=True)
  684. @defer.inlineCallbacks
  685. def _kick_user_from_group(user_id):
  686. if self.hs.is_mine_id(user_id):
  687. groups_local = self.hs.get_groups_local_handler()
  688. yield groups_local.user_removed_from_group(group_id, user_id, {})
  689. else:
  690. yield self.transport_client.remove_user_from_group_notification(
  691. get_domain_from_id(user_id), group_id, user_id, {}
  692. )
  693. yield self.store.maybe_delete_remote_profile_cache(user_id)
  694. # We kick users out in the order of:
  695. # 1. Non-admins
  696. # 2. Other admins
  697. # 3. The requester
  698. #
  699. # This is so that if the deletion fails for some reason other admins or
  700. # the requester still has auth to retry.
  701. non_admins = []
  702. admins = []
  703. for u in users:
  704. if u["user_id"] == requester_user_id:
  705. continue
  706. if u["is_admin"]:
  707. admins.append(u["user_id"])
  708. else:
  709. non_admins.append(u["user_id"])
  710. yield concurrently_execute(_kick_user_from_group, non_admins, 10)
  711. yield concurrently_execute(_kick_user_from_group, admins, 10)
  712. yield _kick_user_from_group(requester_user_id)
  713. yield self.store.delete_group(group_id)
  714. def _parse_join_policy_from_contents(content):
  715. """Given a content for a request, return the specified join policy or None
  716. """
  717. join_policy_dict = content.get("m.join_policy")
  718. if join_policy_dict:
  719. return _parse_join_policy_dict(join_policy_dict)
  720. else:
  721. return None
  722. def _parse_join_policy_dict(join_policy_dict):
  723. """Given a dict for the "m.join_policy" config return the join policy specified
  724. """
  725. join_policy_type = join_policy_dict.get("type")
  726. if not join_policy_type:
  727. return "invite"
  728. if join_policy_type not in ("invite", "open"):
  729. raise SynapseError(400, "Synapse only supports 'invite'/'open' join rule")
  730. return join_policy_type
  731. def _parse_visibility_from_contents(content):
  732. """Given a content for a request parse out whether the entity should be
  733. public or not
  734. """
  735. visibility = content.get("m.visibility")
  736. if visibility:
  737. return _parse_visibility_dict(visibility)
  738. else:
  739. is_public = True
  740. return is_public
  741. def _parse_visibility_dict(visibility):
  742. """Given a dict for the "m.visibility" config return if the entity should
  743. be public or not
  744. """
  745. vis_type = visibility.get("type")
  746. if not vis_type:
  747. return True
  748. if vis_type not in ("public", "private"):
  749. raise SynapseError(400, "Synapse only supports 'public'/'private' visibility")
  750. return vis_type == "public"