test_admin.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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 typing import Dict
  16. from parameterized import parameterized
  17. from twisted.test.proto_helpers import MemoryReactor
  18. from twisted.web.resource import Resource
  19. import synapse.rest.admin
  20. from synapse.http.server import JsonResource
  21. from synapse.rest.admin import VersionServlet
  22. from synapse.rest.client import login, room
  23. from synapse.server import HomeServer
  24. from synapse.util import Clock
  25. from tests import unittest
  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(200, channel.code, msg=channel.json_body)
  36. self.assertEqual({"server_version"}, set(channel.json_body.keys()))
  37. class QuarantineMediaTestCase(unittest.HomeserverTestCase):
  38. """Test /quarantine_media admin API."""
  39. servlets = [
  40. synapse.rest.admin.register_servlets,
  41. synapse.rest.admin.register_servlets_for_media_repo,
  42. login.register_servlets,
  43. room.register_servlets,
  44. ]
  45. def create_resource_dict(self) -> Dict[str, Resource]:
  46. resources = super().create_resource_dict()
  47. resources["/_matrix/media"] = self.hs.get_media_repository_resource()
  48. return resources
  49. def _ensure_quarantined(
  50. self, admin_user_tok: str, server_and_media_id: str
  51. ) -> None:
  52. """Ensure a piece of media is quarantined when trying to access it."""
  53. channel = self.make_request(
  54. "GET",
  55. f"/_matrix/media/v3/download/{server_and_media_id}",
  56. shorthand=False,
  57. access_token=admin_user_tok,
  58. )
  59. # Should be quarantined
  60. self.assertEqual(
  61. 404,
  62. channel.code,
  63. msg=(
  64. "Expected to receive a 404 on accessing quarantined media: %s"
  65. % server_and_media_id
  66. ),
  67. )
  68. @parameterized.expand(
  69. [
  70. # Attempt quarantine media APIs as non-admin
  71. "/_synapse/admin/v1/media/quarantine/example.org/abcde12345",
  72. # And the roomID/userID endpoint
  73. "/_synapse/admin/v1/room/!room%3Aexample.com/media/quarantine",
  74. ]
  75. )
  76. def test_quarantine_media_requires_admin(self, url: str) -> None:
  77. self.register_user("nonadmin", "pass", admin=False)
  78. non_admin_user_tok = self.login("nonadmin", "pass")
  79. channel = self.make_request(
  80. "POST",
  81. url.encode("ascii"),
  82. access_token=non_admin_user_tok,
  83. )
  84. # Expect a forbidden error
  85. self.assertEqual(
  86. 403,
  87. channel.code,
  88. msg="Expected forbidden on quarantining media as a non-admin",
  89. )
  90. def test_quarantine_media_by_id(self) -> None:
  91. self.register_user("id_admin", "pass", admin=True)
  92. admin_user_tok = self.login("id_admin", "pass")
  93. self.register_user("id_nonadmin", "pass", admin=False)
  94. non_admin_user_tok = self.login("id_nonadmin", "pass")
  95. # Upload some media into the room
  96. response = self.helper.upload_media(SMALL_PNG, tok=admin_user_tok)
  97. # Extract media ID from the response
  98. server_name_and_media_id = response["content_uri"][6:] # Cut off 'mxc://'
  99. server_name, media_id = server_name_and_media_id.split("/")
  100. # Attempt to access the media
  101. channel = self.make_request(
  102. "GET",
  103. f"/_matrix/media/v3/download/{server_name_and_media_id}",
  104. shorthand=False,
  105. access_token=non_admin_user_tok,
  106. )
  107. # Should be successful
  108. self.assertEqual(200, channel.code)
  109. # Quarantine the media
  110. url = "/_synapse/admin/v1/media/quarantine/%s/%s" % (
  111. urllib.parse.quote(server_name),
  112. urllib.parse.quote(media_id),
  113. )
  114. channel = self.make_request(
  115. "POST",
  116. url,
  117. access_token=admin_user_tok,
  118. )
  119. self.pump(1.0)
  120. self.assertEqual(200, channel.code, msg=channel.json_body)
  121. # Attempt to access the media
  122. self._ensure_quarantined(admin_user_tok, server_name_and_media_id)
  123. @parameterized.expand(
  124. [
  125. # regular API path
  126. "/_synapse/admin/v1/room/%s/media/quarantine",
  127. # deprecated API path
  128. "/_synapse/admin/v1/quarantine_media/%s",
  129. ]
  130. )
  131. def test_quarantine_all_media_in_room(self, url: str) -> None:
  132. self.register_user("room_admin", "pass", admin=True)
  133. admin_user_tok = self.login("room_admin", "pass")
  134. non_admin_user = self.register_user("room_nonadmin", "pass", admin=False)
  135. non_admin_user_tok = self.login("room_nonadmin", "pass")
  136. room_id = self.helper.create_room_as(non_admin_user, tok=admin_user_tok)
  137. self.helper.join(room_id, non_admin_user, tok=non_admin_user_tok)
  138. # Upload some media
  139. response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  140. response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  141. # Extract mxcs
  142. mxc_1 = response_1["content_uri"]
  143. mxc_2 = response_2["content_uri"]
  144. # Send it into the room
  145. self.helper.send_event(
  146. room_id,
  147. "m.room.message",
  148. content={"body": "image-1", "msgtype": "m.image", "url": mxc_1},
  149. txn_id="111",
  150. tok=non_admin_user_tok,
  151. )
  152. self.helper.send_event(
  153. room_id,
  154. "m.room.message",
  155. content={"body": "image-2", "msgtype": "m.image", "url": mxc_2},
  156. txn_id="222",
  157. tok=non_admin_user_tok,
  158. )
  159. channel = self.make_request(
  160. "POST",
  161. url % urllib.parse.quote(room_id),
  162. access_token=admin_user_tok,
  163. )
  164. self.pump(1.0)
  165. self.assertEqual(200, channel.code, msg=channel.json_body)
  166. self.assertEqual(
  167. channel.json_body, {"num_quarantined": 2}, "Expected 2 quarantined items"
  168. )
  169. # Convert mxc URLs to server/media_id strings
  170. server_and_media_id_1 = mxc_1[6:]
  171. server_and_media_id_2 = mxc_2[6:]
  172. # Test that we cannot download any of the media anymore
  173. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  174. self._ensure_quarantined(admin_user_tok, server_and_media_id_2)
  175. def test_quarantine_all_media_by_user(self) -> None:
  176. self.register_user("user_admin", "pass", admin=True)
  177. admin_user_tok = self.login("user_admin", "pass")
  178. non_admin_user = self.register_user("user_nonadmin", "pass", admin=False)
  179. non_admin_user_tok = self.login("user_nonadmin", "pass")
  180. # Upload some media
  181. response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  182. response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  183. # Extract media IDs
  184. server_and_media_id_1 = response_1["content_uri"][6:]
  185. server_and_media_id_2 = response_2["content_uri"][6:]
  186. # Quarantine all media by this user
  187. url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
  188. non_admin_user
  189. )
  190. channel = self.make_request(
  191. "POST",
  192. url.encode("ascii"),
  193. access_token=admin_user_tok,
  194. )
  195. self.pump(1.0)
  196. self.assertEqual(200, channel.code, msg=channel.json_body)
  197. self.assertEqual(
  198. channel.json_body, {"num_quarantined": 2}, "Expected 2 quarantined items"
  199. )
  200. # Attempt to access each piece of media
  201. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  202. self._ensure_quarantined(admin_user_tok, server_and_media_id_2)
  203. def test_cannot_quarantine_safe_media(self) -> None:
  204. self.register_user("user_admin", "pass", admin=True)
  205. admin_user_tok = self.login("user_admin", "pass")
  206. non_admin_user = self.register_user("user_nonadmin", "pass", admin=False)
  207. non_admin_user_tok = self.login("user_nonadmin", "pass")
  208. # Upload some media
  209. response_1 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  210. response_2 = self.helper.upload_media(SMALL_PNG, tok=non_admin_user_tok)
  211. # Extract media IDs
  212. server_and_media_id_1 = response_1["content_uri"][6:]
  213. server_and_media_id_2 = response_2["content_uri"][6:]
  214. # Mark the second item as safe from quarantine.
  215. _, media_id_2 = server_and_media_id_2.split("/")
  216. # Quarantine the media
  217. url = "/_synapse/admin/v1/media/protect/%s" % (urllib.parse.quote(media_id_2),)
  218. channel = self.make_request("POST", url, access_token=admin_user_tok)
  219. self.pump(1.0)
  220. self.assertEqual(200, channel.code, msg=channel.json_body)
  221. # Quarantine all media by this user
  222. url = "/_synapse/admin/v1/user/%s/media/quarantine" % urllib.parse.quote(
  223. non_admin_user
  224. )
  225. channel = self.make_request(
  226. "POST",
  227. url.encode("ascii"),
  228. access_token=admin_user_tok,
  229. )
  230. self.pump(1.0)
  231. self.assertEqual(200, channel.code, msg=channel.json_body)
  232. self.assertEqual(
  233. channel.json_body, {"num_quarantined": 1}, "Expected 1 quarantined item"
  234. )
  235. # Attempt to access each piece of media, the first should fail, the
  236. # second should succeed.
  237. self._ensure_quarantined(admin_user_tok, server_and_media_id_1)
  238. # Attempt to access each piece of media
  239. channel = self.make_request(
  240. "GET",
  241. f"/_matrix/media/v3/download/{server_and_media_id_2}",
  242. shorthand=False,
  243. access_token=non_admin_user_tok,
  244. )
  245. # Shouldn't be quarantined
  246. self.assertEqual(
  247. 200,
  248. channel.code,
  249. msg=(
  250. "Expected to receive a 200 on accessing not-quarantined media: %s"
  251. % server_and_media_id_2
  252. ),
  253. )
  254. class PurgeHistoryTestCase(unittest.HomeserverTestCase):
  255. servlets = [
  256. synapse.rest.admin.register_servlets,
  257. login.register_servlets,
  258. room.register_servlets,
  259. ]
  260. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  261. self.admin_user = self.register_user("admin", "pass", admin=True)
  262. self.admin_user_tok = self.login("admin", "pass")
  263. self.other_user = self.register_user("user", "pass")
  264. self.other_user_tok = self.login("user", "pass")
  265. self.room_id = self.helper.create_room_as(
  266. self.other_user, tok=self.other_user_tok
  267. )
  268. self.url = f"/_synapse/admin/v1/purge_history/{self.room_id}"
  269. self.url_status = "/_synapse/admin/v1/purge_history_status/"
  270. def test_purge_history(self) -> None:
  271. """
  272. Simple test of purge history API.
  273. Test only that is is possible to call, get status 200 and purge_id.
  274. """
  275. channel = self.make_request(
  276. "POST",
  277. self.url,
  278. content={"delete_local_events": True, "purge_up_to_ts": 0},
  279. access_token=self.admin_user_tok,
  280. )
  281. self.assertEqual(200, channel.code, msg=channel.json_body)
  282. self.assertIn("purge_id", channel.json_body)
  283. purge_id = channel.json_body["purge_id"]
  284. # get status
  285. channel = self.make_request(
  286. "GET",
  287. self.url_status + purge_id,
  288. access_token=self.admin_user_tok,
  289. )
  290. self.assertEqual(200, channel.code, msg=channel.json_body)
  291. self.assertEqual("complete", channel.json_body["status"])
  292. class ExperimentalFeaturesTestCase(unittest.HomeserverTestCase):
  293. servlets = [
  294. synapse.rest.admin.register_servlets,
  295. login.register_servlets,
  296. ]
  297. def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
  298. self.admin_user = self.register_user("admin", "pass", admin=True)
  299. self.admin_user_tok = self.login("admin", "pass")
  300. self.other_user = self.register_user("user", "pass")
  301. self.other_user_tok = self.login("user", "pass")
  302. self.url = "/_synapse/admin/v1/experimental_features"
  303. def test_enable_and_disable(self) -> None:
  304. """
  305. Test basic functionality of ExperimentalFeatures endpoint
  306. """
  307. # test enabling features works
  308. url = f"{self.url}/{self.other_user}"
  309. channel = self.make_request(
  310. "PUT",
  311. url,
  312. content={
  313. "features": {"msc3026": True, "msc3881": True},
  314. },
  315. access_token=self.admin_user_tok,
  316. )
  317. self.assertEqual(channel.code, 200)
  318. # list which features are enabled and ensure the ones we enabled are listed
  319. self.assertEqual(channel.code, 200)
  320. url = f"{self.url}/{self.other_user}"
  321. channel = self.make_request(
  322. "GET",
  323. url,
  324. access_token=self.admin_user_tok,
  325. )
  326. self.assertEqual(channel.code, 200)
  327. self.assertEqual(
  328. True,
  329. channel.json_body["features"]["msc3026"],
  330. )
  331. self.assertEqual(
  332. True,
  333. channel.json_body["features"]["msc3881"],
  334. )
  335. # test disabling a feature works
  336. url = f"{self.url}/{self.other_user}"
  337. channel = self.make_request(
  338. "PUT",
  339. url,
  340. content={"features": {"msc3026": False}},
  341. access_token=self.admin_user_tok,
  342. )
  343. self.assertEqual(channel.code, 200)
  344. # list the features enabled/disabled and ensure they are still are correct
  345. self.assertEqual(channel.code, 200)
  346. url = f"{self.url}/{self.other_user}"
  347. channel = self.make_request(
  348. "GET",
  349. url,
  350. access_token=self.admin_user_tok,
  351. )
  352. self.assertEqual(channel.code, 200)
  353. self.assertEqual(
  354. False,
  355. channel.json_body["features"]["msc3026"],
  356. )
  357. self.assertEqual(
  358. True,
  359. channel.json_body["features"]["msc3881"],
  360. )
  361. self.assertEqual(
  362. False,
  363. channel.json_body["features"]["msc3967"],
  364. )
  365. # test nothing blows up if you try to disable a feature that isn't already enabled
  366. url = f"{self.url}/{self.other_user}"
  367. channel = self.make_request(
  368. "PUT",
  369. url,
  370. content={"features": {"msc3026": False}},
  371. access_token=self.admin_user_tok,
  372. )
  373. self.assertEqual(channel.code, 200)
  374. # test trying to enable a feature without an admin access token is denied
  375. url = f"{self.url}/f{self.other_user}"
  376. channel = self.make_request(
  377. "PUT",
  378. url,
  379. content={"features": {"msc3881": True}},
  380. access_token=self.other_user_tok,
  381. )
  382. self.assertEqual(channel.code, 403)
  383. self.assertEqual(
  384. channel.json_body,
  385. {"errcode": "M_FORBIDDEN", "error": "You are not a server admin"},
  386. )
  387. # test trying to enable a bogus msc is denied
  388. url = f"{self.url}/{self.other_user}"
  389. channel = self.make_request(
  390. "PUT",
  391. url,
  392. content={"features": {"msc6666": True}},
  393. access_token=self.admin_user_tok,
  394. )
  395. self.assertEqual(channel.code, 400)
  396. self.assertEqual(
  397. channel.json_body,
  398. {
  399. "errcode": "M_UNKNOWN",
  400. "error": "'msc6666' is not recognised as a valid experimental feature.",
  401. },
  402. )