test_admin.py 17 KB

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