test_e2e_room_keys.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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,
  48. handlers=None,
  49. replication_layer=mock.Mock(),
  50. )
  51. self.handler = synapse.handlers.e2e_room_keys.E2eRoomKeysHandler(self.hs)
  52. self.local_user = "@boris:" + self.hs.hostname
  53. @defer.inlineCallbacks
  54. def test_get_missing_current_version_info(self):
  55. """Check that we get a 404 if we ask for info about the current version
  56. if there is no version.
  57. """
  58. res = None
  59. try:
  60. yield self.handler.get_version_info(self.local_user)
  61. except errors.SynapseError as e:
  62. res = e.code
  63. self.assertEqual(res, 404)
  64. @defer.inlineCallbacks
  65. def test_get_missing_version_info(self):
  66. """Check that we get a 404 if we ask for info about a specific version
  67. if it doesn't exist.
  68. """
  69. res = None
  70. try:
  71. yield self.handler.get_version_info(self.local_user, "bogus_version")
  72. except errors.SynapseError as e:
  73. res = e.code
  74. self.assertEqual(res, 404)
  75. @defer.inlineCallbacks
  76. def test_create_version(self):
  77. """Check that we can create and then retrieve versions.
  78. """
  79. res = yield self.handler.create_version(self.local_user, {
  80. "algorithm": "m.megolm_backup.v1",
  81. "auth_data": "first_version_auth_data",
  82. })
  83. self.assertEqual(res, "1")
  84. # check we can retrieve it as the current version
  85. res = yield self.handler.get_version_info(self.local_user)
  86. self.assertDictEqual(res, {
  87. "version": "1",
  88. "algorithm": "m.megolm_backup.v1",
  89. "auth_data": "first_version_auth_data",
  90. })
  91. # check we can retrieve it as a specific version
  92. res = yield self.handler.get_version_info(self.local_user, "1")
  93. self.assertDictEqual(res, {
  94. "version": "1",
  95. "algorithm": "m.megolm_backup.v1",
  96. "auth_data": "first_version_auth_data",
  97. })
  98. # upload a new one...
  99. res = yield self.handler.create_version(self.local_user, {
  100. "algorithm": "m.megolm_backup.v1",
  101. "auth_data": "second_version_auth_data",
  102. })
  103. self.assertEqual(res, "2")
  104. # check we can retrieve it as the current version
  105. res = yield self.handler.get_version_info(self.local_user)
  106. self.assertDictEqual(res, {
  107. "version": "2",
  108. "algorithm": "m.megolm_backup.v1",
  109. "auth_data": "second_version_auth_data",
  110. })
  111. @defer.inlineCallbacks
  112. def test_delete_missing_version(self):
  113. """Check that we get a 404 on deleting nonexistent versions
  114. """
  115. res = None
  116. try:
  117. yield self.handler.delete_version(self.local_user, "1")
  118. except errors.SynapseError as e:
  119. res = e.code
  120. self.assertEqual(res, 404)
  121. @defer.inlineCallbacks
  122. def test_delete_missing_current_version(self):
  123. """Check that we get a 404 on deleting nonexistent current version
  124. """
  125. res = None
  126. try:
  127. yield self.handler.delete_version(self.local_user)
  128. except errors.SynapseError as e:
  129. res = e.code
  130. self.assertEqual(res, 404)
  131. @defer.inlineCallbacks
  132. def test_delete_version(self):
  133. """Check that we can create and then delete versions.
  134. """
  135. res = yield self.handler.create_version(self.local_user, {
  136. "algorithm": "m.megolm_backup.v1",
  137. "auth_data": "first_version_auth_data",
  138. })
  139. self.assertEqual(res, "1")
  140. # check we can delete it
  141. yield self.handler.delete_version(self.local_user, "1")
  142. # check that it's gone
  143. res = None
  144. try:
  145. yield self.handler.get_version_info(self.local_user, "1")
  146. except errors.SynapseError as e:
  147. res = e.code
  148. self.assertEqual(res, 404)
  149. @defer.inlineCallbacks
  150. def test_get_missing_backup(self):
  151. """Check that we get a 404 on querying missing backup
  152. """
  153. res = None
  154. try:
  155. yield self.handler.get_room_keys(self.local_user, "bogus_version")
  156. except errors.SynapseError as e:
  157. res = e.code
  158. self.assertEqual(res, 404)
  159. @defer.inlineCallbacks
  160. def test_get_missing_room_keys(self):
  161. """Check we get an empty response from an empty backup
  162. """
  163. version = yield self.handler.create_version(self.local_user, {
  164. "algorithm": "m.megolm_backup.v1",
  165. "auth_data": "first_version_auth_data",
  166. })
  167. self.assertEqual(version, "1")
  168. res = yield self.handler.get_room_keys(self.local_user, version)
  169. self.assertDictEqual(res, {
  170. "rooms": {}
  171. })
  172. # TODO: test the locking semantics when uploading room_keys,
  173. # although this is probably best done in sytest
  174. @defer.inlineCallbacks
  175. def test_upload_room_keys_no_versions(self):
  176. """Check that we get a 404 on uploading keys when no versions are defined
  177. """
  178. res = None
  179. try:
  180. yield self.handler.upload_room_keys(self.local_user, "no_version", room_keys)
  181. except errors.SynapseError as e:
  182. res = e.code
  183. self.assertEqual(res, 404)
  184. @defer.inlineCallbacks
  185. def test_upload_room_keys_bogus_version(self):
  186. """Check that we get a 404 on uploading keys when an nonexistent version
  187. is specified
  188. """
  189. version = yield self.handler.create_version(self.local_user, {
  190. "algorithm": "m.megolm_backup.v1",
  191. "auth_data": "first_version_auth_data",
  192. })
  193. self.assertEqual(version, "1")
  194. res = None
  195. try:
  196. yield self.handler.upload_room_keys(
  197. self.local_user, "bogus_version", room_keys
  198. )
  199. except errors.SynapseError as e:
  200. res = e.code
  201. self.assertEqual(res, 404)
  202. @defer.inlineCallbacks
  203. def test_upload_room_keys_wrong_version(self):
  204. """Check that we get a 403 on uploading keys for an old version
  205. """
  206. version = yield self.handler.create_version(self.local_user, {
  207. "algorithm": "m.megolm_backup.v1",
  208. "auth_data": "first_version_auth_data",
  209. })
  210. self.assertEqual(version, "1")
  211. version = yield self.handler.create_version(self.local_user, {
  212. "algorithm": "m.megolm_backup.v1",
  213. "auth_data": "second_version_auth_data",
  214. })
  215. self.assertEqual(version, "2")
  216. res = None
  217. try:
  218. yield self.handler.upload_room_keys(self.local_user, "1", room_keys)
  219. except errors.SynapseError as e:
  220. res = e.code
  221. self.assertEqual(res, 403)
  222. @defer.inlineCallbacks
  223. def test_upload_room_keys_insert(self):
  224. """Check that we can insert and retrieve keys for a session
  225. """
  226. version = yield self.handler.create_version(self.local_user, {
  227. "algorithm": "m.megolm_backup.v1",
  228. "auth_data": "first_version_auth_data",
  229. })
  230. self.assertEqual(version, "1")
  231. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  232. res = yield self.handler.get_room_keys(self.local_user, version)
  233. self.assertDictEqual(res, room_keys)
  234. # check getting room_keys for a given room
  235. res = yield self.handler.get_room_keys(
  236. self.local_user,
  237. version,
  238. room_id="!abc:matrix.org"
  239. )
  240. self.assertDictEqual(res, room_keys)
  241. # check getting room_keys for a given session_id
  242. res = yield self.handler.get_room_keys(
  243. self.local_user,
  244. version,
  245. room_id="!abc:matrix.org",
  246. session_id="c0ff33",
  247. )
  248. self.assertDictEqual(res, room_keys)
  249. @defer.inlineCallbacks
  250. def test_upload_room_keys_merge(self):
  251. """Check that we can upload a new room_key for an existing session and
  252. have it correctly merged"""
  253. version = yield self.handler.create_version(self.local_user, {
  254. "algorithm": "m.megolm_backup.v1",
  255. "auth_data": "first_version_auth_data",
  256. })
  257. self.assertEqual(version, "1")
  258. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  259. new_room_keys = copy.deepcopy(room_keys)
  260. new_room_key = new_room_keys['rooms']['!abc:matrix.org']['sessions']['c0ff33']
  261. # test that increasing the message_index doesn't replace the existing session
  262. new_room_key['first_message_index'] = 2
  263. new_room_key['session_data'] = 'new'
  264. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  265. res = yield self.handler.get_room_keys(self.local_user, version)
  266. self.assertEqual(
  267. res['rooms']['!abc:matrix.org']['sessions']['c0ff33']['session_data'],
  268. "SSBBTSBBIEZJU0gK"
  269. )
  270. # test that marking the session as verified however /does/ replace it
  271. new_room_key['is_verified'] = True
  272. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  273. res = yield self.handler.get_room_keys(self.local_user, version)
  274. self.assertEqual(
  275. res['rooms']['!abc:matrix.org']['sessions']['c0ff33']['session_data'],
  276. "new"
  277. )
  278. # test that a session with a higher forwarded_count doesn't replace one
  279. # with a lower forwarding count
  280. new_room_key['forwarded_count'] = 2
  281. new_room_key['session_data'] = 'other'
  282. yield self.handler.upload_room_keys(self.local_user, version, new_room_keys)
  283. res = yield self.handler.get_room_keys(self.local_user, version)
  284. self.assertEqual(
  285. res['rooms']['!abc:matrix.org']['sessions']['c0ff33']['session_data'],
  286. "new"
  287. )
  288. # TODO: check edge cases as well as the common variations here
  289. @defer.inlineCallbacks
  290. def test_delete_room_keys(self):
  291. """Check that we can insert and delete keys for a session
  292. """
  293. version = yield self.handler.create_version(self.local_user, {
  294. "algorithm": "m.megolm_backup.v1",
  295. "auth_data": "first_version_auth_data",
  296. })
  297. self.assertEqual(version, "1")
  298. # check for bulk-delete
  299. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  300. yield self.handler.delete_room_keys(self.local_user, version)
  301. res = yield self.handler.get_room_keys(
  302. self.local_user,
  303. version,
  304. room_id="!abc:matrix.org",
  305. session_id="c0ff33",
  306. )
  307. self.assertDictEqual(res, {
  308. "rooms": {}
  309. })
  310. # check for bulk-delete per room
  311. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  312. yield self.handler.delete_room_keys(
  313. self.local_user,
  314. version,
  315. room_id="!abc:matrix.org",
  316. )
  317. res = yield self.handler.get_room_keys(
  318. self.local_user,
  319. version,
  320. room_id="!abc:matrix.org",
  321. session_id="c0ff33",
  322. )
  323. self.assertDictEqual(res, {
  324. "rooms": {}
  325. })
  326. # check for bulk-delete per session
  327. yield self.handler.upload_room_keys(self.local_user, version, room_keys)
  328. yield self.handler.delete_room_keys(
  329. self.local_user,
  330. version,
  331. room_id="!abc:matrix.org",
  332. session_id="c0ff33",
  333. )
  334. res = yield self.handler.get_room_keys(
  335. self.local_user,
  336. version,
  337. room_id="!abc:matrix.org",
  338. session_id="c0ff33",
  339. )
  340. self.assertDictEqual(res, {
  341. "rooms": {}
  342. })