groups_server.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2017 Vector Creations Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from twisted.internet import defer
  16. from synapse.api.errors import SynapseError
  17. from synapse.types import UserID, get_domain_from_id, RoomID, GroupID
  18. import logging
  19. import urllib
  20. logger = logging.getLogger(__name__)
  21. # TODO: Allow users to "knock" or simpkly join depending on rules
  22. # TODO: Federation admin APIs
  23. # TODO: is_priveged flag to users and is_public to users and rooms
  24. # TODO: Audit log for admins (profile updates, membership changes, users who tried
  25. # to join but were rejected, etc)
  26. # TODO: Flairs
  27. class GroupsServerHandler(object):
  28. def __init__(self, hs):
  29. self.hs = hs
  30. self.store = hs.get_datastore()
  31. self.room_list_handler = hs.get_room_list_handler()
  32. self.auth = hs.get_auth()
  33. self.clock = hs.get_clock()
  34. self.keyring = hs.get_keyring()
  35. self.is_mine_id = hs.is_mine_id
  36. self.signing_key = hs.config.signing_key[0]
  37. self.server_name = hs.hostname
  38. self.attestations = hs.get_groups_attestation_signing()
  39. self.transport_client = hs.get_federation_transport_client()
  40. self.profile_handler = hs.get_profile_handler()
  41. # Ensure attestations get renewed
  42. hs.get_groups_attestation_renewer()
  43. @defer.inlineCallbacks
  44. def check_group_is_ours(self, group_id, and_exists=False, and_is_admin=None):
  45. """Check that the group is ours, and optionally if it exists.
  46. If group does exist then return group.
  47. Args:
  48. group_id (str)
  49. and_exists (bool): whether to also check if group exists
  50. and_is_admin (str): whether to also check if given str is a user_id
  51. that is an admin
  52. """
  53. if not self.is_mine_id(group_id):
  54. raise SynapseError(400, "Group not on this server")
  55. group = yield self.store.get_group(group_id)
  56. if and_exists and not group:
  57. raise SynapseError(404, "Unknown group")
  58. if and_is_admin:
  59. is_admin = yield self.store.is_user_admin_in_group(group_id, and_is_admin)
  60. if not is_admin:
  61. raise SynapseError(403, "User is not admin in group")
  62. defer.returnValue(group)
  63. @defer.inlineCallbacks
  64. def get_group_summary(self, group_id, requester_user_id):
  65. """Get the summary for a group as seen by requester_user_id.
  66. The group summary consists of the profile of the room, and a curated
  67. list of users and rooms. These list *may* be organised by role/category.
  68. The roles/categories are ordered, and so are the users/rooms within them.
  69. A user/room may appear in multiple roles/categories.
  70. """
  71. yield self.check_group_is_ours(group_id, and_exists=True)
  72. is_user_in_group = yield self.store.is_user_in_group(requester_user_id, group_id)
  73. profile = yield self.get_group_profile(group_id, requester_user_id)
  74. users, roles = yield self.store.get_users_for_summary_by_role(
  75. group_id, include_private=is_user_in_group,
  76. )
  77. # TODO: Add profiles to users
  78. rooms, categories = yield self.store.get_rooms_for_summary_by_category(
  79. group_id, include_private=is_user_in_group,
  80. )
  81. for room_entry in rooms:
  82. room_id = room_entry["room_id"]
  83. joined_users = yield self.store.get_users_in_room(room_id)
  84. entry = yield self.room_list_handler.generate_room_entry(
  85. room_id, len(joined_users),
  86. with_alias=False, allow_private=True,
  87. )
  88. entry = dict(entry) # so we don't change whats cached
  89. entry.pop("room_id", None)
  90. room_entry["profile"] = entry
  91. rooms.sort(key=lambda e: e.get("order", 0))
  92. for entry in users:
  93. user_id = entry["user_id"]
  94. if not self.is_mine_id(requester_user_id):
  95. attestation = yield self.store.get_remote_attestation(group_id, user_id)
  96. if not attestation:
  97. continue
  98. entry["attestation"] = attestation
  99. else:
  100. entry["attestation"] = self.attestations.create_attestation(
  101. group_id, user_id,
  102. )
  103. user_profile = yield self.profile_handler.get_profile_from_cache(user_id)
  104. entry.update(user_profile)
  105. users.sort(key=lambda e: e.get("order", 0))
  106. membership_info = yield self.store.get_users_membership_info_in_group(
  107. group_id, requester_user_id,
  108. )
  109. defer.returnValue({
  110. "profile": profile,
  111. "users_section": {
  112. "users": users,
  113. "roles": roles,
  114. "total_user_count_estimate": 0, # TODO
  115. },
  116. "rooms_section": {
  117. "rooms": rooms,
  118. "categories": categories,
  119. "total_room_count_estimate": 0, # TODO
  120. },
  121. "user": membership_info,
  122. })
  123. @defer.inlineCallbacks
  124. def update_group_summary_room(self, group_id, user_id, room_id, category_id, content):
  125. """Add/update a room to the group summary
  126. """
  127. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  128. RoomID.from_string(room_id) # Ensure valid room id
  129. order = content.get("order", None)
  130. is_public = _parse_visibility_from_contents(content)
  131. yield self.store.add_room_to_summary(
  132. group_id=group_id,
  133. room_id=room_id,
  134. category_id=category_id,
  135. order=order,
  136. is_public=is_public,
  137. )
  138. defer.returnValue({})
  139. @defer.inlineCallbacks
  140. def delete_group_summary_room(self, group_id, user_id, room_id, category_id):
  141. """Remove a room from the summary
  142. """
  143. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  144. yield self.store.remove_room_from_summary(
  145. group_id=group_id,
  146. room_id=room_id,
  147. category_id=category_id,
  148. )
  149. defer.returnValue({})
  150. @defer.inlineCallbacks
  151. def get_group_categories(self, group_id, user_id):
  152. """Get all categories in a group (as seen by user)
  153. """
  154. yield self.check_group_is_ours(group_id, and_exists=True)
  155. categories = yield self.store.get_group_categories(
  156. group_id=group_id,
  157. )
  158. defer.returnValue({"categories": categories})
  159. @defer.inlineCallbacks
  160. def get_group_category(self, group_id, user_id, category_id):
  161. """Get a specific category in a group (as seen by user)
  162. """
  163. yield self.check_group_is_ours(group_id, and_exists=True)
  164. res = yield self.store.get_group_category(
  165. group_id=group_id,
  166. category_id=category_id,
  167. )
  168. defer.returnValue(res)
  169. @defer.inlineCallbacks
  170. def update_group_category(self, group_id, user_id, category_id, content):
  171. """Add/Update a group category
  172. """
  173. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  174. is_public = _parse_visibility_from_contents(content)
  175. profile = content.get("profile")
  176. yield self.store.upsert_group_category(
  177. group_id=group_id,
  178. category_id=category_id,
  179. is_public=is_public,
  180. profile=profile,
  181. )
  182. defer.returnValue({})
  183. @defer.inlineCallbacks
  184. def delete_group_category(self, group_id, user_id, category_id):
  185. """Delete a group category
  186. """
  187. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  188. yield self.store.remove_group_category(
  189. group_id=group_id,
  190. category_id=category_id,
  191. )
  192. defer.returnValue({})
  193. @defer.inlineCallbacks
  194. def get_group_roles(self, group_id, user_id):
  195. """Get all roles in a group (as seen by user)
  196. """
  197. yield self.check_group_is_ours(group_id, and_exists=True)
  198. roles = yield self.store.get_group_roles(
  199. group_id=group_id,
  200. )
  201. defer.returnValue({"roles": roles})
  202. @defer.inlineCallbacks
  203. def get_group_role(self, group_id, user_id, role_id):
  204. """Get a specific role in a group (as seen by user)
  205. """
  206. yield self.check_group_is_ours(group_id, and_exists=True)
  207. res = yield self.store.get_group_role(
  208. group_id=group_id,
  209. role_id=role_id,
  210. )
  211. defer.returnValue(res)
  212. @defer.inlineCallbacks
  213. def update_group_role(self, group_id, user_id, role_id, content):
  214. """Add/update a role in a group
  215. """
  216. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  217. is_public = _parse_visibility_from_contents(content)
  218. profile = content.get("profile")
  219. yield self.store.upsert_group_role(
  220. group_id=group_id,
  221. role_id=role_id,
  222. is_public=is_public,
  223. profile=profile,
  224. )
  225. defer.returnValue({})
  226. @defer.inlineCallbacks
  227. def delete_group_role(self, group_id, user_id, role_id):
  228. """Remove role from group
  229. """
  230. yield self.check_group_is_ours(group_id, and_exists=True, and_is_admin=user_id)
  231. yield self.store.remove_group_role(
  232. group_id=group_id,
  233. role_id=role_id,
  234. )
  235. defer.returnValue({})
  236. @defer.inlineCallbacks
  237. def update_group_summary_user(self, group_id, requester_user_id, user_id, role_id,
  238. content):
  239. """Add/update a users entry in the group summary
  240. """
  241. yield self.check_group_is_ours(
  242. group_id, and_exists=True, and_is_admin=requester_user_id,
  243. )
  244. order = content.get("order", None)
  245. is_public = _parse_visibility_from_contents(content)
  246. yield self.store.add_user_to_summary(
  247. group_id=group_id,
  248. user_id=user_id,
  249. role_id=role_id,
  250. order=order,
  251. is_public=is_public,
  252. )
  253. defer.returnValue({})
  254. @defer.inlineCallbacks
  255. def delete_group_summary_user(self, group_id, requester_user_id, user_id, role_id):
  256. """Remove a user from the group summary
  257. """
  258. yield self.check_group_is_ours(
  259. group_id, and_exists=True, and_is_admin=requester_user_id,
  260. )
  261. yield self.store.remove_user_from_summary(
  262. group_id=group_id,
  263. user_id=user_id,
  264. role_id=role_id,
  265. )
  266. defer.returnValue({})
  267. @defer.inlineCallbacks
  268. def get_group_profile(self, group_id, requester_user_id):
  269. """Get the group profile as seen by requester_user_id
  270. """
  271. yield self.check_group_is_ours(group_id)
  272. group_description = yield self.store.get_group(group_id)
  273. if group_description:
  274. defer.returnValue(group_description)
  275. else:
  276. raise SynapseError(404, "Unknown group")
  277. @defer.inlineCallbacks
  278. def update_group_profile(self, group_id, requester_user_id, content):
  279. """Update the group profile
  280. """
  281. yield self.check_group_is_ours(
  282. group_id, and_exists=True, and_is_admin=requester_user_id,
  283. )
  284. profile = {}
  285. for keyname in ("name", "avatar_url", "short_description",
  286. "long_description"):
  287. if keyname in content:
  288. value = content[keyname]
  289. if not isinstance(value, basestring):
  290. raise SynapseError(400, "%r value is not a string" % (keyname,))
  291. profile[keyname] = value
  292. yield self.store.update_group_profile(group_id, profile)
  293. @defer.inlineCallbacks
  294. def get_users_in_group(self, group_id, requester_user_id):
  295. """Get the users in group as seen by requester_user_id.
  296. The ordering is arbitrary at the moment
  297. """
  298. yield self.check_group_is_ours(group_id, and_exists=True)
  299. is_user_in_group = yield self.store.is_user_in_group(requester_user_id, group_id)
  300. user_results = yield self.store.get_users_in_group(
  301. group_id, include_private=is_user_in_group,
  302. )
  303. chunk = []
  304. for user_result in user_results:
  305. g_user_id = user_result["user_id"]
  306. is_public = user_result["is_public"]
  307. entry = {"user_id": g_user_id}
  308. profile = yield self.profile_handler.get_profile_from_cache(g_user_id)
  309. entry.update(profile)
  310. if not is_public:
  311. entry["is_public"] = False
  312. if not self.is_mine_id(g_user_id):
  313. attestation = yield self.store.get_remote_attestation(group_id, g_user_id)
  314. if not attestation:
  315. continue
  316. entry["attestation"] = attestation
  317. else:
  318. entry["attestation"] = self.attestations.create_attestation(
  319. group_id, g_user_id,
  320. )
  321. chunk.append(entry)
  322. # TODO: If admin add lists of users whose attestations have timed out
  323. defer.returnValue({
  324. "chunk": chunk,
  325. "total_user_count_estimate": len(user_results),
  326. })
  327. @defer.inlineCallbacks
  328. def get_invited_users_in_group(self, group_id, requester_user_id):
  329. """Get the users that have been invited to a group as seen by requester_user_id.
  330. The ordering is arbitrary at the moment
  331. """
  332. yield self.check_group_is_ours(group_id, and_exists=True)
  333. is_user_in_group = yield self.store.is_user_in_group(requester_user_id, group_id)
  334. if not is_user_in_group:
  335. raise SynapseError(403, "User not in group")
  336. invited_users = yield self.store.get_invited_users_in_group(group_id)
  337. user_profiles = []
  338. for user_id in invited_users:
  339. user_profile = {
  340. "user_id": user_id
  341. }
  342. try:
  343. profile = yield self.profile_handler.get_profile_from_cache(user_id)
  344. user_profile.update(profile)
  345. except Exception as e:
  346. logger.warn("Error getting profile for %s: %s", user_id, e)
  347. user_profiles.append(user_profile)
  348. defer.returnValue({
  349. "chunk": user_profiles,
  350. "total_user_count_estimate": len(invited_users),
  351. })
  352. @defer.inlineCallbacks
  353. def get_rooms_in_group(self, group_id, requester_user_id):
  354. """Get the rooms in group as seen by requester_user_id
  355. This returns rooms in order of decreasing number of joined users
  356. """
  357. yield self.check_group_is_ours(group_id, and_exists=True)
  358. is_user_in_group = yield self.store.is_user_in_group(requester_user_id, group_id)
  359. room_results = yield self.store.get_rooms_in_group(
  360. group_id, include_private=is_user_in_group,
  361. )
  362. chunk = []
  363. for room_result in room_results:
  364. room_id = room_result["room_id"]
  365. is_public = room_result["is_public"]
  366. joined_users = yield self.store.get_users_in_room(room_id)
  367. entry = yield self.room_list_handler.generate_room_entry(
  368. room_id, len(joined_users),
  369. with_alias=False, allow_private=True,
  370. )
  371. if not entry:
  372. continue
  373. if not is_public:
  374. entry["is_public"] = False
  375. chunk.append(entry)
  376. chunk.sort(key=lambda e: -e["num_joined_members"])
  377. defer.returnValue({
  378. "chunk": chunk,
  379. "total_room_count_estimate": len(room_results),
  380. })
  381. @defer.inlineCallbacks
  382. def add_room_to_group(self, group_id, requester_user_id, room_id, content):
  383. """Add room to group
  384. """
  385. RoomID.from_string(room_id) # Ensure valid room id
  386. yield self.check_group_is_ours(
  387. group_id, and_exists=True, and_is_admin=requester_user_id
  388. )
  389. is_public = _parse_visibility_from_contents(content)
  390. yield self.store.add_room_to_group(group_id, room_id, is_public=is_public)
  391. defer.returnValue({})
  392. @defer.inlineCallbacks
  393. def remove_room_from_group(self, group_id, requester_user_id, room_id):
  394. """Remove room from group
  395. """
  396. yield self.check_group_is_ours(
  397. group_id, and_exists=True, and_is_admin=requester_user_id
  398. )
  399. yield self.store.remove_room_from_group(group_id, room_id)
  400. defer.returnValue({})
  401. @defer.inlineCallbacks
  402. def invite_to_group(self, group_id, user_id, requester_user_id, content):
  403. """Invite user to group
  404. """
  405. group = yield self.check_group_is_ours(
  406. group_id, and_exists=True, and_is_admin=requester_user_id
  407. )
  408. # TODO: Check if user knocked
  409. # TODO: Check if user is already invited
  410. content = {
  411. "profile": {
  412. "name": group["name"],
  413. "avatar_url": group["avatar_url"],
  414. },
  415. "inviter": requester_user_id,
  416. }
  417. if self.hs.is_mine_id(user_id):
  418. groups_local = self.hs.get_groups_local_handler()
  419. res = yield groups_local.on_invite(group_id, user_id, content)
  420. local_attestation = None
  421. else:
  422. local_attestation = self.attestations.create_attestation(group_id, user_id)
  423. content.update({
  424. "attestation": local_attestation,
  425. })
  426. res = yield self.transport_client.invite_to_group_notification(
  427. get_domain_from_id(user_id), group_id, user_id, content
  428. )
  429. user_profile = res.get("user_profile", {})
  430. yield self.store.add_remote_profile_cache(
  431. user_id,
  432. displayname=user_profile.get("displayname"),
  433. avatar_url=user_profile.get("avatar_url"),
  434. )
  435. if res["state"] == "join":
  436. if not self.hs.is_mine_id(user_id):
  437. remote_attestation = res["attestation"]
  438. yield self.attestations.verify_attestation(
  439. remote_attestation,
  440. user_id=user_id,
  441. group_id=group_id,
  442. )
  443. else:
  444. remote_attestation = None
  445. yield self.store.add_user_to_group(
  446. group_id, user_id,
  447. is_admin=False,
  448. is_public=False, # TODO
  449. local_attestation=local_attestation,
  450. remote_attestation=remote_attestation,
  451. )
  452. elif res["state"] == "invite":
  453. yield self.store.add_group_invite(
  454. group_id, user_id,
  455. )
  456. defer.returnValue({
  457. "state": "invite"
  458. })
  459. elif res["state"] == "reject":
  460. defer.returnValue({
  461. "state": "reject"
  462. })
  463. else:
  464. raise SynapseError(502, "Unknown state returned by HS")
  465. @defer.inlineCallbacks
  466. def accept_invite(self, group_id, user_id, content):
  467. """User tries to accept an invite to the group.
  468. This is different from them asking to join, and so should error if no
  469. invite exists (and they're not a member of the group)
  470. """
  471. yield self.check_group_is_ours(group_id, and_exists=True)
  472. if not self.store.is_user_invited_to_local_group(group_id, user_id):
  473. raise SynapseError(403, "User not invited to group")
  474. if not self.hs.is_mine_id(user_id):
  475. remote_attestation = content["attestation"]
  476. yield self.attestations.verify_attestation(
  477. remote_attestation,
  478. user_id=user_id,
  479. group_id=group_id,
  480. )
  481. else:
  482. remote_attestation = None
  483. local_attestation = self.attestations.create_attestation(group_id, user_id)
  484. is_public = _parse_visibility_from_contents(content)
  485. yield self.store.add_user_to_group(
  486. group_id, user_id,
  487. is_admin=False,
  488. is_public=is_public,
  489. local_attestation=local_attestation,
  490. remote_attestation=remote_attestation,
  491. )
  492. defer.returnValue({
  493. "state": "join",
  494. "attestation": local_attestation,
  495. })
  496. @defer.inlineCallbacks
  497. def knock(self, group_id, user_id, content):
  498. """A user requests becoming a member of the group
  499. """
  500. yield self.check_group_is_ours(group_id, and_exists=True)
  501. raise NotImplementedError()
  502. @defer.inlineCallbacks
  503. def accept_knock(self, group_id, user_id, content):
  504. """Accept a users knock to the room.
  505. Errors if the user hasn't knocked, rather than inviting them.
  506. """
  507. yield self.check_group_is_ours(group_id, and_exists=True)
  508. raise NotImplementedError()
  509. @defer.inlineCallbacks
  510. def remove_user_from_group(self, group_id, user_id, requester_user_id, content):
  511. """Remove a user from the group; either a user is leaving or and admin
  512. kicked htem.
  513. """
  514. yield self.check_group_is_ours(group_id, and_exists=True)
  515. is_kick = False
  516. if requester_user_id != user_id:
  517. is_admin = yield self.store.is_user_admin_in_group(
  518. group_id, requester_user_id
  519. )
  520. if not is_admin:
  521. raise SynapseError(403, "User is not admin in group")
  522. is_kick = True
  523. yield self.store.remove_user_from_group(
  524. group_id, user_id,
  525. )
  526. if is_kick:
  527. if self.hs.is_mine_id(user_id):
  528. groups_local = self.hs.get_groups_local_handler()
  529. yield groups_local.user_removed_from_group(group_id, user_id, {})
  530. else:
  531. yield self.transport_client.remove_user_from_group_notification(
  532. get_domain_from_id(user_id), group_id, user_id, {}
  533. )
  534. if not self.hs.is_mine_id(user_id):
  535. yield self.store.maybe_delete_remote_profile_cache(user_id)
  536. defer.returnValue({})
  537. @defer.inlineCallbacks
  538. def create_group(self, group_id, user_id, content):
  539. group = yield self.check_group_is_ours(group_id)
  540. _validate_group_id(group_id)
  541. logger.info("Attempting to create group with ID: %r", group_id)
  542. if group:
  543. raise SynapseError(400, "Group already exists")
  544. is_admin = yield self.auth.is_server_admin(UserID.from_string(user_id))
  545. if not is_admin:
  546. if not self.hs.config.enable_group_creation:
  547. raise SynapseError(
  548. 403, "Only server admin can create group on this server",
  549. )
  550. localpart = GroupID.from_string(group_id).localpart
  551. if not localpart.startswith(self.hs.config.group_creation_prefix):
  552. raise SynapseError(
  553. 400,
  554. "Can only create groups with prefix %r on this server" % (
  555. self.hs.config.group_creation_prefix,
  556. ),
  557. )
  558. profile = content.get("profile", {})
  559. name = profile.get("name")
  560. avatar_url = profile.get("avatar_url")
  561. short_description = profile.get("short_description")
  562. long_description = profile.get("long_description")
  563. user_profile = content.get("user_profile", {})
  564. yield self.store.create_group(
  565. group_id,
  566. user_id,
  567. name=name,
  568. avatar_url=avatar_url,
  569. short_description=short_description,
  570. long_description=long_description,
  571. )
  572. if not self.hs.is_mine_id(user_id):
  573. remote_attestation = content["attestation"]
  574. yield self.attestations.verify_attestation(
  575. remote_attestation,
  576. user_id=user_id,
  577. group_id=group_id,
  578. )
  579. local_attestation = self.attestations.create_attestation(group_id, user_id)
  580. else:
  581. local_attestation = None
  582. remote_attestation = None
  583. yield self.store.add_user_to_group(
  584. group_id, user_id,
  585. is_admin=True,
  586. is_public=True, # TODO
  587. local_attestation=local_attestation,
  588. remote_attestation=remote_attestation,
  589. )
  590. if not self.hs.is_mine_id(user_id):
  591. yield self.store.add_remote_profile_cache(
  592. user_id,
  593. displayname=user_profile.get("displayname"),
  594. avatar_url=user_profile.get("avatar_url"),
  595. )
  596. defer.returnValue({
  597. "group_id": group_id,
  598. })
  599. def _parse_visibility_from_contents(content):
  600. """Given a content for a request parse out whether the entity should be
  601. public or not
  602. """
  603. visibility = content.get("visibility")
  604. if visibility:
  605. vis_type = visibility["type"]
  606. if vis_type not in ("public", "private"):
  607. raise SynapseError(
  608. 400, "Synapse only supports 'public'/'private' visibility"
  609. )
  610. is_public = vis_type == "public"
  611. else:
  612. is_public = True
  613. return is_public
  614. def _validate_group_id(group_id):
  615. """Validates the group ID is valid for creation on this home server
  616. """
  617. localpart = GroupID.from_string(group_id).localpart
  618. if localpart.lower() != localpart:
  619. raise SynapseError(400, "Group ID must be lower case")
  620. if urllib.quote(localpart.encode('utf-8')) != localpart:
  621. raise SynapseError(
  622. 400,
  623. "Group ID can only contain characters a-z, 0-9, or '_-./'",
  624. )