test_admin.py 13 KB

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