client.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-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 logging
  17. import urllib
  18. from twisted.internet import defer
  19. from synapse.api.constants import Membership
  20. from synapse.api.urls import FEDERATION_PREFIX as PREFIX
  21. from synapse.util.logutils import log_function
  22. logger = logging.getLogger(__name__)
  23. class TransportLayerClient(object):
  24. """Sends federation HTTP requests to other servers"""
  25. def __init__(self, hs):
  26. self.server_name = hs.hostname
  27. self.client = hs.get_http_client()
  28. @log_function
  29. def get_room_state(self, destination, room_id, event_id):
  30. """ Requests all state for a given room from the given server at the
  31. given event.
  32. Args:
  33. destination (str): The host name of the remote home server we want
  34. to get the state from.
  35. context (str): The name of the context we want the state of
  36. event_id (str): The event we want the context at.
  37. Returns:
  38. Deferred: Results in a dict received from the remote homeserver.
  39. """
  40. logger.debug("get_room_state dest=%s, room=%s",
  41. destination, room_id)
  42. path = _create_path(PREFIX, "/state/%s/", room_id)
  43. return self.client.get_json(
  44. destination, path=path, args={"event_id": event_id},
  45. )
  46. @log_function
  47. def get_room_state_ids(self, destination, room_id, event_id):
  48. """ Requests all state for a given room from the given server at the
  49. given event. Returns the state's event_id's
  50. Args:
  51. destination (str): The host name of the remote home server we want
  52. to get the state from.
  53. context (str): The name of the context we want the state of
  54. event_id (str): The event we want the context at.
  55. Returns:
  56. Deferred: Results in a dict received from the remote homeserver.
  57. """
  58. logger.debug("get_room_state_ids dest=%s, room=%s",
  59. destination, room_id)
  60. path = _create_path(PREFIX, "/state_ids/%s/", room_id)
  61. return self.client.get_json(
  62. destination, path=path, args={"event_id": event_id},
  63. )
  64. @log_function
  65. def get_event(self, destination, event_id, timeout=None):
  66. """ Requests the pdu with give id and origin from the given server.
  67. Args:
  68. destination (str): The host name of the remote home server we want
  69. to get the state from.
  70. event_id (str): The id of the event being requested.
  71. timeout (int): How long to try (in ms) the destination for before
  72. giving up. None indicates no timeout.
  73. Returns:
  74. Deferred: Results in a dict received from the remote homeserver.
  75. """
  76. logger.debug("get_pdu dest=%s, event_id=%s",
  77. destination, event_id)
  78. path = _create_path(PREFIX, "/event/%s/", event_id)
  79. return self.client.get_json(destination, path=path, timeout=timeout)
  80. @log_function
  81. def backfill(self, destination, room_id, event_tuples, limit):
  82. """ Requests `limit` previous PDUs in a given context before list of
  83. PDUs.
  84. Args:
  85. dest (str)
  86. room_id (str)
  87. event_tuples (list)
  88. limt (int)
  89. Returns:
  90. Deferred: Results in a dict received from the remote homeserver.
  91. """
  92. logger.debug(
  93. "backfill dest=%s, room_id=%s, event_tuples=%s, limit=%s",
  94. destination, room_id, repr(event_tuples), str(limit)
  95. )
  96. if not event_tuples:
  97. # TODO: raise?
  98. return
  99. path = _create_path(PREFIX, "/backfill/%s/", room_id)
  100. args = {
  101. "v": event_tuples,
  102. "limit": [str(limit)],
  103. }
  104. return self.client.get_json(
  105. destination,
  106. path=path,
  107. args=args,
  108. )
  109. @defer.inlineCallbacks
  110. @log_function
  111. def send_transaction(self, transaction, json_data_callback=None):
  112. """ Sends the given Transaction to its destination
  113. Args:
  114. transaction (Transaction)
  115. Returns:
  116. Deferred: Results of the deferred is a tuple in the form of
  117. (response_code, response_body) where the response_body is a
  118. python dict decoded from json
  119. """
  120. logger.debug(
  121. "send_data dest=%s, txid=%s",
  122. transaction.destination, transaction.transaction_id
  123. )
  124. if transaction.destination == self.server_name:
  125. raise RuntimeError("Transport layer cannot send to itself!")
  126. # FIXME: This is only used by the tests. The actual json sent is
  127. # generated by the json_data_callback.
  128. json_data = transaction.get_dict()
  129. path = _create_path(PREFIX, "/send/%s/", transaction.transaction_id)
  130. response = yield self.client.put_json(
  131. transaction.destination,
  132. path=path,
  133. data=json_data,
  134. json_data_callback=json_data_callback,
  135. long_retries=True,
  136. backoff_on_404=True, # If we get a 404 the other side has gone
  137. )
  138. logger.debug(
  139. "send_data dest=%s, txid=%s, got response: 200",
  140. transaction.destination, transaction.transaction_id,
  141. )
  142. defer.returnValue(response)
  143. @defer.inlineCallbacks
  144. @log_function
  145. def make_query(self, destination, query_type, args, retry_on_dns_fail,
  146. ignore_backoff=False):
  147. path = _create_path(PREFIX, "/query/%s", query_type)
  148. content = yield self.client.get_json(
  149. destination=destination,
  150. path=path,
  151. args=args,
  152. retry_on_dns_fail=retry_on_dns_fail,
  153. timeout=10000,
  154. ignore_backoff=ignore_backoff,
  155. )
  156. defer.returnValue(content)
  157. @defer.inlineCallbacks
  158. @log_function
  159. def make_membership_event(self, destination, room_id, user_id, membership):
  160. """Asks a remote server to build and sign us a membership event
  161. Note that this does not append any events to any graphs.
  162. Args:
  163. destination (str): address of remote homeserver
  164. room_id (str): room to join/leave
  165. user_id (str): user to be joined/left
  166. membership (str): one of join/leave
  167. Returns:
  168. Deferred: Succeeds when we get a 2xx HTTP response. The result
  169. will be the decoded JSON body (ie, the new event).
  170. Fails with ``HTTPRequestException`` if we get an HTTP response
  171. code >= 300.
  172. Fails with ``NotRetryingDestination`` if we are not yet ready
  173. to retry this server.
  174. Fails with ``FederationDeniedError`` if the remote destination
  175. is not in our federation whitelist
  176. """
  177. valid_memberships = {Membership.JOIN, Membership.LEAVE}
  178. if membership not in valid_memberships:
  179. raise RuntimeError(
  180. "make_membership_event called with membership='%s', must be one of %s" %
  181. (membership, ",".join(valid_memberships))
  182. )
  183. path = _create_path(PREFIX, "/make_%s/%s/%s", membership, room_id, user_id)
  184. ignore_backoff = False
  185. retry_on_dns_fail = False
  186. if membership == Membership.LEAVE:
  187. # we particularly want to do our best to send leave events. The
  188. # problem is that if it fails, we won't retry it later, so if the
  189. # remote server was just having a momentary blip, the room will be
  190. # out of sync.
  191. ignore_backoff = True
  192. retry_on_dns_fail = True
  193. content = yield self.client.get_json(
  194. destination=destination,
  195. path=path,
  196. retry_on_dns_fail=retry_on_dns_fail,
  197. timeout=20000,
  198. ignore_backoff=ignore_backoff,
  199. )
  200. defer.returnValue(content)
  201. @defer.inlineCallbacks
  202. @log_function
  203. def send_join(self, destination, room_id, event_id, content):
  204. path = _create_path(PREFIX, "/send_join/%s/%s", room_id, event_id)
  205. response = yield self.client.put_json(
  206. destination=destination,
  207. path=path,
  208. data=content,
  209. )
  210. defer.returnValue(response)
  211. @defer.inlineCallbacks
  212. @log_function
  213. def send_leave(self, destination, room_id, event_id, content):
  214. path = _create_path(PREFIX, "/send_leave/%s/%s", room_id, event_id)
  215. response = yield self.client.put_json(
  216. destination=destination,
  217. path=path,
  218. data=content,
  219. # we want to do our best to send this through. The problem is
  220. # that if it fails, we won't retry it later, so if the remote
  221. # server was just having a momentary blip, the room will be out of
  222. # sync.
  223. ignore_backoff=True,
  224. )
  225. defer.returnValue(response)
  226. @defer.inlineCallbacks
  227. @log_function
  228. def send_invite(self, destination, room_id, event_id, content):
  229. path = _create_path(PREFIX, "/invite/%s/%s", room_id, event_id)
  230. response = yield self.client.put_json(
  231. destination=destination,
  232. path=path,
  233. data=content,
  234. ignore_backoff=True,
  235. )
  236. defer.returnValue(response)
  237. @defer.inlineCallbacks
  238. @log_function
  239. def get_public_rooms(self, remote_server, limit, since_token,
  240. search_filter=None, include_all_networks=False,
  241. third_party_instance_id=None):
  242. path = PREFIX + "/publicRooms"
  243. args = {
  244. "include_all_networks": "true" if include_all_networks else "false",
  245. }
  246. if third_party_instance_id:
  247. args["third_party_instance_id"] = third_party_instance_id,
  248. if limit:
  249. args["limit"] = [str(limit)]
  250. if since_token:
  251. args["since"] = [since_token]
  252. # TODO(erikj): Actually send the search_filter across federation.
  253. response = yield self.client.get_json(
  254. destination=remote_server,
  255. path=path,
  256. args=args,
  257. ignore_backoff=True,
  258. )
  259. defer.returnValue(response)
  260. @defer.inlineCallbacks
  261. @log_function
  262. def exchange_third_party_invite(self, destination, room_id, event_dict):
  263. path = _create_path(PREFIX, "/exchange_third_party_invite/%s", room_id,)
  264. response = yield self.client.put_json(
  265. destination=destination,
  266. path=path,
  267. data=event_dict,
  268. )
  269. defer.returnValue(response)
  270. @defer.inlineCallbacks
  271. @log_function
  272. def get_event_auth(self, destination, room_id, event_id):
  273. path = _create_path(PREFIX, "/event_auth/%s/%s", room_id, event_id)
  274. content = yield self.client.get_json(
  275. destination=destination,
  276. path=path,
  277. )
  278. defer.returnValue(content)
  279. @defer.inlineCallbacks
  280. @log_function
  281. def send_query_auth(self, destination, room_id, event_id, content):
  282. path = _create_path(PREFIX, "/query_auth/%s/%s", room_id, event_id)
  283. content = yield self.client.post_json(
  284. destination=destination,
  285. path=path,
  286. data=content,
  287. )
  288. defer.returnValue(content)
  289. @defer.inlineCallbacks
  290. @log_function
  291. def query_client_keys(self, destination, query_content, timeout):
  292. """Query the device keys for a list of user ids hosted on a remote
  293. server.
  294. Request:
  295. {
  296. "device_keys": {
  297. "<user_id>": ["<device_id>"]
  298. } }
  299. Response:
  300. {
  301. "device_keys": {
  302. "<user_id>": {
  303. "<device_id>": {...}
  304. } } }
  305. Args:
  306. destination(str): The server to query.
  307. query_content(dict): The user ids to query.
  308. Returns:
  309. A dict containg the device keys.
  310. """
  311. path = PREFIX + "/user/keys/query"
  312. content = yield self.client.post_json(
  313. destination=destination,
  314. path=path,
  315. data=query_content,
  316. timeout=timeout,
  317. )
  318. defer.returnValue(content)
  319. @defer.inlineCallbacks
  320. @log_function
  321. def query_user_devices(self, destination, user_id, timeout):
  322. """Query the devices for a user id hosted on a remote server.
  323. Response:
  324. {
  325. "stream_id": "...",
  326. "devices": [ { ... } ]
  327. }
  328. Args:
  329. destination(str): The server to query.
  330. query_content(dict): The user ids to query.
  331. Returns:
  332. A dict containg the device keys.
  333. """
  334. path = _create_path(PREFIX, "/user/devices/%s", user_id)
  335. content = yield self.client.get_json(
  336. destination=destination,
  337. path=path,
  338. timeout=timeout,
  339. )
  340. defer.returnValue(content)
  341. @defer.inlineCallbacks
  342. @log_function
  343. def claim_client_keys(self, destination, query_content, timeout):
  344. """Claim one-time keys for a list of devices hosted on a remote server.
  345. Request:
  346. {
  347. "one_time_keys": {
  348. "<user_id>": {
  349. "<device_id>": "<algorithm>"
  350. } } }
  351. Response:
  352. {
  353. "device_keys": {
  354. "<user_id>": {
  355. "<device_id>": {
  356. "<algorithm>:<key_id>": "<key_base64>"
  357. } } } }
  358. Args:
  359. destination(str): The server to query.
  360. query_content(dict): The user ids to query.
  361. Returns:
  362. A dict containg the one-time keys.
  363. """
  364. path = PREFIX + "/user/keys/claim"
  365. content = yield self.client.post_json(
  366. destination=destination,
  367. path=path,
  368. data=query_content,
  369. timeout=timeout,
  370. )
  371. defer.returnValue(content)
  372. @defer.inlineCallbacks
  373. @log_function
  374. def get_missing_events(self, destination, room_id, earliest_events,
  375. latest_events, limit, min_depth, timeout):
  376. path = _create_path(PREFIX, "/get_missing_events/%s", room_id,)
  377. content = yield self.client.post_json(
  378. destination=destination,
  379. path=path,
  380. data={
  381. "limit": int(limit),
  382. "min_depth": int(min_depth),
  383. "earliest_events": earliest_events,
  384. "latest_events": latest_events,
  385. },
  386. timeout=timeout,
  387. )
  388. defer.returnValue(content)
  389. @log_function
  390. def get_group_profile(self, destination, group_id, requester_user_id):
  391. """Get a group profile
  392. """
  393. path = _create_path(PREFIX, "/groups/%s/profile", group_id,)
  394. return self.client.get_json(
  395. destination=destination,
  396. path=path,
  397. args={"requester_user_id": requester_user_id},
  398. ignore_backoff=True,
  399. )
  400. @log_function
  401. def update_group_profile(self, destination, group_id, requester_user_id, content):
  402. """Update a remote group profile
  403. Args:
  404. destination (str)
  405. group_id (str)
  406. requester_user_id (str)
  407. content (dict): The new profile of the group
  408. """
  409. path = _create_path(PREFIX, "/groups/%s/profile", group_id,)
  410. return self.client.post_json(
  411. destination=destination,
  412. path=path,
  413. args={"requester_user_id": requester_user_id},
  414. data=content,
  415. ignore_backoff=True,
  416. )
  417. @log_function
  418. def get_group_summary(self, destination, group_id, requester_user_id):
  419. """Get a group summary
  420. """
  421. path = _create_path(PREFIX, "/groups/%s/summary", group_id,)
  422. return self.client.get_json(
  423. destination=destination,
  424. path=path,
  425. args={"requester_user_id": requester_user_id},
  426. ignore_backoff=True,
  427. )
  428. @log_function
  429. def get_rooms_in_group(self, destination, group_id, requester_user_id):
  430. """Get all rooms in a group
  431. """
  432. path = _create_path(PREFIX, "/groups/%s/rooms", group_id,)
  433. return self.client.get_json(
  434. destination=destination,
  435. path=path,
  436. args={"requester_user_id": requester_user_id},
  437. ignore_backoff=True,
  438. )
  439. def add_room_to_group(self, destination, group_id, requester_user_id, room_id,
  440. content):
  441. """Add a room to a group
  442. """
  443. path = _create_path(PREFIX, "/groups/%s/room/%s", group_id, room_id,)
  444. return self.client.post_json(
  445. destination=destination,
  446. path=path,
  447. args={"requester_user_id": requester_user_id},
  448. data=content,
  449. ignore_backoff=True,
  450. )
  451. def update_room_in_group(self, destination, group_id, requester_user_id, room_id,
  452. config_key, content):
  453. """Update room in group
  454. """
  455. path = _create_path(
  456. PREFIX, "/groups/%s/room/%s/config/%s",
  457. group_id, room_id, config_key,
  458. )
  459. return self.client.post_json(
  460. destination=destination,
  461. path=path,
  462. args={"requester_user_id": requester_user_id},
  463. data=content,
  464. ignore_backoff=True,
  465. )
  466. def remove_room_from_group(self, destination, group_id, requester_user_id, room_id):
  467. """Remove a room from a group
  468. """
  469. path = _create_path(PREFIX, "/groups/%s/room/%s", group_id, room_id,)
  470. return self.client.delete_json(
  471. destination=destination,
  472. path=path,
  473. args={"requester_user_id": requester_user_id},
  474. ignore_backoff=True,
  475. )
  476. @log_function
  477. def get_users_in_group(self, destination, group_id, requester_user_id):
  478. """Get users in a group
  479. """
  480. path = _create_path(PREFIX, "/groups/%s/users", group_id,)
  481. return self.client.get_json(
  482. destination=destination,
  483. path=path,
  484. args={"requester_user_id": requester_user_id},
  485. ignore_backoff=True,
  486. )
  487. @log_function
  488. def get_invited_users_in_group(self, destination, group_id, requester_user_id):
  489. """Get users that have been invited to a group
  490. """
  491. path = _create_path(PREFIX, "/groups/%s/invited_users", group_id,)
  492. return self.client.get_json(
  493. destination=destination,
  494. path=path,
  495. args={"requester_user_id": requester_user_id},
  496. ignore_backoff=True,
  497. )
  498. @log_function
  499. def accept_group_invite(self, destination, group_id, user_id, content):
  500. """Accept a group invite
  501. """
  502. path = _create_path(
  503. PREFIX, "/groups/%s/users/%s/accept_invite",
  504. group_id, user_id,
  505. )
  506. return self.client.post_json(
  507. destination=destination,
  508. path=path,
  509. data=content,
  510. ignore_backoff=True,
  511. )
  512. @log_function
  513. def join_group(self, destination, group_id, user_id, content):
  514. """Attempts to join a group
  515. """
  516. path = _create_path(PREFIX, "/groups/%s/users/%s/join", group_id, user_id)
  517. return self.client.post_json(
  518. destination=destination,
  519. path=path,
  520. data=content,
  521. ignore_backoff=True,
  522. )
  523. @log_function
  524. def invite_to_group(self, destination, group_id, user_id, requester_user_id, content):
  525. """Invite a user to a group
  526. """
  527. path = _create_path(PREFIX, "/groups/%s/users/%s/invite", group_id, user_id)
  528. return self.client.post_json(
  529. destination=destination,
  530. path=path,
  531. args={"requester_user_id": requester_user_id},
  532. data=content,
  533. ignore_backoff=True,
  534. )
  535. @log_function
  536. def invite_to_group_notification(self, destination, group_id, user_id, content):
  537. """Sent by group server to inform a user's server that they have been
  538. invited.
  539. """
  540. path = _create_path(PREFIX, "/groups/local/%s/users/%s/invite", group_id, user_id)
  541. return self.client.post_json(
  542. destination=destination,
  543. path=path,
  544. data=content,
  545. ignore_backoff=True,
  546. )
  547. @log_function
  548. def remove_user_from_group(self, destination, group_id, requester_user_id,
  549. user_id, content):
  550. """Remove a user fron a group
  551. """
  552. path = _create_path(PREFIX, "/groups/%s/users/%s/remove", group_id, user_id)
  553. return self.client.post_json(
  554. destination=destination,
  555. path=path,
  556. args={"requester_user_id": requester_user_id},
  557. data=content,
  558. ignore_backoff=True,
  559. )
  560. @log_function
  561. def remove_user_from_group_notification(self, destination, group_id, user_id,
  562. content):
  563. """Sent by group server to inform a user's server that they have been
  564. kicked from the group.
  565. """
  566. path = _create_path(PREFIX, "/groups/local/%s/users/%s/remove", group_id, user_id)
  567. return self.client.post_json(
  568. destination=destination,
  569. path=path,
  570. data=content,
  571. ignore_backoff=True,
  572. )
  573. @log_function
  574. def renew_group_attestation(self, destination, group_id, user_id, content):
  575. """Sent by either a group server or a user's server to periodically update
  576. the attestations
  577. """
  578. path = _create_path(PREFIX, "/groups/%s/renew_attestation/%s", group_id, user_id)
  579. return self.client.post_json(
  580. destination=destination,
  581. path=path,
  582. data=content,
  583. ignore_backoff=True,
  584. )
  585. @log_function
  586. def update_group_summary_room(self, destination, group_id, user_id, room_id,
  587. category_id, content):
  588. """Update a room entry in a group summary
  589. """
  590. if category_id:
  591. path = _create_path(
  592. PREFIX, "/groups/%s/summary/categories/%s/rooms/%s",
  593. group_id, category_id, room_id,
  594. )
  595. else:
  596. path = _create_path(PREFIX, "/groups/%s/summary/rooms/%s", group_id, room_id,)
  597. return self.client.post_json(
  598. destination=destination,
  599. path=path,
  600. args={"requester_user_id": user_id},
  601. data=content,
  602. ignore_backoff=True,
  603. )
  604. @log_function
  605. def delete_group_summary_room(self, destination, group_id, user_id, room_id,
  606. category_id):
  607. """Delete a room entry in a group summary
  608. """
  609. if category_id:
  610. path = _create_path(
  611. PREFIX + "/groups/%s/summary/categories/%s/rooms/%s",
  612. group_id, category_id, room_id,
  613. )
  614. else:
  615. path = _create_path(PREFIX, "/groups/%s/summary/rooms/%s", group_id, room_id,)
  616. return self.client.delete_json(
  617. destination=destination,
  618. path=path,
  619. args={"requester_user_id": user_id},
  620. ignore_backoff=True,
  621. )
  622. @log_function
  623. def get_group_categories(self, destination, group_id, requester_user_id):
  624. """Get all categories in a group
  625. """
  626. path = _create_path(PREFIX, "/groups/%s/categories", group_id,)
  627. return self.client.get_json(
  628. destination=destination,
  629. path=path,
  630. args={"requester_user_id": requester_user_id},
  631. ignore_backoff=True,
  632. )
  633. @log_function
  634. def get_group_category(self, destination, group_id, requester_user_id, category_id):
  635. """Get category info in a group
  636. """
  637. path = _create_path(PREFIX, "/groups/%s/categories/%s", group_id, category_id,)
  638. return self.client.get_json(
  639. destination=destination,
  640. path=path,
  641. args={"requester_user_id": requester_user_id},
  642. ignore_backoff=True,
  643. )
  644. @log_function
  645. def update_group_category(self, destination, group_id, requester_user_id, category_id,
  646. content):
  647. """Update a category in a group
  648. """
  649. path = _create_path(PREFIX, "/groups/%s/categories/%s", group_id, category_id,)
  650. return self.client.post_json(
  651. destination=destination,
  652. path=path,
  653. args={"requester_user_id": requester_user_id},
  654. data=content,
  655. ignore_backoff=True,
  656. )
  657. @log_function
  658. def delete_group_category(self, destination, group_id, requester_user_id,
  659. category_id):
  660. """Delete a category in a group
  661. """
  662. path = _create_path(PREFIX, "/groups/%s/categories/%s", group_id, category_id,)
  663. return self.client.delete_json(
  664. destination=destination,
  665. path=path,
  666. args={"requester_user_id": requester_user_id},
  667. ignore_backoff=True,
  668. )
  669. @log_function
  670. def get_group_roles(self, destination, group_id, requester_user_id):
  671. """Get all roles in a group
  672. """
  673. path = _create_path(PREFIX, "/groups/%s/roles", group_id,)
  674. return self.client.get_json(
  675. destination=destination,
  676. path=path,
  677. args={"requester_user_id": requester_user_id},
  678. ignore_backoff=True,
  679. )
  680. @log_function
  681. def get_group_role(self, destination, group_id, requester_user_id, role_id):
  682. """Get a roles info
  683. """
  684. path = _create_path(PREFIX, "/groups/%s/roles/%s", group_id, role_id,)
  685. return self.client.get_json(
  686. destination=destination,
  687. path=path,
  688. args={"requester_user_id": requester_user_id},
  689. ignore_backoff=True,
  690. )
  691. @log_function
  692. def update_group_role(self, destination, group_id, requester_user_id, role_id,
  693. content):
  694. """Update a role in a group
  695. """
  696. path = _create_path(PREFIX, "/groups/%s/roles/%s", group_id, role_id,)
  697. return self.client.post_json(
  698. destination=destination,
  699. path=path,
  700. args={"requester_user_id": requester_user_id},
  701. data=content,
  702. ignore_backoff=True,
  703. )
  704. @log_function
  705. def delete_group_role(self, destination, group_id, requester_user_id, role_id):
  706. """Delete a role in a group
  707. """
  708. path = _create_path(PREFIX, "/groups/%s/roles/%s", group_id, role_id,)
  709. return self.client.delete_json(
  710. destination=destination,
  711. path=path,
  712. args={"requester_user_id": requester_user_id},
  713. ignore_backoff=True,
  714. )
  715. @log_function
  716. def update_group_summary_user(self, destination, group_id, requester_user_id,
  717. user_id, role_id, content):
  718. """Update a users entry in a group
  719. """
  720. if role_id:
  721. path = _create_path(
  722. PREFIX, "/groups/%s/summary/roles/%s/users/%s",
  723. group_id, role_id, user_id,
  724. )
  725. else:
  726. path = _create_path(PREFIX, "/groups/%s/summary/users/%s", group_id, user_id,)
  727. return self.client.post_json(
  728. destination=destination,
  729. path=path,
  730. args={"requester_user_id": requester_user_id},
  731. data=content,
  732. ignore_backoff=True,
  733. )
  734. @log_function
  735. def set_group_join_policy(self, destination, group_id, requester_user_id,
  736. content):
  737. """Sets the join policy for a group
  738. """
  739. path = _create_path(PREFIX, "/groups/%s/settings/m.join_policy", group_id,)
  740. return self.client.put_json(
  741. destination=destination,
  742. path=path,
  743. args={"requester_user_id": requester_user_id},
  744. data=content,
  745. ignore_backoff=True,
  746. )
  747. @log_function
  748. def delete_group_summary_user(self, destination, group_id, requester_user_id,
  749. user_id, role_id):
  750. """Delete a users entry in a group
  751. """
  752. if role_id:
  753. path = _create_path(
  754. PREFIX, "/groups/%s/summary/roles/%s/users/%s",
  755. group_id, role_id, user_id,
  756. )
  757. else:
  758. path = _create_path(PREFIX, "/groups/%s/summary/users/%s", group_id, user_id,)
  759. return self.client.delete_json(
  760. destination=destination,
  761. path=path,
  762. args={"requester_user_id": requester_user_id},
  763. ignore_backoff=True,
  764. )
  765. def bulk_get_publicised_groups(self, destination, user_ids):
  766. """Get the groups a list of users are publicising
  767. """
  768. path = PREFIX + "/get_groups_publicised"
  769. content = {"user_ids": user_ids}
  770. return self.client.post_json(
  771. destination=destination,
  772. path=path,
  773. data=content,
  774. ignore_backoff=True,
  775. )
  776. def _create_path(prefix, path, *args):
  777. """Creates a path from the prefix, path template and args. Ensures that
  778. all args are url encoded.
  779. Example:
  780. _create_path(PREFIX, "/event/%s/", event_id)
  781. Args:
  782. prefix (str)
  783. path (str): String template for the path
  784. args: ([str]): Args to insert into path. Each arg will be url encoded
  785. Returns:
  786. str
  787. """
  788. return prefix + path % tuple(urllib.quote(arg, "") for arg in args)