test_e2e_room_keys.py 19 KB

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