test_e2e_room_keys.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 OpenMarket Ltd
  3. # Copyright 2017 New Vector Ltd
  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. import mock
  18. from twisted.internet import defer
  19. import synapse.api.errors
  20. import synapse.handlers.e2e_room_keys
  21. import synapse.storage
  22. from synapse.api import errors
  23. from tests import unittest, utils
  24. # sample room_key data for use in the tests
  25. room_keys = {
  26. "rooms": {
  27. "!abc:matrix.org": {
  28. "sessions": {
  29. "c0ff33": {
  30. "first_message_index": 1,
  31. "forwarded_count": 1,
  32. "is_verified": False,
  33. "session_data": "SSBBTSBBIEZJU0gK",
  34. }
  35. }
  36. }
  37. }
  38. }
  39. class E2eRoomKeysHandlerTestCase(unittest.TestCase):
  40. def __init__(self, *args, **kwargs):
  41. super(E2eRoomKeysHandlerTestCase, self).__init__(*args, **kwargs)
  42. self.hs = None # type: synapse.server.HomeServer
  43. self.handler = None # type: synapse.handlers.e2e_keys.E2eRoomKeysHandler
  44. @defer.inlineCallbacks
  45. def setUp(self):
  46. self.hs = yield utils.setup_test_homeserver(
  47. self.addCleanup, handlers=None, replication_layer=mock.Mock()
  48. )
  49. self.handler = synapse.handlers.e2e_room_keys.E2eRoomKeysHandler(self.hs)
  50. self.local_user = "@boris:" + self.hs.hostname
  51. @defer.inlineCallbacks
  52. def test_get_missing_current_version_info(self):
  53. """Check that we get a 404 if we ask for info about the current version
  54. if there is no version.
  55. """
  56. res = None
  57. try:
  58. yield self.handler.get_version_info(self.local_user)
  59. except errors.SynapseError as e:
  60. res = e.code
  61. self.assertEqual(res, 404)
  62. @defer.inlineCallbacks
  63. def test_get_missing_version_info(self):
  64. """Check that we get a 404 if we ask for info about a specific version
  65. if it doesn't exist.
  66. """
  67. res = None
  68. try:
  69. yield self.handler.get_version_info(self.local_user, "bogus_version")
  70. except errors.SynapseError as e:
  71. res = e.code
  72. self.assertEqual(res, 404)
  73. @defer.inlineCallbacks
  74. def test_create_version(self):
  75. """Check that we can create and then retrieve versions.
  76. """
  77. res = yield self.handler.create_version(
  78. self.local_user,
  79. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  80. )
  81. self.assertEqual(res, "1")
  82. # check we can retrieve it as the current version
  83. res = yield self.handler.get_version_info(self.local_user)
  84. self.assertDictEqual(
  85. res,
  86. {
  87. "version": "1",
  88. "algorithm": "m.megolm_backup.v1",
  89. "auth_data": "first_version_auth_data",
  90. },
  91. )
  92. # check we can retrieve it as a specific version
  93. res = yield self.handler.get_version_info(self.local_user, "1")
  94. self.assertDictEqual(
  95. res,
  96. {
  97. "version": "1",
  98. "algorithm": "m.megolm_backup.v1",
  99. "auth_data": "first_version_auth_data",
  100. },
  101. )
  102. # upload a new one...
  103. res = yield self.handler.create_version(
  104. self.local_user,
  105. {
  106. "algorithm": "m.megolm_backup.v1",
  107. "auth_data": "second_version_auth_data",
  108. },
  109. )
  110. self.assertEqual(res, "2")
  111. # check we can retrieve it as the current version
  112. res = yield self.handler.get_version_info(self.local_user)
  113. self.assertDictEqual(
  114. res,
  115. {
  116. "version": "2",
  117. "algorithm": "m.megolm_backup.v1",
  118. "auth_data": "second_version_auth_data",
  119. },
  120. )
  121. @defer.inlineCallbacks
  122. def test_update_version(self):
  123. """Check that we can update versions.
  124. """
  125. version = yield self.handler.create_version(
  126. self.local_user,
  127. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  128. )
  129. self.assertEqual(version, "1")
  130. res = yield self.handler.update_version(
  131. self.local_user,
  132. version,
  133. {
  134. "algorithm": "m.megolm_backup.v1",
  135. "auth_data": "revised_first_version_auth_data",
  136. "version": version,
  137. },
  138. )
  139. self.assertDictEqual(res, {})
  140. # check we can retrieve it as the current version
  141. res = yield self.handler.get_version_info(self.local_user)
  142. self.assertDictEqual(
  143. res,
  144. {
  145. "algorithm": "m.megolm_backup.v1",
  146. "auth_data": "revised_first_version_auth_data",
  147. "version": version,
  148. },
  149. )
  150. @defer.inlineCallbacks
  151. def test_update_missing_version(self):
  152. """Check that we get a 404 on updating nonexistent versions
  153. """
  154. res = None
  155. try:
  156. yield self.handler.update_version(
  157. self.local_user,
  158. "1",
  159. {
  160. "algorithm": "m.megolm_backup.v1",
  161. "auth_data": "revised_first_version_auth_data",
  162. "version": "1",
  163. },
  164. )
  165. except errors.SynapseError as e:
  166. res = e.code
  167. self.assertEqual(res, 404)
  168. @defer.inlineCallbacks
  169. def test_update_omitted_version(self):
  170. """Check that the update succeeds if the version is missing from the body
  171. """
  172. version = yield self.handler.create_version(
  173. self.local_user,
  174. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  175. )
  176. self.assertEqual(version, "1")
  177. yield self.handler.update_version(
  178. self.local_user,
  179. version,
  180. {
  181. "algorithm": "m.megolm_backup.v1",
  182. "auth_data": "revised_first_version_auth_data",
  183. },
  184. )
  185. # check we can retrieve it as the current version
  186. res = yield self.handler.get_version_info(self.local_user)
  187. self.assertDictEqual(
  188. res,
  189. {
  190. "algorithm": "m.megolm_backup.v1",
  191. "auth_data": "revised_first_version_auth_data",
  192. "version": version,
  193. },
  194. )
  195. @defer.inlineCallbacks
  196. def test_update_bad_version(self):
  197. """Check that we get a 400 if the version in the body doesn't match
  198. """
  199. version = yield self.handler.create_version(
  200. self.local_user,
  201. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  202. )
  203. self.assertEqual(version, "1")
  204. res = None
  205. try:
  206. yield self.handler.update_version(
  207. self.local_user,
  208. version,
  209. {
  210. "algorithm": "m.megolm_backup.v1",
  211. "auth_data": "revised_first_version_auth_data",
  212. "version": "incorrect",
  213. },
  214. )
  215. except errors.SynapseError as e:
  216. res = e.code
  217. self.assertEqual(res, 400)
  218. @defer.inlineCallbacks
  219. def test_delete_missing_version(self):
  220. """Check that we get a 404 on deleting nonexistent versions
  221. """
  222. res = None
  223. try:
  224. yield self.handler.delete_version(self.local_user, "1")
  225. except errors.SynapseError as e:
  226. res = e.code
  227. self.assertEqual(res, 404)
  228. @defer.inlineCallbacks
  229. def test_delete_missing_current_version(self):
  230. """Check that we get a 404 on deleting nonexistent current version
  231. """
  232. res = None
  233. try:
  234. yield self.handler.delete_version(self.local_user)
  235. except errors.SynapseError as e:
  236. res = e.code
  237. self.assertEqual(res, 404)
  238. @defer.inlineCallbacks
  239. def test_delete_version(self):
  240. """Check that we can create and then delete versions.
  241. """
  242. res = yield self.handler.create_version(
  243. self.local_user,
  244. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  245. )
  246. self.assertEqual(res, "1")
  247. # check we can delete it
  248. yield self.handler.delete_version(self.local_user, "1")
  249. # check that it's gone
  250. res = None
  251. try:
  252. yield self.handler.get_version_info(self.local_user, "1")
  253. except errors.SynapseError as e:
  254. res = e.code
  255. self.assertEqual(res, 404)
  256. @defer.inlineCallbacks
  257. def test_get_missing_backup(self):
  258. """Check that we get a 404 on querying missing backup
  259. """
  260. res = None
  261. try:
  262. yield self.handler.get_room_keys(self.local_user, "bogus_version")
  263. except errors.SynapseError as e:
  264. res = e.code
  265. self.assertEqual(res, 404)
  266. @defer.inlineCallbacks
  267. def test_get_missing_room_keys(self):
  268. """Check we get an empty response from an empty backup
  269. """
  270. version = yield self.handler.create_version(
  271. self.local_user,
  272. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  273. )
  274. self.assertEqual(version, "1")
  275. res = yield self.handler.get_room_keys(self.local_user, version)
  276. self.assertDictEqual(res, {"rooms": {}})
  277. # TODO: test the locking semantics when uploading room_keys,
  278. # although this is probably best done in sytest
  279. @defer.inlineCallbacks
  280. def test_upload_room_keys_no_versions(self):
  281. """Check that we get a 404 on uploading keys when no versions are defined
  282. """
  283. res = None
  284. try:
  285. yield self.handler.upload_room_keys(
  286. self.local_user, "no_version", room_keys
  287. )
  288. except errors.SynapseError as e:
  289. res = e.code
  290. self.assertEqual(res, 404)
  291. @defer.inlineCallbacks
  292. def test_upload_room_keys_bogus_version(self):
  293. """Check that we get a 404 on uploading keys when an nonexistent version
  294. is specified
  295. """
  296. version = yield self.handler.create_version(
  297. self.local_user,
  298. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  299. )
  300. self.assertEqual(version, "1")
  301. res = None
  302. try:
  303. yield self.handler.upload_room_keys(
  304. self.local_user, "bogus_version", room_keys
  305. )
  306. except errors.SynapseError as e:
  307. res = e.code
  308. self.assertEqual(res, 404)
  309. @defer.inlineCallbacks
  310. def test_upload_room_keys_wrong_version(self):
  311. """Check that we get a 403 on uploading keys for an old version
  312. """
  313. version = yield self.handler.create_version(
  314. self.local_user,
  315. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  316. )
  317. self.assertEqual(version, "1")
  318. version = yield self.handler.create_version(
  319. self.local_user,
  320. {
  321. "algorithm": "m.megolm_backup.v1",
  322. "auth_data": "second_version_auth_data",
  323. },
  324. )
  325. self.assertEqual(version, "2")
  326. res = None
  327. try:
  328. yield self.handler.upload_room_keys(self.local_user, "1", room_keys)
  329. except errors.SynapseError as e:
  330. res = e.code
  331. self.assertEqual(res, 403)
  332. @defer.inlineCallbacks
  333. def test_upload_room_keys_insert(self):
  334. """Check that we can insert and retrieve keys for a session
  335. """
  336. version = yield self.handler.create_version(
  337. self.local_user,
  338. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  339. )
  340. self.assertEqual(version, "1")
  341. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  342. res = yield self.handler.get_room_keys(self.local_user, version)
  343. self.assertDictEqual(res, room_keys)
  344. # check getting room_keys for a given room
  345. res = yield self.handler.get_room_keys(
  346. self.local_user, version, room_id="!abc:matrix.org"
  347. )
  348. self.assertDictEqual(res, room_keys)
  349. # check getting room_keys for a given session_id
  350. res = yield self.handler.get_room_keys(
  351. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  352. )
  353. self.assertDictEqual(res, room_keys)
  354. @defer.inlineCallbacks
  355. def test_upload_room_keys_merge(self):
  356. """Check that we can upload a new room_key for an existing session and
  357. have it correctly merged"""
  358. version = yield self.handler.create_version(
  359. self.local_user,
  360. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  361. )
  362. self.assertEqual(version, "1")
  363. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  364. new_room_keys = copy.deepcopy(room_keys)
  365. new_room_key = new_room_keys["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]
  366. # test that increasing the message_index doesn't replace the existing session
  367. new_room_key["first_message_index"] = 2
  368. new_room_key["session_data"] = "new"
  369. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  370. res = yield self.handler.get_room_keys(self.local_user, version)
  371. self.assertEqual(
  372. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"],
  373. "SSBBTSBBIEZJU0gK",
  374. )
  375. # test that marking the session as verified however /does/ replace it
  376. new_room_key["is_verified"] = True
  377. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  378. res = yield self.handler.get_room_keys(self.local_user, version)
  379. self.assertEqual(
  380. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  381. )
  382. # test that a session with a higher forwarded_count doesn't replace one
  383. # with a lower forwarding count
  384. new_room_key["forwarded_count"] = 2
  385. new_room_key["session_data"] = "other"
  386. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  387. res = yield self.handler.get_room_keys(self.local_user, version)
  388. self.assertEqual(
  389. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  390. )
  391. # TODO: check edge cases as well as the common variations here
  392. @defer.inlineCallbacks
  393. def test_delete_room_keys(self):
  394. """Check that we can insert and delete keys for a session
  395. """
  396. version = yield self.handler.create_version(
  397. self.local_user,
  398. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  399. )
  400. self.assertEqual(version, "1")
  401. # check for bulk-delete
  402. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  403. yield self.handler.delete_room_keys(self.local_user, version)
  404. res = yield self.handler.get_room_keys(
  405. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  406. )
  407. self.assertDictEqual(res, {"rooms": {}})
  408. # check for bulk-delete per room
  409. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  410. yield self.handler.delete_room_keys(
  411. self.local_user, version, room_id="!abc:matrix.org"
  412. )
  413. res = yield self.handler.get_room_keys(
  414. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  415. )
  416. self.assertDictEqual(res, {"rooms": {}})
  417. # check for bulk-delete per session
  418. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  419. yield self.handler.delete_room_keys(
  420. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  421. )
  422. res = yield self.handler.get_room_keys(
  423. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  424. )
  425. self.assertDictEqual(res, {"rooms": {}})