You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

898 lines
30 KiB

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