groups_server.py 31 KB

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