test_media.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. # Copyright 2020 Dirk Klimpel
  2. # Copyright 2021 The Matrix.org Foundation C.I.C.
  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 os
  16. from typing import Dict
  17. from parameterized import parameterized
  18. from twisted.test.proto_helpers import MemoryReactor
  19. from twisted.web.resource import Resource
  20. import synapse.rest.admin
  21. from synapse.api.errors import Codes
  22. from synapse.media.filepath import MediaFilePaths
  23. from synapse.rest.client import login, profile, room
  24. from synapse.server import HomeServer
  25. from synapse.util import Clock
  26. from tests import unittest
  27. from tests.test_utils import SMALL_PNG
  28. VALID_TIMESTAMP = 1609459200000 # 2021-01-01 in milliseconds
  29. INVALID_TIMESTAMP_IN_S = 1893456000 # 2030-01-01 in seconds
  30. class _AdminMediaTests(unittest.HomeserverTestCase):
  31. servlets = [
  32. synapse.rest.admin.register_servlets,
  33. synapse.rest.admin.register_servlets_for_media_repo,
  34. login.register_servlets,
  35. ]
  36. def create_resource_dict(self) -> Dict[str, Resource]:
  37. resources = super().create_resource_dict()
  38. resources["/_matrix/media"] = self.hs.get_media_repository_resource()
  39. return resources
  40. class DeleteMediaByIDTestCase(_AdminMediaTests):
  41. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  42. self.server_name = hs.hostname
  43. self.admin_user = self.register_user("admin", "pass", admin=True)
  44. self.admin_user_tok = self.login("admin", "pass")
  45. self.filepaths = MediaFilePaths(hs.config.media.media_store_path)
  46. def test_no_auth(self) -> None:
  47. """
  48. Try to delete media without authentication.
  49. """
  50. url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, "12345")
  51. channel = self.make_request("DELETE", url, b"{}")
  52. self.assertEqual(
  53. 401,
  54. channel.code,
  55. msg=channel.json_body,
  56. )
  57. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  58. def test_requester_is_no_admin(self) -> None:
  59. """
  60. If the user is not a server admin, an error is returned.
  61. """
  62. self.other_user = self.register_user("user", "pass")
  63. self.other_user_token = self.login("user", "pass")
  64. url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, "12345")
  65. channel = self.make_request(
  66. "DELETE",
  67. url,
  68. access_token=self.other_user_token,
  69. )
  70. self.assertEqual(403, channel.code, msg=channel.json_body)
  71. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  72. def test_media_does_not_exist(self) -> None:
  73. """
  74. Tests that a lookup for a media that does not exist returns a 404
  75. """
  76. url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, "12345")
  77. channel = self.make_request(
  78. "DELETE",
  79. url,
  80. access_token=self.admin_user_tok,
  81. )
  82. self.assertEqual(404, channel.code, msg=channel.json_body)
  83. self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"])
  84. def test_media_is_not_local(self) -> None:
  85. """
  86. Tests that a lookup for a media that is not a local returns a 400
  87. """
  88. url = "/_synapse/admin/v1/media/%s/%s" % ("unknown_domain", "12345")
  89. channel = self.make_request(
  90. "DELETE",
  91. url,
  92. access_token=self.admin_user_tok,
  93. )
  94. self.assertEqual(400, channel.code, msg=channel.json_body)
  95. self.assertEqual("Can only delete local media", channel.json_body["error"])
  96. def test_delete_media(self) -> None:
  97. """
  98. Tests that delete a media is successfully
  99. """
  100. # Upload some media into the room
  101. response = self.helper.upload_media(
  102. SMALL_PNG,
  103. tok=self.admin_user_tok,
  104. expect_code=200,
  105. )
  106. # Extract media ID from the response
  107. server_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  108. server_name, media_id = server_and_media_id.split("/")
  109. self.assertEqual(server_name, self.server_name)
  110. # Attempt to access media
  111. channel = self.make_request(
  112. "GET",
  113. f"/_matrix/media/v3/download/{server_and_media_id}",
  114. shorthand=False,
  115. access_token=self.admin_user_tok,
  116. )
  117. # Should be successful
  118. self.assertEqual(
  119. 200,
  120. channel.code,
  121. msg=(
  122. "Expected to receive a 200 on accessing media: %s" % server_and_media_id
  123. ),
  124. )
  125. # Test if the file exists
  126. local_path = self.filepaths.local_media_filepath(media_id)
  127. self.assertTrue(os.path.exists(local_path))
  128. url = "/_synapse/admin/v1/media/%s/%s" % (self.server_name, media_id)
  129. # Delete media
  130. channel = self.make_request(
  131. "DELETE",
  132. url,
  133. access_token=self.admin_user_tok,
  134. )
  135. self.assertEqual(200, channel.code, msg=channel.json_body)
  136. self.assertEqual(1, channel.json_body["total"])
  137. self.assertEqual(
  138. media_id,
  139. channel.json_body["deleted_media"][0],
  140. )
  141. # Attempt to access media
  142. channel = self.make_request(
  143. "GET",
  144. f"/_matrix/media/v3/download/{server_and_media_id}",
  145. shorthand=False,
  146. access_token=self.admin_user_tok,
  147. )
  148. self.assertEqual(
  149. 404,
  150. channel.code,
  151. msg=(
  152. "Expected to receive a 404 on accessing deleted media: %s"
  153. % server_and_media_id
  154. ),
  155. )
  156. # Test if the file is deleted
  157. self.assertFalse(os.path.exists(local_path))
  158. class DeleteMediaByDateSizeTestCase(_AdminMediaTests):
  159. servlets = [
  160. synapse.rest.admin.register_servlets,
  161. synapse.rest.admin.register_servlets_for_media_repo,
  162. login.register_servlets,
  163. profile.register_servlets,
  164. room.register_servlets,
  165. ]
  166. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  167. self.media_repo = hs.get_media_repository_resource()
  168. self.server_name = hs.hostname
  169. self.admin_user = self.register_user("admin", "pass", admin=True)
  170. self.admin_user_tok = self.login("admin", "pass")
  171. self.filepaths = MediaFilePaths(hs.config.media.media_store_path)
  172. self.url = "/_synapse/admin/v1/media/delete"
  173. self.legacy_url = "/_synapse/admin/v1/media/%s/delete" % self.server_name
  174. # Move clock up to somewhat realistic time
  175. self.reactor.advance(1000000000)
  176. def test_no_auth(self) -> None:
  177. """
  178. Try to delete media without authentication.
  179. """
  180. channel = self.make_request("POST", self.url, b"{}")
  181. self.assertEqual(401, channel.code, msg=channel.json_body)
  182. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  183. def test_requester_is_no_admin(self) -> None:
  184. """
  185. If the user is not a server admin, an error is returned.
  186. """
  187. self.other_user = self.register_user("user", "pass")
  188. self.other_user_token = self.login("user", "pass")
  189. channel = self.make_request(
  190. "POST",
  191. self.url,
  192. access_token=self.other_user_token,
  193. )
  194. self.assertEqual(403, channel.code, msg=channel.json_body)
  195. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  196. def test_media_is_not_local(self) -> None:
  197. """
  198. Tests that a lookup for media that is not local returns a 400
  199. """
  200. url = "/_synapse/admin/v1/media/%s/delete" % "unknown_domain"
  201. channel = self.make_request(
  202. "POST",
  203. url + f"?before_ts={VALID_TIMESTAMP}",
  204. access_token=self.admin_user_tok,
  205. )
  206. self.assertEqual(400, channel.code, msg=channel.json_body)
  207. self.assertEqual("Can only delete local media", channel.json_body["error"])
  208. def test_missing_parameter(self) -> None:
  209. """
  210. If the parameter `before_ts` is missing, an error is returned.
  211. """
  212. channel = self.make_request(
  213. "POST",
  214. self.url,
  215. access_token=self.admin_user_tok,
  216. )
  217. self.assertEqual(400, channel.code, msg=channel.json_body)
  218. self.assertEqual(Codes.MISSING_PARAM, channel.json_body["errcode"])
  219. self.assertEqual(
  220. "Missing integer query parameter 'before_ts'", channel.json_body["error"]
  221. )
  222. def test_invalid_parameter(self) -> None:
  223. """
  224. If parameters are invalid, an error is returned.
  225. """
  226. channel = self.make_request(
  227. "POST",
  228. self.url + "?before_ts=-1234",
  229. access_token=self.admin_user_tok,
  230. )
  231. self.assertEqual(400, channel.code, msg=channel.json_body)
  232. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  233. self.assertEqual(
  234. "Query parameter before_ts must be a positive integer.",
  235. channel.json_body["error"],
  236. )
  237. channel = self.make_request(
  238. "POST",
  239. self.url + f"?before_ts={INVALID_TIMESTAMP_IN_S}",
  240. access_token=self.admin_user_tok,
  241. )
  242. self.assertEqual(400, channel.code, msg=channel.json_body)
  243. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  244. self.assertEqual(
  245. "Query parameter before_ts you provided is from the year 1970. "
  246. + "Double check that you are providing a timestamp in milliseconds.",
  247. channel.json_body["error"],
  248. )
  249. channel = self.make_request(
  250. "POST",
  251. self.url + f"?before_ts={VALID_TIMESTAMP}&size_gt=-1234",
  252. access_token=self.admin_user_tok,
  253. )
  254. self.assertEqual(400, channel.code, msg=channel.json_body)
  255. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  256. self.assertEqual(
  257. "Query parameter size_gt must be a string representing a positive integer.",
  258. channel.json_body["error"],
  259. )
  260. channel = self.make_request(
  261. "POST",
  262. self.url + f"?before_ts={VALID_TIMESTAMP}&keep_profiles=not_bool",
  263. access_token=self.admin_user_tok,
  264. )
  265. self.assertEqual(400, channel.code, msg=channel.json_body)
  266. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  267. self.assertEqual(
  268. "Boolean query parameter 'keep_profiles' must be one of ['true', 'false']",
  269. channel.json_body["error"],
  270. )
  271. @parameterized.expand([(True,), (False,)])
  272. def test_delete_media_never_accessed(self, use_legacy_url: bool) -> None:
  273. """
  274. Tests that media deleted if it is older than `before_ts` and never accessed
  275. `last_access_ts` is `NULL` and `created_ts` < `before_ts`
  276. """
  277. url = self.legacy_url if use_legacy_url else self.url
  278. # upload and do not access
  279. server_and_media_id = self._create_media()
  280. self.pump(1.0)
  281. # test that the file exists
  282. media_id = server_and_media_id.split("/")[1]
  283. local_path = self.filepaths.local_media_filepath(media_id)
  284. self.assertTrue(os.path.exists(local_path))
  285. # timestamp after upload/create
  286. now_ms = self.clock.time_msec()
  287. channel = self.make_request(
  288. "POST",
  289. url + "?before_ts=" + str(now_ms),
  290. access_token=self.admin_user_tok,
  291. )
  292. self.assertEqual(200, channel.code, msg=channel.json_body)
  293. self.assertEqual(1, channel.json_body["total"])
  294. self.assertEqual(
  295. media_id,
  296. channel.json_body["deleted_media"][0],
  297. )
  298. self._access_media(server_and_media_id, False)
  299. def test_keep_media_by_date(self) -> None:
  300. """
  301. Tests that media is not deleted if it is newer than `before_ts`
  302. """
  303. # timestamp before upload
  304. now_ms = self.clock.time_msec()
  305. server_and_media_id = self._create_media()
  306. self._access_media(server_and_media_id)
  307. channel = self.make_request(
  308. "POST",
  309. self.url + "?before_ts=" + str(now_ms),
  310. access_token=self.admin_user_tok,
  311. )
  312. self.assertEqual(200, channel.code, msg=channel.json_body)
  313. self.assertEqual(0, channel.json_body["total"])
  314. self._access_media(server_and_media_id)
  315. # timestamp after upload
  316. now_ms = self.clock.time_msec()
  317. channel = self.make_request(
  318. "POST",
  319. self.url + "?before_ts=" + str(now_ms),
  320. access_token=self.admin_user_tok,
  321. )
  322. self.assertEqual(200, channel.code, msg=channel.json_body)
  323. self.assertEqual(1, channel.json_body["total"])
  324. self.assertEqual(
  325. server_and_media_id.split("/")[1],
  326. channel.json_body["deleted_media"][0],
  327. )
  328. self._access_media(server_and_media_id, False)
  329. def test_keep_media_by_size(self) -> None:
  330. """
  331. Tests that media is not deleted if its size is smaller than or equal
  332. to `size_gt`
  333. """
  334. server_and_media_id = self._create_media()
  335. self._access_media(server_and_media_id)
  336. now_ms = self.clock.time_msec()
  337. channel = self.make_request(
  338. "POST",
  339. self.url + "?before_ts=" + str(now_ms) + "&size_gt=67",
  340. access_token=self.admin_user_tok,
  341. )
  342. self.assertEqual(200, channel.code, msg=channel.json_body)
  343. self.assertEqual(0, channel.json_body["total"])
  344. self._access_media(server_and_media_id)
  345. now_ms = self.clock.time_msec()
  346. channel = self.make_request(
  347. "POST",
  348. self.url + "?before_ts=" + str(now_ms) + "&size_gt=66",
  349. access_token=self.admin_user_tok,
  350. )
  351. self.assertEqual(200, channel.code, msg=channel.json_body)
  352. self.assertEqual(1, channel.json_body["total"])
  353. self.assertEqual(
  354. server_and_media_id.split("/")[1],
  355. channel.json_body["deleted_media"][0],
  356. )
  357. self._access_media(server_and_media_id, False)
  358. def test_keep_media_by_user_avatar(self) -> None:
  359. """
  360. Tests that we do not delete media if is used as a user avatar
  361. Tests parameter `keep_profiles`
  362. """
  363. server_and_media_id = self._create_media()
  364. self._access_media(server_and_media_id)
  365. # set media as avatar
  366. channel = self.make_request(
  367. "PUT",
  368. "/profile/%s/avatar_url" % (self.admin_user,),
  369. content={"avatar_url": "mxc://%s" % (server_and_media_id,)},
  370. access_token=self.admin_user_tok,
  371. )
  372. self.assertEqual(200, channel.code, msg=channel.json_body)
  373. now_ms = self.clock.time_msec()
  374. channel = self.make_request(
  375. "POST",
  376. self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
  377. access_token=self.admin_user_tok,
  378. )
  379. self.assertEqual(200, channel.code, msg=channel.json_body)
  380. self.assertEqual(0, channel.json_body["total"])
  381. self._access_media(server_and_media_id)
  382. now_ms = self.clock.time_msec()
  383. channel = self.make_request(
  384. "POST",
  385. self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
  386. access_token=self.admin_user_tok,
  387. )
  388. self.assertEqual(200, channel.code, msg=channel.json_body)
  389. self.assertEqual(1, channel.json_body["total"])
  390. self.assertEqual(
  391. server_and_media_id.split("/")[1],
  392. channel.json_body["deleted_media"][0],
  393. )
  394. self._access_media(server_and_media_id, False)
  395. def test_keep_media_by_room_avatar(self) -> None:
  396. """
  397. Tests that we do not delete media if it is used as a room avatar
  398. Tests parameter `keep_profiles`
  399. """
  400. server_and_media_id = self._create_media()
  401. self._access_media(server_and_media_id)
  402. # set media as room avatar
  403. room_id = self.helper.create_room_as(self.admin_user, tok=self.admin_user_tok)
  404. channel = self.make_request(
  405. "PUT",
  406. "/rooms/%s/state/m.room.avatar" % (room_id,),
  407. content={"url": "mxc://%s" % (server_and_media_id,)},
  408. access_token=self.admin_user_tok,
  409. )
  410. self.assertEqual(200, channel.code, msg=channel.json_body)
  411. now_ms = self.clock.time_msec()
  412. channel = self.make_request(
  413. "POST",
  414. self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=true",
  415. access_token=self.admin_user_tok,
  416. )
  417. self.assertEqual(200, channel.code, msg=channel.json_body)
  418. self.assertEqual(0, channel.json_body["total"])
  419. self._access_media(server_and_media_id)
  420. now_ms = self.clock.time_msec()
  421. channel = self.make_request(
  422. "POST",
  423. self.url + "?before_ts=" + str(now_ms) + "&keep_profiles=false",
  424. access_token=self.admin_user_tok,
  425. )
  426. self.assertEqual(200, channel.code, msg=channel.json_body)
  427. self.assertEqual(1, channel.json_body["total"])
  428. self.assertEqual(
  429. server_and_media_id.split("/")[1],
  430. channel.json_body["deleted_media"][0],
  431. )
  432. self._access_media(server_and_media_id, False)
  433. def _create_media(self) -> str:
  434. """
  435. Create a media and return media_id and server_and_media_id
  436. """
  437. # Upload some media into the room
  438. response = self.helper.upload_media(
  439. SMALL_PNG,
  440. tok=self.admin_user_tok,
  441. expect_code=200,
  442. )
  443. # Extract media ID from the response
  444. server_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  445. server_name = server_and_media_id.split("/")[0]
  446. # Check that new media is a local and not remote
  447. self.assertEqual(server_name, self.server_name)
  448. return server_and_media_id
  449. def _access_media(
  450. self, server_and_media_id: str, expect_success: bool = True
  451. ) -> None:
  452. """
  453. Try to access a media and check the result
  454. """
  455. media_id = server_and_media_id.split("/")[1]
  456. local_path = self.filepaths.local_media_filepath(media_id)
  457. channel = self.make_request(
  458. "GET",
  459. f"/_matrix/media/v3/download/{server_and_media_id}",
  460. shorthand=False,
  461. access_token=self.admin_user_tok,
  462. )
  463. if expect_success:
  464. self.assertEqual(
  465. 200,
  466. channel.code,
  467. msg=(
  468. "Expected to receive a 200 on accessing media: %s"
  469. % server_and_media_id
  470. ),
  471. )
  472. # Test that the file exists
  473. self.assertTrue(os.path.exists(local_path))
  474. else:
  475. self.assertEqual(
  476. 404,
  477. channel.code,
  478. msg=(
  479. "Expected to receive a 404 on accessing deleted media: %s"
  480. % (server_and_media_id)
  481. ),
  482. )
  483. # Test that the file is deleted
  484. self.assertFalse(os.path.exists(local_path))
  485. class QuarantineMediaByIDTestCase(_AdminMediaTests):
  486. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  487. self.store = hs.get_datastores().main
  488. self.server_name = hs.hostname
  489. self.admin_user = self.register_user("admin", "pass", admin=True)
  490. self.admin_user_tok = self.login("admin", "pass")
  491. # Upload some media into the room
  492. response = self.helper.upload_media(
  493. SMALL_PNG,
  494. tok=self.admin_user_tok,
  495. expect_code=200,
  496. )
  497. # Extract media ID from the response
  498. server_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  499. self.media_id = server_and_media_id.split("/")[1]
  500. self.url = "/_synapse/admin/v1/media/%s/%s/%s"
  501. @parameterized.expand(["quarantine", "unquarantine"])
  502. def test_no_auth(self, action: str) -> None:
  503. """
  504. Try to protect media without authentication.
  505. """
  506. channel = self.make_request(
  507. "POST",
  508. self.url % (action, self.server_name, self.media_id),
  509. b"{}",
  510. )
  511. self.assertEqual(401, channel.code, msg=channel.json_body)
  512. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  513. @parameterized.expand(["quarantine", "unquarantine"])
  514. def test_requester_is_no_admin(self, action: str) -> None:
  515. """
  516. If the user is not a server admin, an error is returned.
  517. """
  518. self.other_user = self.register_user("user", "pass")
  519. self.other_user_token = self.login("user", "pass")
  520. channel = self.make_request(
  521. "POST",
  522. self.url % (action, self.server_name, self.media_id),
  523. access_token=self.other_user_token,
  524. )
  525. self.assertEqual(403, channel.code, msg=channel.json_body)
  526. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  527. def test_quarantine_media(self) -> None:
  528. """
  529. Tests that quarantining and remove from quarantine a media is successfully
  530. """
  531. media_info = self.get_success(self.store.get_local_media(self.media_id))
  532. assert media_info is not None
  533. self.assertFalse(media_info["quarantined_by"])
  534. # quarantining
  535. channel = self.make_request(
  536. "POST",
  537. self.url % ("quarantine", self.server_name, self.media_id),
  538. access_token=self.admin_user_tok,
  539. )
  540. self.assertEqual(200, channel.code, msg=channel.json_body)
  541. self.assertFalse(channel.json_body)
  542. media_info = self.get_success(self.store.get_local_media(self.media_id))
  543. assert media_info is not None
  544. self.assertTrue(media_info["quarantined_by"])
  545. # remove from quarantine
  546. channel = self.make_request(
  547. "POST",
  548. self.url % ("unquarantine", self.server_name, self.media_id),
  549. access_token=self.admin_user_tok,
  550. )
  551. self.assertEqual(200, channel.code, msg=channel.json_body)
  552. self.assertFalse(channel.json_body)
  553. media_info = self.get_success(self.store.get_local_media(self.media_id))
  554. assert media_info is not None
  555. self.assertFalse(media_info["quarantined_by"])
  556. def test_quarantine_protected_media(self) -> None:
  557. """
  558. Tests that quarantining from protected media fails
  559. """
  560. # protect
  561. self.get_success(self.store.mark_local_media_as_safe(self.media_id, safe=True))
  562. # verify protection
  563. media_info = self.get_success(self.store.get_local_media(self.media_id))
  564. assert media_info is not None
  565. self.assertTrue(media_info["safe_from_quarantine"])
  566. # quarantining
  567. channel = self.make_request(
  568. "POST",
  569. self.url % ("quarantine", self.server_name, self.media_id),
  570. access_token=self.admin_user_tok,
  571. )
  572. self.assertEqual(200, channel.code, msg=channel.json_body)
  573. self.assertFalse(channel.json_body)
  574. # verify that is not in quarantine
  575. media_info = self.get_success(self.store.get_local_media(self.media_id))
  576. assert media_info is not None
  577. self.assertFalse(media_info["quarantined_by"])
  578. class ProtectMediaByIDTestCase(_AdminMediaTests):
  579. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  580. hs.get_media_repository_resource()
  581. self.store = hs.get_datastores().main
  582. self.admin_user = self.register_user("admin", "pass", admin=True)
  583. self.admin_user_tok = self.login("admin", "pass")
  584. # Upload some media into the room
  585. response = self.helper.upload_media(
  586. SMALL_PNG,
  587. tok=self.admin_user_tok,
  588. expect_code=200,
  589. )
  590. # Extract media ID from the response
  591. server_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  592. self.media_id = server_and_media_id.split("/")[1]
  593. self.url = "/_synapse/admin/v1/media/%s/%s"
  594. @parameterized.expand(["protect", "unprotect"])
  595. def test_no_auth(self, action: str) -> None:
  596. """
  597. Try to protect media without authentication.
  598. """
  599. channel = self.make_request("POST", self.url % (action, self.media_id), b"{}")
  600. self.assertEqual(401, channel.code, msg=channel.json_body)
  601. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  602. @parameterized.expand(["protect", "unprotect"])
  603. def test_requester_is_no_admin(self, action: str) -> None:
  604. """
  605. If the user is not a server admin, an error is returned.
  606. """
  607. self.other_user = self.register_user("user", "pass")
  608. self.other_user_token = self.login("user", "pass")
  609. channel = self.make_request(
  610. "POST",
  611. self.url % (action, self.media_id),
  612. access_token=self.other_user_token,
  613. )
  614. self.assertEqual(403, channel.code, msg=channel.json_body)
  615. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  616. def test_protect_media(self) -> None:
  617. """
  618. Tests that protect and unprotect a media is successfully
  619. """
  620. media_info = self.get_success(self.store.get_local_media(self.media_id))
  621. assert media_info is not None
  622. self.assertFalse(media_info["safe_from_quarantine"])
  623. # protect
  624. channel = self.make_request(
  625. "POST",
  626. self.url % ("protect", self.media_id),
  627. access_token=self.admin_user_tok,
  628. )
  629. self.assertEqual(200, channel.code, msg=channel.json_body)
  630. self.assertFalse(channel.json_body)
  631. media_info = self.get_success(self.store.get_local_media(self.media_id))
  632. assert media_info is not None
  633. self.assertTrue(media_info["safe_from_quarantine"])
  634. # unprotect
  635. channel = self.make_request(
  636. "POST",
  637. self.url % ("unprotect", self.media_id),
  638. access_token=self.admin_user_tok,
  639. )
  640. self.assertEqual(200, channel.code, msg=channel.json_body)
  641. self.assertFalse(channel.json_body)
  642. media_info = self.get_success(self.store.get_local_media(self.media_id))
  643. assert media_info is not None
  644. self.assertFalse(media_info["safe_from_quarantine"])
  645. class PurgeMediaCacheTestCase(_AdminMediaTests):
  646. servlets = [
  647. synapse.rest.admin.register_servlets,
  648. synapse.rest.admin.register_servlets_for_media_repo,
  649. login.register_servlets,
  650. profile.register_servlets,
  651. room.register_servlets,
  652. ]
  653. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  654. self.media_repo = hs.get_media_repository_resource()
  655. self.server_name = hs.hostname
  656. self.admin_user = self.register_user("admin", "pass", admin=True)
  657. self.admin_user_tok = self.login("admin", "pass")
  658. self.filepaths = MediaFilePaths(hs.config.media.media_store_path)
  659. self.url = "/_synapse/admin/v1/purge_media_cache"
  660. def test_no_auth(self) -> None:
  661. """
  662. Try to delete media without authentication.
  663. """
  664. channel = self.make_request("POST", self.url, b"{}")
  665. self.assertEqual(
  666. 401,
  667. channel.code,
  668. msg=channel.json_body,
  669. )
  670. self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"])
  671. def test_requester_is_not_admin(self) -> None:
  672. """
  673. If the user is not a server admin, an error is returned.
  674. """
  675. self.other_user = self.register_user("user", "pass")
  676. self.other_user_token = self.login("user", "pass")
  677. channel = self.make_request(
  678. "POST",
  679. self.url,
  680. access_token=self.other_user_token,
  681. )
  682. self.assertEqual(403, channel.code, msg=channel.json_body)
  683. self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"])
  684. def test_invalid_parameter(self) -> None:
  685. """
  686. If parameters are invalid, an error is returned.
  687. """
  688. channel = self.make_request(
  689. "POST",
  690. self.url + "?before_ts=-1234",
  691. access_token=self.admin_user_tok,
  692. )
  693. self.assertEqual(400, channel.code, msg=channel.json_body)
  694. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  695. self.assertEqual(
  696. "Query parameter before_ts must be a positive integer.",
  697. channel.json_body["error"],
  698. )
  699. channel = self.make_request(
  700. "POST",
  701. self.url + f"?before_ts={INVALID_TIMESTAMP_IN_S}",
  702. access_token=self.admin_user_tok,
  703. )
  704. self.assertEqual(400, channel.code, msg=channel.json_body)
  705. self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"])
  706. self.assertEqual(
  707. "Query parameter before_ts you provided is from the year 1970. "
  708. + "Double check that you are providing a timestamp in milliseconds.",
  709. channel.json_body["error"],
  710. )