test_e2e_room_keys.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2017 New Vector Ltd
  4. # Copyright 2019 Matrix.org Foundation C.I.C.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License");
  7. # you may not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS,
  14. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. # See the License for the specific language governing permissions and
  16. # limitations under the License.
  17. import copy
  18. import mock
  19. from synapse.api.errors import SynapseError
  20. from tests import unittest
  21. # sample room_key data for use in the tests
  22. room_keys = {
  23. "rooms": {
  24. "!abc:matrix.org": {
  25. "sessions": {
  26. "c0ff33": {
  27. "first_message_index": 1,
  28. "forwarded_count": 1,
  29. "is_verified": False,
  30. "session_data": "SSBBTSBBIEZJU0gK",
  31. }
  32. }
  33. }
  34. }
  35. }
  36. class E2eRoomKeysHandlerTestCase(unittest.HomeserverTestCase):
  37. def make_homeserver(self, reactor, clock):
  38. return self.setup_test_homeserver(replication_layer=mock.Mock())
  39. def prepare(self, reactor, clock, hs):
  40. self.handler = hs.get_e2e_room_keys_handler()
  41. self.local_user = "@boris:" + hs.hostname
  42. def test_get_missing_current_version_info(self):
  43. """Check that we get a 404 if we ask for info about the current version
  44. if there is no version.
  45. """
  46. e = self.get_failure(
  47. self.handler.get_version_info(self.local_user), SynapseError
  48. )
  49. res = e.value.code
  50. self.assertEqual(res, 404)
  51. def test_get_missing_version_info(self):
  52. """Check that we get a 404 if we ask for info about a specific version
  53. if it doesn't exist.
  54. """
  55. e = self.get_failure(
  56. self.handler.get_version_info(self.local_user, "bogus_version"),
  57. SynapseError,
  58. )
  59. res = e.value.code
  60. self.assertEqual(res, 404)
  61. def test_create_version(self):
  62. """Check that we can create and then retrieve versions."""
  63. res = self.get_success(
  64. self.handler.create_version(
  65. self.local_user,
  66. {
  67. "algorithm": "m.megolm_backup.v1",
  68. "auth_data": "first_version_auth_data",
  69. },
  70. )
  71. )
  72. self.assertEqual(res, "1")
  73. # check we can retrieve it as the current version
  74. res = self.get_success(self.handler.get_version_info(self.local_user))
  75. version_etag = res["etag"]
  76. self.assertIsInstance(version_etag, str)
  77. del res["etag"]
  78. self.assertDictEqual(
  79. res,
  80. {
  81. "version": "1",
  82. "algorithm": "m.megolm_backup.v1",
  83. "auth_data": "first_version_auth_data",
  84. "count": 0,
  85. },
  86. )
  87. # check we can retrieve it as a specific version
  88. res = self.get_success(self.handler.get_version_info(self.local_user, "1"))
  89. self.assertEqual(res["etag"], version_etag)
  90. del res["etag"]
  91. self.assertDictEqual(
  92. res,
  93. {
  94. "version": "1",
  95. "algorithm": "m.megolm_backup.v1",
  96. "auth_data": "first_version_auth_data",
  97. "count": 0,
  98. },
  99. )
  100. # upload a new one...
  101. res = self.get_success(
  102. self.handler.create_version(
  103. self.local_user,
  104. {
  105. "algorithm": "m.megolm_backup.v1",
  106. "auth_data": "second_version_auth_data",
  107. },
  108. )
  109. )
  110. self.assertEqual(res, "2")
  111. # check we can retrieve it as the current version
  112. res = self.get_success(self.handler.get_version_info(self.local_user))
  113. del res["etag"]
  114. self.assertDictEqual(
  115. res,
  116. {
  117. "version": "2",
  118. "algorithm": "m.megolm_backup.v1",
  119. "auth_data": "second_version_auth_data",
  120. "count": 0,
  121. },
  122. )
  123. def test_update_version(self):
  124. """Check that we can update versions."""
  125. version = self.get_success(
  126. self.handler.create_version(
  127. self.local_user,
  128. {
  129. "algorithm": "m.megolm_backup.v1",
  130. "auth_data": "first_version_auth_data",
  131. },
  132. )
  133. )
  134. self.assertEqual(version, "1")
  135. res = self.get_success(
  136. self.handler.update_version(
  137. self.local_user,
  138. version,
  139. {
  140. "algorithm": "m.megolm_backup.v1",
  141. "auth_data": "revised_first_version_auth_data",
  142. "version": version,
  143. },
  144. )
  145. )
  146. self.assertDictEqual(res, {})
  147. # check we can retrieve it as the current version
  148. res = self.get_success(self.handler.get_version_info(self.local_user))
  149. del res["etag"]
  150. self.assertDictEqual(
  151. res,
  152. {
  153. "algorithm": "m.megolm_backup.v1",
  154. "auth_data": "revised_first_version_auth_data",
  155. "version": version,
  156. "count": 0,
  157. },
  158. )
  159. def test_update_missing_version(self):
  160. """Check that we get a 404 on updating nonexistent versions"""
  161. e = self.get_failure(
  162. self.handler.update_version(
  163. self.local_user,
  164. "1",
  165. {
  166. "algorithm": "m.megolm_backup.v1",
  167. "auth_data": "revised_first_version_auth_data",
  168. "version": "1",
  169. },
  170. ),
  171. SynapseError,
  172. )
  173. res = e.value.code
  174. self.assertEqual(res, 404)
  175. def test_update_omitted_version(self):
  176. """Check that the update succeeds if the version is missing from the body"""
  177. version = self.get_success(
  178. self.handler.create_version(
  179. self.local_user,
  180. {
  181. "algorithm": "m.megolm_backup.v1",
  182. "auth_data": "first_version_auth_data",
  183. },
  184. )
  185. )
  186. self.assertEqual(version, "1")
  187. self.get_success(
  188. self.handler.update_version(
  189. self.local_user,
  190. version,
  191. {
  192. "algorithm": "m.megolm_backup.v1",
  193. "auth_data": "revised_first_version_auth_data",
  194. },
  195. )
  196. )
  197. # check we can retrieve it as the current version
  198. res = self.get_success(self.handler.get_version_info(self.local_user))
  199. del res["etag"] # etag is opaque, so don't test its contents
  200. self.assertDictEqual(
  201. res,
  202. {
  203. "algorithm": "m.megolm_backup.v1",
  204. "auth_data": "revised_first_version_auth_data",
  205. "version": version,
  206. "count": 0,
  207. },
  208. )
  209. def test_update_bad_version(self):
  210. """Check that we get a 400 if the version in the body doesn't match"""
  211. version = self.get_success(
  212. self.handler.create_version(
  213. self.local_user,
  214. {
  215. "algorithm": "m.megolm_backup.v1",
  216. "auth_data": "first_version_auth_data",
  217. },
  218. )
  219. )
  220. self.assertEqual(version, "1")
  221. e = self.get_failure(
  222. self.handler.update_version(
  223. self.local_user,
  224. version,
  225. {
  226. "algorithm": "m.megolm_backup.v1",
  227. "auth_data": "revised_first_version_auth_data",
  228. "version": "incorrect",
  229. },
  230. ),
  231. SynapseError,
  232. )
  233. res = e.value.code
  234. self.assertEqual(res, 400)
  235. def test_delete_missing_version(self):
  236. """Check that we get a 404 on deleting nonexistent versions"""
  237. e = self.get_failure(
  238. self.handler.delete_version(self.local_user, "1"), SynapseError
  239. )
  240. res = e.value.code
  241. self.assertEqual(res, 404)
  242. def test_delete_missing_current_version(self):
  243. """Check that we get a 404 on deleting nonexistent current version"""
  244. e = self.get_failure(self.handler.delete_version(self.local_user), SynapseError)
  245. res = e.value.code
  246. self.assertEqual(res, 404)
  247. def test_delete_version(self):
  248. """Check that we can create and then delete versions."""
  249. res = self.get_success(
  250. self.handler.create_version(
  251. self.local_user,
  252. {
  253. "algorithm": "m.megolm_backup.v1",
  254. "auth_data": "first_version_auth_data",
  255. },
  256. )
  257. )
  258. self.assertEqual(res, "1")
  259. # check we can delete it
  260. self.get_success(self.handler.delete_version(self.local_user, "1"))
  261. # check that it's gone
  262. e = self.get_failure(
  263. self.handler.get_version_info(self.local_user, "1"), SynapseError
  264. )
  265. res = e.value.code
  266. self.assertEqual(res, 404)
  267. def test_get_missing_backup(self):
  268. """Check that we get a 404 on querying missing backup"""
  269. e = self.get_failure(
  270. self.handler.get_room_keys(self.local_user, "bogus_version"), SynapseError
  271. )
  272. res = e.value.code
  273. self.assertEqual(res, 404)
  274. def test_get_missing_room_keys(self):
  275. """Check we get an empty response from an empty backup"""
  276. version = self.get_success(
  277. self.handler.create_version(
  278. self.local_user,
  279. {
  280. "algorithm": "m.megolm_backup.v1",
  281. "auth_data": "first_version_auth_data",
  282. },
  283. )
  284. )
  285. self.assertEqual(version, "1")
  286. res = self.get_success(self.handler.get_room_keys(self.local_user, version))
  287. self.assertDictEqual(res, {"rooms": {}})
  288. # TODO: test the locking semantics when uploading room_keys,
  289. # although this is probably best done in sytest
  290. def test_upload_room_keys_no_versions(self):
  291. """Check that we get a 404 on uploading keys when no versions are defined"""
  292. e = self.get_failure(
  293. self.handler.upload_room_keys(self.local_user, "no_version", room_keys),
  294. SynapseError,
  295. )
  296. res = e.value.code
  297. self.assertEqual(res, 404)
  298. def test_upload_room_keys_bogus_version(self):
  299. """Check that we get a 404 on uploading keys when an nonexistent version
  300. is specified
  301. """
  302. version = self.get_success(
  303. self.handler.create_version(
  304. self.local_user,
  305. {
  306. "algorithm": "m.megolm_backup.v1",
  307. "auth_data": "first_version_auth_data",
  308. },
  309. )
  310. )
  311. self.assertEqual(version, "1")
  312. e = self.get_failure(
  313. self.handler.upload_room_keys(self.local_user, "bogus_version", room_keys),
  314. SynapseError,
  315. )
  316. res = e.value.code
  317. self.assertEqual(res, 404)
  318. def test_upload_room_keys_wrong_version(self):
  319. """Check that we get a 403 on uploading keys for an old version"""
  320. version = self.get_success(
  321. self.handler.create_version(
  322. self.local_user,
  323. {
  324. "algorithm": "m.megolm_backup.v1",
  325. "auth_data": "first_version_auth_data",
  326. },
  327. )
  328. )
  329. self.assertEqual(version, "1")
  330. version = self.get_success(
  331. self.handler.create_version(
  332. self.local_user,
  333. {
  334. "algorithm": "m.megolm_backup.v1",
  335. "auth_data": "second_version_auth_data",
  336. },
  337. )
  338. )
  339. self.assertEqual(version, "2")
  340. e = self.get_failure(
  341. self.handler.upload_room_keys(self.local_user, "1", room_keys), SynapseError
  342. )
  343. res = e.value.code
  344. self.assertEqual(res, 403)
  345. def test_upload_room_keys_insert(self):
  346. """Check that we can insert and retrieve keys for a session"""
  347. version = self.get_success(
  348. self.handler.create_version(
  349. self.local_user,
  350. {
  351. "algorithm": "m.megolm_backup.v1",
  352. "auth_data": "first_version_auth_data",
  353. },
  354. )
  355. )
  356. self.assertEqual(version, "1")
  357. self.get_success(
  358. self.handler.upload_room_keys(self.local_user, version, room_keys)
  359. )
  360. res = self.get_success(self.handler.get_room_keys(self.local_user, version))
  361. self.assertDictEqual(res, room_keys)
  362. # check getting room_keys for a given room
  363. res = self.get_success(
  364. self.handler.get_room_keys(
  365. self.local_user, version, room_id="!abc:matrix.org"
  366. )
  367. )
  368. self.assertDictEqual(res, room_keys)
  369. # check getting room_keys for a given session_id
  370. res = self.get_success(
  371. self.handler.get_room_keys(
  372. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  373. )
  374. )
  375. self.assertDictEqual(res, room_keys)
  376. def test_upload_room_keys_merge(self):
  377. """Check that we can upload a new room_key for an existing session and
  378. have it correctly merged"""
  379. version = self.get_success(
  380. self.handler.create_version(
  381. self.local_user,
  382. {
  383. "algorithm": "m.megolm_backup.v1",
  384. "auth_data": "first_version_auth_data",
  385. },
  386. )
  387. )
  388. self.assertEqual(version, "1")
  389. self.get_success(
  390. self.handler.upload_room_keys(self.local_user, version, room_keys)
  391. )
  392. # get the etag to compare to future versions
  393. res = self.get_success(self.handler.get_version_info(self.local_user))
  394. backup_etag = res["etag"]
  395. self.assertEqual(res["count"], 1)
  396. new_room_keys = copy.deepcopy(room_keys)
  397. new_room_key = new_room_keys["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]
  398. # test that increasing the message_index doesn't replace the existing session
  399. new_room_key["first_message_index"] = 2
  400. new_room_key["session_data"] = "new"
  401. self.get_success(
  402. self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  403. )
  404. res = self.get_success(self.handler.get_room_keys(self.local_user, version))
  405. self.assertEqual(
  406. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"],
  407. "SSBBTSBBIEZJU0gK",
  408. )
  409. # the etag should be the same since the session did not change
  410. res = self.get_success(self.handler.get_version_info(self.local_user))
  411. self.assertEqual(res["etag"], backup_etag)
  412. # test that marking the session as verified however /does/ replace it
  413. new_room_key["is_verified"] = True
  414. self.get_success(
  415. self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  416. )
  417. res = self.get_success(self.handler.get_room_keys(self.local_user, version))
  418. self.assertEqual(
  419. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  420. )
  421. # the etag should NOT be equal now, since the key changed
  422. res = self.get_success(self.handler.get_version_info(self.local_user))
  423. self.assertNotEqual(res["etag"], backup_etag)
  424. backup_etag = res["etag"]
  425. # test that a session with a higher forwarded_count doesn't replace one
  426. # with a lower forwarding count
  427. new_room_key["forwarded_count"] = 2
  428. new_room_key["session_data"] = "other"
  429. self.get_success(
  430. self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  431. )
  432. res = self.get_success(self.handler.get_room_keys(self.local_user, version))
  433. self.assertEqual(
  434. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  435. )
  436. # the etag should be the same since the session did not change
  437. res = self.get_success(self.handler.get_version_info(self.local_user))
  438. self.assertEqual(res["etag"], backup_etag)
  439. # TODO: check edge cases as well as the common variations here
  440. def test_delete_room_keys(self):
  441. """Check that we can insert and delete keys for a session"""
  442. version = self.get_success(
  443. self.handler.create_version(
  444. self.local_user,
  445. {
  446. "algorithm": "m.megolm_backup.v1",
  447. "auth_data": "first_version_auth_data",
  448. },
  449. )
  450. )
  451. self.assertEqual(version, "1")
  452. # check for bulk-delete
  453. self.get_success(
  454. self.handler.upload_room_keys(self.local_user, version, room_keys)
  455. )
  456. self.get_success(self.handler.delete_room_keys(self.local_user, version))
  457. res = self.get_success(
  458. self.handler.get_room_keys(
  459. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  460. )
  461. )
  462. self.assertDictEqual(res, {"rooms": {}})
  463. # check for bulk-delete per room
  464. self.get_success(
  465. self.handler.upload_room_keys(self.local_user, version, room_keys)
  466. )
  467. self.get_success(
  468. self.handler.delete_room_keys(
  469. self.local_user, version, room_id="!abc:matrix.org"
  470. )
  471. )
  472. res = self.get_success(
  473. self.handler.get_room_keys(
  474. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  475. )
  476. )
  477. self.assertDictEqual(res, {"rooms": {}})
  478. # check for bulk-delete per session
  479. self.get_success(
  480. self.handler.upload_room_keys(self.local_user, version, room_keys)
  481. )
  482. self.get_success(
  483. self.handler.delete_room_keys(
  484. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  485. )
  486. )
  487. res = self.get_success(
  488. self.handler.get_room_keys(
  489. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  490. )
  491. )
  492. self.assertDictEqual(res, {"rooms": {}})