test_admin.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 New Vector 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. import json
  16. import os
  17. import urllib.parse
  18. from binascii import unhexlify
  19. from mock import Mock
  20. from twisted.internet.defer import Deferred
  21. import synapse.rest.admin
  22. from synapse.http.server import JsonResource
  23. from synapse.logging.context import make_deferred_yieldable
  24. from synapse.rest.admin import VersionServlet
  25. from synapse.rest.client.v1 import login, room
  26. from synapse.rest.client.v2_alpha import groups
  27. from tests import unittest
  28. class VersionTestCase(unittest.HomeserverTestCase):
  29. url = "/_synapse/admin/v1/server_version"
  30. def create_test_json_resource(self):
  31. resource = JsonResource(self.hs)
  32. VersionServlet(self.hs).register(resource)
  33. return resource
  34. def test_version_string(self):
  35. request, channel = self.make_request("GET", self.url, shorthand=False)
  36. self.render(request)
  37. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  38. self.assertEqual(
  39. {"server_version", "python_version"}, set(channel.json_body.keys())
  40. )
  41. class DeleteGroupTestCase(unittest.HomeserverTestCase):
  42. servlets = [
  43. synapse.rest.admin.register_servlets_for_client_rest_resource,
  44. login.register_servlets,
  45. groups.register_servlets,
  46. ]
  47. def prepare(self, reactor, clock, hs):
  48. self.store = hs.get_datastore()
  49. self.admin_user = self.register_user("admin", "pass", admin=True)
  50. self.admin_user_tok = self.login("admin", "pass")
  51. self.other_user = self.register_user("user", "pass")
  52. self.other_user_token = self.login("user", "pass")
  53. def test_delete_group(self):
  54. # Create a new group
  55. request, channel = self.make_request(
  56. "POST",
  57. "/create_group".encode("ascii"),
  58. access_token=self.admin_user_tok,
  59. content={"localpart": "test"},
  60. )
  61. self.render(request)
  62. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  63. group_id = channel.json_body["group_id"]
  64. self._check_group(group_id, expect_code=200)
  65. # Invite/join another user
  66. url = "/groups/%s/admin/users/invite/%s" % (group_id, self.other_user)
  67. request, channel = self.make_request(
  68. "PUT", url.encode("ascii"), access_token=self.admin_user_tok, content={}
  69. )
  70. self.render(request)
  71. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  72. url = "/groups/%s/self/accept_invite" % (group_id,)
  73. request, channel = self.make_request(
  74. "PUT", url.encode("ascii"), access_token=self.other_user_token, content={}
  75. )
  76. self.render(request)
  77. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  78. # Check other user knows they're in the group
  79. self.assertIn(group_id, self._get_groups_user_is_in(self.admin_user_tok))
  80. self.assertIn(group_id, self._get_groups_user_is_in(self.other_user_token))
  81. # Now delete the group
  82. url = "/admin/delete_group/" + group_id
  83. request, channel = self.make_request(
  84. "POST",
  85. url.encode("ascii"),
  86. access_token=self.admin_user_tok,
  87. content={"localpart": "test"},
  88. )
  89. self.render(request)
  90. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  91. # Check group returns 404
  92. self._check_group(group_id, expect_code=404)
  93. # Check users don't think they're in the group
  94. self.assertNotIn(group_id, self._get_groups_user_is_in(self.admin_user_tok))
  95. self.assertNotIn(group_id, self._get_groups_user_is_in(self.other_user_token))
  96. def _check_group(self, group_id, expect_code):
  97. """Assert that trying to fetch the given group results in the given
  98. HTTP status code
  99. """
  100. url = "/groups/%s/profile" % (group_id,)
  101. request, channel = self.make_request(
  102. "GET", url.encode("ascii"), access_token=self.admin_user_tok
  103. )
  104. self.render(request)
  105. self.assertEqual(
  106. expect_code, int(channel.result["code"]), msg=channel.result["body"]
  107. )
  108. def _get_groups_user_is_in(self, access_token):
  109. """Returns the list of groups the user is in (given their access token)
  110. """
  111. request, channel = self.make_request(
  112. "GET", "/joined_groups".encode("ascii"), access_token=access_token
  113. )
  114. self.render(request)
  115. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  116. return channel.json_body["groups"]
  117. class QuarantineMediaTestCase(unittest.HomeserverTestCase):
  118. """Test /quarantine_media admin API.
  119. """
  120. servlets = [
  121. synapse.rest.admin.register_servlets,
  122. synapse.rest.admin.register_servlets_for_media_repo,
  123. login.register_servlets,
  124. room.register_servlets,
  125. ]
  126. def prepare(self, reactor, clock, hs):
  127. self.store = hs.get_datastore()
  128. self.hs = hs
  129. # Allow for uploading and downloading to/from the media repo
  130. self.media_repo = hs.get_media_repository_resource()
  131. self.download_resource = self.media_repo.children[b"download"]
  132. self.upload_resource = self.media_repo.children[b"upload"]
  133. self.image_data = unhexlify(
  134. b"89504e470d0a1a0a0000000d4948445200000001000000010806"
  135. b"0000001f15c4890000000a49444154789c63000100000500010d"
  136. b"0a2db40000000049454e44ae426082"
  137. )
  138. def make_homeserver(self, reactor, clock):
  139. self.fetches = []
  140. def get_file(destination, path, output_stream, args=None, max_size=None):
  141. """
  142. Returns tuple[int,dict,str,int] of file length, response headers,
  143. absolute URI, and response code.
  144. """
  145. def write_to(r):
  146. data, response = r
  147. output_stream.write(data)
  148. return response
  149. d = Deferred()
  150. d.addCallback(write_to)
  151. self.fetches.append((d, destination, path, args))
  152. return make_deferred_yieldable(d)
  153. client = Mock()
  154. client.get_file = get_file
  155. self.storage_path = self.mktemp()
  156. self.media_store_path = self.mktemp()
  157. os.mkdir(self.storage_path)
  158. os.mkdir(self.media_store_path)
  159. config = self.default_config()
  160. config["media_store_path"] = self.media_store_path
  161. config["thumbnail_requirements"] = {}
  162. config["max_image_pixels"] = 2000000
  163. provider_config = {
  164. "module": "synapse.rest.media.v1.storage_provider.FileStorageProviderBackend",
  165. "store_local": True,
  166. "store_synchronous": False,
  167. "store_remote": True,
  168. "config": {"directory": self.storage_path},
  169. }
  170. config["media_storage_providers"] = [provider_config]
  171. hs = self.setup_test_homeserver(config=config, http_client=client)
  172. return hs
  173. def test_quarantine_media_requires_admin(self):
  174. self.register_user("nonadmin", "pass", admin=False)
  175. non_admin_user_tok = self.login("nonadmin", "pass")
  176. # Attempt quarantine media APIs as non-admin
  177. url = "/_synapse/admin/v1/media/quarantine/example.org/abcde12345"
  178. request, channel = self.make_request(
  179. "POST", url.encode("ascii"), access_token=non_admin_user_tok,
  180. )
  181. self.render(request)
  182. # Expect a forbidden error
  183. self.assertEqual(
  184. 403,
  185. int(channel.result["code"]),
  186. msg="Expected forbidden on quarantining media as a non-admin",
  187. )
  188. # And the roomID/userID endpoint
  189. url = "/_synapse/admin/v1/room/!room%3Aexample.com/media/quarantine"
  190. request, channel = self.make_request(
  191. "POST", url.encode("ascii"), access_token=non_admin_user_tok,
  192. )
  193. self.render(request)
  194. # Expect a forbidden error
  195. self.assertEqual(
  196. 403,
  197. int(channel.result["code"]),
  198. msg="Expected forbidden on quarantining media as a non-admin",
  199. )
  200. def test_quarantine_media_by_id(self):
  201. self.register_user("id_admin", "pass", admin=True)
  202. admin_user_tok = self.login("id_admin", "pass")
  203. self.register_user("id_nonadmin", "pass", admin=False)
  204. non_admin_user_tok = self.login("id_nonadmin", "pass")
  205. # Upload some media into the room
  206. response = self.helper.upload_media(
  207. self.upload_resource, self.image_data, tok=admin_user_tok
  208. )
  209. # Extract media ID from the response
  210. server_name_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  211. server_name, media_id = server_name_and_media_id.split("/")
  212. # Attempt to access the media
  213. request, channel = self.make_request(
  214. "GET",
  215. server_name_and_media_id,
  216. shorthand=False,
  217. access_token=non_admin_user_tok,
  218. )
  219. request.render(self.download_resource)
  220. self.pump(1.0)
  221. # Should be successful
  222. self.assertEqual(200, int(channel.code), msg=channel.result["body"])
  223. # Quarantine the media
  224. url = "/_synapse/admin/v1/media/quarantine/%s/%s" % (
  225. urllib.parse.quote(server_name),
  226. urllib.parse.quote(media_id),
  227. )
  228. request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
  229. self.render(request)
  230. self.pump(1.0)
  231. self.assertEqual(200, int(channel.code), msg=channel.result["body"])
  232. # Attempt to access the media
  233. request, channel = self.make_request(
  234. "GET",
  235. server_name_and_media_id,
  236. shorthand=False,
  237. access_token=admin_user_tok,
  238. )
  239. request.render(self.download_resource)
  240. self.pump(1.0)
  241. # Should be quarantined
  242. self.assertEqual(
  243. 404,
  244. int(channel.code),
  245. msg=(
  246. "Expected to receive a 404 on accessing quarantined media: %s"
  247. % server_name_and_media_id
  248. ),
  249. )
  250. def test_quarantine_all_media_in_room(self, override_url_template=None):
  251. self.register_user("room_admin", "pass", admin=True)
  252. admin_user_tok = self.login("room_admin", "pass")
  253. non_admin_user = self.register_user("room_nonadmin", "pass", admin=False)
  254. non_admin_user_tok = self.login("room_nonadmin", "pass")
  255. room_id = self.helper.create_room_as(non_admin_user, tok=admin_user_tok)
  256. self.helper.join(room_id, non_admin_user, tok=non_admin_user_tok)
  257. # Upload some media
  258. response_1 = self.helper.upload_media(
  259. self.upload_resource, self.image_data, tok=non_admin_user_tok
  260. )
  261. response_2 = self.helper.upload_media(
  262. self.upload_resource, self.image_data, tok=non_admin_user_tok
  263. )
  264. # Extract mxcs
  265. mxc_1 = response_1["content_uri"]
  266. mxc_2 = response_2["content_uri"]
  267. # Send it into the room
  268. self.helper.send_event(
  269. room_id,
  270. "m.room.message",
  271. content={"body": "image-1", "msgtype": "m.image", "url": mxc_1},
  272. txn_id="111",
  273. tok=non_admin_user_tok,
  274. )
  275. self.helper.send_event(
  276. room_id,
  277. "m.room.message",
  278. content={"body": "image-2", "msgtype": "m.image", "url": mxc_2},
  279. txn_id="222",
  280. tok=non_admin_user_tok,
  281. )
  282. # Quarantine all media in the room
  283. if override_url_template:
  284. url = override_url_template % urllib.parse.quote(room_id)
  285. else:
  286. url = "/_synapse/admin/v1/room/%s/media/quarantine" % urllib.parse.quote(
  287. room_id
  288. )
  289. request, channel = self.make_request("POST", url, access_token=admin_user_tok,)
  290. self.render(request)
  291. self.pump(1.0)
  292. self.assertEqual(200, int(channel.code), msg=channel.result["body"])
  293. self.assertEqual(
  294. json.loads(channel.result["body"].decode("utf-8")),
  295. {"num_quarantined": 2},
  296. "Expected 2 quarantined items",
  297. )
  298. # Convert mxc URLs to server/media_id strings
  299. server_and_media_id_1 = mxc_1[6:]
  300. server_and_media_id_2 = mxc_2[6:]
  301. # Test that we cannot download any of the media anymore
  302. request, channel = self.make_request(
  303. "GET",
  304. server_and_media_id_1,
  305. shorthand=False,
  306. access_token=non_admin_user_tok,
  307. )
  308. request.render(self.download_resource)
  309. self.pump(1.0)
  310. # Should be quarantined
  311. self.assertEqual(
  312. 404,
  313. int(channel.code),
  314. msg=(
  315. "Expected to receive a 404 on accessing quarantined media: %s"
  316. % server_and_media_id_1
  317. ),
  318. )
  319. request, channel = self.make_request(
  320. "GET",
  321. server_and_media_id_2,
  322. shorthand=False,
  323. access_token=non_admin_user_tok,
  324. )
  325. request.render(self.download_resource)
  326. self.pump(1.0)
  327. # Should be quarantined
  328. self.assertEqual(
  329. 404,
  330. int(channel.code),
  331. msg=(
  332. "Expected to receive a 404 on accessing quarantined media: %s"
  333. % server_and_media_id_2
  334. ),
  335. )
  336. def test_quaraantine_all_media_in_room_deprecated_api_path(self):
  337. # Perform the above test with the deprecated API path
  338. self.test_quarantine_all_media_in_room("/_synapse/admin/v1/quarantine_media/%s")
  339. def test_quarantine_all_media_by_user(self):
  340. self.register_user("user_admin", "pass", admin=True)
  341. admin_user_tok = self.login("user_admin", "pass")
  342. non_admin_user = self.register_user("user_nonadmin", "pass", admin=False)
  343. non_admin_user_tok = self.login("user_nonadmin", "pass")
  344. # Upload some media
  345. response_1 = self.helper.upload_media(
  346. self.upload_resource, self.image_data, tok=non_admin_user_tok
  347. )
  348. response_2 = self.helper.upload_media(
  349. self.upload_resource, self.image_data, tok=non_admin_user_tok
  350. )
  351. # Extract media IDs
  352. server_and_media_id_1 = response_1["content_uri"][6:]
  353. server_and_media_id_2 = response_2["content_uri"][6:]
  354. # Quarantine all media by this user
  355. url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
  356. non_admin_user
  357. )
  358. request, channel = self.make_request(
  359. "POST", url.encode("ascii"), access_token=admin_user_tok,
  360. )
  361. self.render(request)
  362. self.pump(1.0)
  363. self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"])
  364. self.assertEqual(
  365. json.loads(channel.result["body"].decode("utf-8")),
  366. {"num_quarantined": 2},
  367. "Expected 2 quarantined items",
  368. )
  369. # Attempt to access each piece of media
  370. request, channel = self.make_request(
  371. "GET",
  372. server_and_media_id_1,
  373. shorthand=False,
  374. access_token=non_admin_user_tok,
  375. )
  376. request.render(self.download_resource)
  377. self.pump(1.0)
  378. # Should be quarantined
  379. self.assertEqual(
  380. 404,
  381. int(channel.code),
  382. msg=(
  383. "Expected to receive a 404 on accessing quarantined media: %s"
  384. % server_and_media_id_1,
  385. ),
  386. )
  387. # Attempt to access each piece of media
  388. request, channel = self.make_request(
  389. "GET",
  390. server_and_media_id_2,
  391. shorthand=False,
  392. access_token=non_admin_user_tok,
  393. )
  394. request.render(self.download_resource)
  395. self.pump(1.0)
  396. # Should be quarantined
  397. self.assertEqual(
  398. 404,
  399. int(channel.code),
  400. msg=(
  401. "Expected to receive a 404 on accessing quarantined media: %s"
  402. % server_and_media_id_2
  403. ),
  404. )