test_e2e_room_keys.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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_bad_version(self):
  170. """Check that we get a 400 if the version in the body is missing or
  171. doesn't match
  172. """
  173. version = yield self.handler.create_version(
  174. self.local_user,
  175. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  176. )
  177. self.assertEqual(version, "1")
  178. res = None
  179. try:
  180. yield self.handler.update_version(
  181. self.local_user,
  182. version,
  183. {
  184. "algorithm": "m.megolm_backup.v1",
  185. "auth_data": "revised_first_version_auth_data",
  186. },
  187. )
  188. except errors.SynapseError as e:
  189. res = e.code
  190. self.assertEqual(res, 400)
  191. res = None
  192. try:
  193. yield self.handler.update_version(
  194. self.local_user,
  195. version,
  196. {
  197. "algorithm": "m.megolm_backup.v1",
  198. "auth_data": "revised_first_version_auth_data",
  199. "version": "incorrect",
  200. },
  201. )
  202. except errors.SynapseError as e:
  203. res = e.code
  204. self.assertEqual(res, 400)
  205. @defer.inlineCallbacks
  206. def test_delete_missing_version(self):
  207. """Check that we get a 404 on deleting nonexistent versions
  208. """
  209. res = None
  210. try:
  211. yield self.handler.delete_version(self.local_user, "1")
  212. except errors.SynapseError as e:
  213. res = e.code
  214. self.assertEqual(res, 404)
  215. @defer.inlineCallbacks
  216. def test_delete_missing_current_version(self):
  217. """Check that we get a 404 on deleting nonexistent current version
  218. """
  219. res = None
  220. try:
  221. yield self.handler.delete_version(self.local_user)
  222. except errors.SynapseError as e:
  223. res = e.code
  224. self.assertEqual(res, 404)
  225. @defer.inlineCallbacks
  226. def test_delete_version(self):
  227. """Check that we can create and then delete versions.
  228. """
  229. res = yield self.handler.create_version(
  230. self.local_user,
  231. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  232. )
  233. self.assertEqual(res, "1")
  234. # check we can delete it
  235. yield self.handler.delete_version(self.local_user, "1")
  236. # check that it's gone
  237. res = None
  238. try:
  239. yield self.handler.get_version_info(self.local_user, "1")
  240. except errors.SynapseError as e:
  241. res = e.code
  242. self.assertEqual(res, 404)
  243. @defer.inlineCallbacks
  244. def test_get_missing_backup(self):
  245. """Check that we get a 404 on querying missing backup
  246. """
  247. res = None
  248. try:
  249. yield self.handler.get_room_keys(self.local_user, "bogus_version")
  250. except errors.SynapseError as e:
  251. res = e.code
  252. self.assertEqual(res, 404)
  253. @defer.inlineCallbacks
  254. def test_get_missing_room_keys(self):
  255. """Check we get an empty response from an empty backup
  256. """
  257. version = yield self.handler.create_version(
  258. self.local_user,
  259. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  260. )
  261. self.assertEqual(version, "1")
  262. res = yield self.handler.get_room_keys(self.local_user, version)
  263. self.assertDictEqual(res, {"rooms": {}})
  264. # TODO: test the locking semantics when uploading room_keys,
  265. # although this is probably best done in sytest
  266. @defer.inlineCallbacks
  267. def test_upload_room_keys_no_versions(self):
  268. """Check that we get a 404 on uploading keys when no versions are defined
  269. """
  270. res = None
  271. try:
  272. yield self.handler.upload_room_keys(
  273. self.local_user, "no_version", room_keys
  274. )
  275. except errors.SynapseError as e:
  276. res = e.code
  277. self.assertEqual(res, 404)
  278. @defer.inlineCallbacks
  279. def test_upload_room_keys_bogus_version(self):
  280. """Check that we get a 404 on uploading keys when an nonexistent version
  281. is specified
  282. """
  283. version = yield self.handler.create_version(
  284. self.local_user,
  285. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  286. )
  287. self.assertEqual(version, "1")
  288. res = None
  289. try:
  290. yield self.handler.upload_room_keys(
  291. self.local_user, "bogus_version", room_keys
  292. )
  293. except errors.SynapseError as e:
  294. res = e.code
  295. self.assertEqual(res, 404)
  296. @defer.inlineCallbacks
  297. def test_upload_room_keys_wrong_version(self):
  298. """Check that we get a 403 on uploading keys for an old version
  299. """
  300. version = yield self.handler.create_version(
  301. self.local_user,
  302. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  303. )
  304. self.assertEqual(version, "1")
  305. version = yield self.handler.create_version(
  306. self.local_user,
  307. {
  308. "algorithm": "m.megolm_backup.v1",
  309. "auth_data": "second_version_auth_data",
  310. },
  311. )
  312. self.assertEqual(version, "2")
  313. res = None
  314. try:
  315. yield self.handler.upload_room_keys(self.local_user, "1", room_keys)
  316. except errors.SynapseError as e:
  317. res = e.code
  318. self.assertEqual(res, 403)
  319. @defer.inlineCallbacks
  320. def test_upload_room_keys_insert(self):
  321. """Check that we can insert and retrieve keys for a session
  322. """
  323. version = yield self.handler.create_version(
  324. self.local_user,
  325. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  326. )
  327. self.assertEqual(version, "1")
  328. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  329. res = yield self.handler.get_room_keys(self.local_user, version)
  330. self.assertDictEqual(res, room_keys)
  331. # check getting room_keys for a given room
  332. res = yield self.handler.get_room_keys(
  333. self.local_user, version, room_id="!abc:matrix.org"
  334. )
  335. self.assertDictEqual(res, room_keys)
  336. # check getting room_keys for a given session_id
  337. res = yield self.handler.get_room_keys(
  338. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  339. )
  340. self.assertDictEqual(res, room_keys)
  341. @defer.inlineCallbacks
  342. def test_upload_room_keys_merge(self):
  343. """Check that we can upload a new room_key for an existing session and
  344. have it correctly merged"""
  345. version = yield self.handler.create_version(
  346. self.local_user,
  347. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  348. )
  349. self.assertEqual(version, "1")
  350. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  351. new_room_keys = copy.deepcopy(room_keys)
  352. new_room_key = new_room_keys["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]
  353. # test that increasing the message_index doesn't replace the existing session
  354. new_room_key["first_message_index"] = 2
  355. new_room_key["session_data"] = "new"
  356. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  357. res = yield self.handler.get_room_keys(self.local_user, version)
  358. self.assertEqual(
  359. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"],
  360. "SSBBTSBBIEZJU0gK",
  361. )
  362. # test that marking the session as verified however /does/ replace it
  363. new_room_key["is_verified"] = True
  364. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  365. res = yield self.handler.get_room_keys(self.local_user, version)
  366. self.assertEqual(
  367. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  368. )
  369. # test that a session with a higher forwarded_count doesn't replace one
  370. # with a lower forwarding count
  371. new_room_key["forwarded_count"] = 2
  372. new_room_key["session_data"] = "other"
  373. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  374. res = yield self.handler.get_room_keys(self.local_user, version)
  375. self.assertEqual(
  376. res["rooms"]["!abc:matrix.org"]["sessions"]["c0ff33"]["session_data"], "new"
  377. )
  378. # TODO: check edge cases as well as the common variations here
  379. @defer.inlineCallbacks
  380. def test_delete_room_keys(self):
  381. """Check that we can insert and delete keys for a session
  382. """
  383. version = yield self.handler.create_version(
  384. self.local_user,
  385. {"algorithm": "m.megolm_backup.v1", "auth_data": "first_version_auth_data"},
  386. )
  387. self.assertEqual(version, "1")
  388. # check for bulk-delete
  389. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  390. yield self.handler.delete_room_keys(self.local_user, version)
  391. res = yield self.handler.get_room_keys(
  392. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  393. )
  394. self.assertDictEqual(res, {"rooms": {}})
  395. # check for bulk-delete per room
  396. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  397. yield self.handler.delete_room_keys(
  398. self.local_user, version, room_id="!abc:matrix.org"
  399. )
  400. res = yield self.handler.get_room_keys(
  401. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  402. )
  403. self.assertDictEqual(res, {"rooms": {}})
  404. # check for bulk-delete per session
  405. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  406. yield self.handler.delete_room_keys(
  407. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  408. )
  409. res = yield self.handler.get_room_keys(
  410. self.local_user, version, room_id="!abc:matrix.org", session_id="c0ff33"
  411. )
  412. self.assertDictEqual(res, {"rooms": {}})