test_admin.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. # Copyright 2018-2021 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import urllib.parse
  15. from parameterized import parameterized
  16. from twisted.test.proto_helpers import MemoryReactor
  17. import synapse.rest.admin
  18. from synapse.http.server import JsonResource
  19. from synapse.rest.admin import VersionServlet
  20. from synapse.rest.client import login, room
  21. from synapse.server import HomeServer
  22. from synapse.util import Clock
  23. from tests import unittest
  24. from tests.server import FakeSite, make_request
  25. from tests.test_utils import SMALL_PNG
  26. class VersionTestCase(unittest.HomeserverTestCase):
  27. url = "/_synapse/admin/v1/server_version"
  28. def create_test_resource(self) -> JsonResource:
  29. resource = JsonResource(self.hs)
  30. VersionServlet(self.hs).register(resource)
  31. return resource
  32. def test_version_string(self) -> None:
  33. channel = self.make_request("GET", self.url, shorthand=False)
  34. self.assertEqual(200, channel.code, msg=channel.json_body)
  35. self.assertEqual(
  36. {"server_version", "python_version"}, set(channel.json_body.keys())
  37. )
  38. class QuarantineMediaTestCase(unittest.HomeserverTestCase):
  39. """Test /quarantine_media admin API."""
  40. servlets = [
  41. synapse.rest.admin.register_servlets,
  42. synapse.rest.admin.register_servlets_for_media_repo,
  43. login.register_servlets,
  44. room.register_servlets,
  45. ]
  46. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  47. # Allow for uploading and downloading to/from the media repo
  48. self.media_repo = hs.get_media_repository_resource()
  49. self.download_resource = self.media_repo.children[b"download"]
  50. self.upload_resource = self.media_repo.children[b"upload"]
  51. def _ensure_quarantined(
  52. self, admin_user_tok: str, server_and_media_id: str
  53. ) -> None:
  54. """Ensure a piece of media is quarantined when trying to access it."""
  55. channel = make_request(
  56. self.reactor,
  57. FakeSite(self.download_resource, self.reactor),
  58. "GET",
  59. server_and_media_id,
  60. shorthand=False,
  61. access_token=admin_user_tok,
  62. )
  63. # Should be quarantined
  64. self.assertEqual(
  65. 404,
  66. channel.code,
  67. msg=(
  68. "Expected to receive a 404 on accessing quarantined media: %s"
  69. % server_and_media_id
  70. ),
  71. )
  72. @parameterized.expand(
  73. [
  74. # Attempt quarantine media APIs as non-admin
  75. "/_synapse/admin/v1/media/quarantine/example.org/abcde12345",
  76. # And the roomID/userID endpoint
  77. "/_synapse/admin/v1/room/!room%3Aexample.com/media/quarantine",
  78. ]
  79. )
  80. def test_quarantine_media_requires_admin(self, url: str) -> None:
  81. self.register_user("nonadmin", "pass", admin=False)
  82. non_admin_user_tok = self.login("nonadmin", "pass")
  83. channel = self.make_request(
  84. "POST",
  85. url.encode("ascii"),
  86. access_token=non_admin_user_tok,
  87. )
  88. # Expect a forbidden error
  89. self.assertEqual(
  90. 403,
  91. channel.code,
  92. msg="Expected forbidden on quarantining media as a non-admin",
  93. )
  94. def test_quarantine_media_by_id(self) -> None:
  95. self.register_user("id_admin", "pass", admin=True)
  96. admin_user_tok = self.login("id_admin", "pass")
  97. self.register_user("id_nonadmin", "pass", admin=False)
  98. non_admin_user_tok = self.login("id_nonadmin", "pass")
  99. # Upload some media into the room
  100. response = self.helper.upload_media(
  101. self.upload_resource, SMALL_PNG, tok=admin_user_tok
  102. )
  103. # Extract media ID from the response
  104. server_name_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  105. server_name, media_id = server_name_and_media_id.split("/")
  106. # Attempt to access the media
  107. channel = make_request(
  108. self.reactor,
  109. FakeSite(self.download_resource, self.reactor),
  110. "GET",
  111. server_name_and_media_id,
  112. shorthand=False,
  113. access_token=non_admin_user_tok,
  114. )
  115. # Should be successful
  116. self.assertEqual(200, channel.code)
  117. # Quarantine the media
  118. url = "/_synapse/admin/v1/media/quarantine/%s/%s" % (
  119. urllib.parse.quote(server_name),
  120. urllib.parse.quote(media_id),
  121. )
  122. channel = self.make_request(
  123. "POST",
  124. url,
  125. access_token=admin_user_tok,
  126. )
  127. self.pump(1.0)
  128. self.assertEqual(200, channel.code, msg=channel.json_body)
  129. # Attempt to access the media
  130. self._ensure_quarantined(admin_user_tok, server_name_and_media_id)
  131. @parameterized.expand(
  132. [
  133. # regular API path
  134. "/_synapse/admin/v1/room/%s/media/quarantine",
  135. # deprecated API path
  136. "/_synapse/admin/v1/quarantine_media/%s",
  137. ]
  138. )
  139. def test_quarantine_all_media_in_room(self, url: str) -> None:
  140. self.register_user("room_admin", "pass", admin=True)
  141. admin_user_tok = self.login("room_admin", "pass")
  142. non_admin_user = self.register_user("room_nonadmin", "pass", admin=False)
  143. non_admin_user_tok = self.login("room_nonadmin", "pass")
  144. room_id = self.helper.create_room_as(non_admin_user, tok=admin_user_tok)
  145. self.helper.join(room_id, non_admin_user, tok=non_admin_user_tok)
  146. # Upload some media
  147. response_1 = self.helper.upload_media(
  148. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  149. )
  150. response_2 = self.helper.upload_media(
  151. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  152. )
  153. # Extract mxcs
  154. mxc_1 = response_1["content_uri"]
  155. mxc_2 = response_2["content_uri"]
  156. # Send it into the room
  157. self.helper.send_event(
  158. room_id,
  159. "m.room.message",
  160. content={"body": "image-1", "msgtype": "m.image", "url": mxc_1},
  161. txn_id="111",
  162. tok=non_admin_user_tok,
  163. )
  164. self.helper.send_event(
  165. room_id,
  166. "m.room.message",
  167. content={"body": "image-2", "msgtype": "m.image", "url": mxc_2},
  168. txn_id="222",
  169. tok=non_admin_user_tok,
  170. )
  171. channel = self.make_request(
  172. "POST",
  173. url % urllib.parse.quote(room_id),
  174. access_token=admin_user_tok,
  175. )
  176. self.pump(1.0)
  177. self.assertEqual(200, channel.code, msg=channel.json_body)
  178. self.assertEqual(
  179. channel.json_body, {"num_quarantined": 2}, "Expected 2 quarantined items"
  180. )
  181. # Convert mxc URLs to server/media_id strings
  182. server_and_media_id_1 = mxc_1[6:]
  183. server_and_media_id_2 = mxc_2[6:]
  184. # Test that we cannot download any of the media anymore
  185. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  186. self._ensure_quarantined(admin_user_tok, server_and_media_id_2)
  187. def test_quarantine_all_media_by_user(self) -> None:
  188. self.register_user("user_admin", "pass", admin=True)
  189. admin_user_tok = self.login("user_admin", "pass")
  190. non_admin_user = self.register_user("user_nonadmin", "pass", admin=False)
  191. non_admin_user_tok = self.login("user_nonadmin", "pass")
  192. # Upload some media
  193. response_1 = self.helper.upload_media(
  194. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  195. )
  196. response_2 = self.helper.upload_media(
  197. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  198. )
  199. # Extract media IDs
  200. server_and_media_id_1 = response_1["content_uri"][6:]
  201. server_and_media_id_2 = response_2["content_uri"][6:]
  202. # Quarantine all media by this user
  203. url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
  204. non_admin_user
  205. )
  206. channel = self.make_request(
  207. "POST",
  208. url.encode("ascii"),
  209. access_token=admin_user_tok,
  210. )
  211. self.pump(1.0)
  212. self.assertEqual(200, channel.code, msg=channel.json_body)
  213. self.assertEqual(
  214. channel.json_body, {"num_quarantined": 2}, "Expected 2 quarantined items"
  215. )
  216. # Attempt to access each piece of media
  217. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  218. self._ensure_quarantined(admin_user_tok, server_and_media_id_2)
  219. def test_cannot_quarantine_safe_media(self) -> None:
  220. self.register_user("user_admin", "pass", admin=True)
  221. admin_user_tok = self.login("user_admin", "pass")
  222. non_admin_user = self.register_user("user_nonadmin", "pass", admin=False)
  223. non_admin_user_tok = self.login("user_nonadmin", "pass")
  224. # Upload some media
  225. response_1 = self.helper.upload_media(
  226. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  227. )
  228. response_2 = self.helper.upload_media(
  229. self.upload_resource, SMALL_PNG, tok=non_admin_user_tok
  230. )
  231. # Extract media IDs
  232. server_and_media_id_1 = response_1["content_uri"][6:]
  233. server_and_media_id_2 = response_2["content_uri"][6:]
  234. # Mark the second item as safe from quarantine.
  235. _, media_id_2 = server_and_media_id_2.split("/")
  236. # Quarantine the media
  237. url = "/_synapse/admin/v1/media/protect/%s" % (urllib.parse.quote(media_id_2),)
  238. channel = self.make_request("POST", url, access_token=admin_user_tok)
  239. self.pump(1.0)
  240. self.assertEqual(200, channel.code, msg=channel.json_body)
  241. # Quarantine all media by this user
  242. url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
  243. non_admin_user
  244. )
  245. channel = self.make_request(
  246. "POST",
  247. url.encode("ascii"),
  248. access_token=admin_user_tok,
  249. )
  250. self.pump(1.0)
  251. self.assertEqual(200, channel.code, msg=channel.json_body)
  252. self.assertEqual(
  253. channel.json_body, {"num_quarantined": 1}, "Expected 1 quarantined item"
  254. )
  255. # Attempt to access each piece of media, the first should fail, the
  256. # second should succeed.
  257. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  258. # Attempt to access each piece of media
  259. channel = make_request(
  260. self.reactor,
  261. FakeSite(self.download_resource, self.reactor),
  262. "GET",
  263. server_and_media_id_2,
  264. shorthand=False,
  265. access_token=non_admin_user_tok,
  266. )
  267. # Shouldn't be quarantined
  268. self.assertEqual(
  269. 200,
  270. channel.code,
  271. msg=(
  272. "Expected to receive a 200 on accessing not-quarantined media: %s"
  273. % server_and_media_id_2
  274. ),
  275. )
  276. class PurgeHistoryTestCase(unittest.HomeserverTestCase):
  277. servlets = [
  278. synapse.rest.admin.register_servlets,
  279. login.register_servlets,
  280. room.register_servlets,
  281. ]
  282. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  283. self.admin_user = self.register_user("admin", "pass", admin=True)
  284. self.admin_user_tok = self.login("admin", "pass")
  285. self.other_user = self.register_user("user", "pass")
  286. self.other_user_tok = self.login("user", "pass")
  287. self.room_id = self.helper.create_room_as(
  288. self.other_user, tok=self.other_user_tok
  289. )
  290. self.url = f"/_synapse/admin/v1/purge_history/{self.room_id}"
  291. self.url_status = "/_synapse/admin/v1/purge_history_status/"
  292. def test_purge_history(self) -> None:
  293. """
  294. Simple test of purge history API.
  295. Test only that is is possible to call, get status 200 and purge_id.
  296. """
  297. channel = self.make_request(
  298. "POST",
  299. self.url,
  300. content={"delete_local_events": True, "purge_up_to_ts": 0},
  301. access_token=self.admin_user_tok,
  302. )
  303. self.assertEqual(200, channel.code, msg=channel.json_body)
  304. self.assertIn("purge_id", channel.json_body)
  305. purge_id = channel.json_body["purge_id"]
  306. # get status
  307. channel = self.make_request(
  308. "GET",
  309. self.url_status + purge_id,
  310. access_token=self.admin_user_tok,
  311. )
  312. self.assertEqual(200, channel.code, msg=channel.json_body)
  313. self.assertEqual("complete", channel.json_body["status"])