client.py 30 KB

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