test_media.py 30 KB

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