omemo.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. (function (root, factory) {
  2. define(["jasmine", "mock", "test-utils"], factory);
  3. } (this, function (jasmine, mock, test_utils) {
  4. var Strophe = converse.env.Strophe;
  5. var b64_sha1 = converse.env.b64_sha1;
  6. var $iq = converse.env.$iq;
  7. var $msg = converse.env.$msg;
  8. var _ = converse.env._;
  9. var u = converse.env.utils;
  10. function deviceListFetched (_converse, jid) {
  11. return _.get(_.filter(
  12. _converse.connection.IQ_stanzas,
  13. iq => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.devicelist"]`)
  14. ).pop(), 'nodeTree');
  15. }
  16. function ownDeviceHasBeenPublished (_converse) {
  17. return _.get(_.filter(
  18. _converse.connection.IQ_stanzas,
  19. iq => iq.nodeTree.querySelector('iq[from="'+_converse.bare_jid+'"] publish[node="eu.siacs.conversations.axolotl.devicelist"]')
  20. ).pop(), 'nodeTree');
  21. }
  22. function bundleHasBeenPublished (_converse) {
  23. return _.get(_.filter(
  24. _converse.connection.IQ_stanzas,
  25. iq => iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:123456789"]')
  26. ).pop(), 'nodeTree');
  27. }
  28. function bundleFetched (_converse, jid, device_id) {
  29. return _.get(_.filter(
  30. _converse.connection.IQ_stanzas,
  31. (iq) => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`)
  32. ).pop(), 'nodeTree');
  33. }
  34. function initializedOMEMO (_converse) {
  35. return test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
  36. .then(iq_stanza => {
  37. const stanza = $iq({
  38. 'from': _converse.bare_jid,
  39. 'id': iq_stanza.getAttribute('id'),
  40. 'to': _converse.bare_jid,
  41. 'type': 'result',
  42. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  43. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  44. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  45. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  46. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  47. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  48. return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
  49. }).then(iq_stanza => {
  50. const stanza = $iq({
  51. 'from': _converse.bare_jid,
  52. 'id': iq_stanza.getAttribute('id'),
  53. 'to': _converse.bare_jid,
  54. 'type': 'result'});
  55. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  56. return test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
  57. }).then(iq_stanza => {
  58. const stanza = $iq({
  59. 'from': _converse.bare_jid,
  60. 'id': iq_stanza.getAttribute('id'),
  61. 'to': _converse.bare_jid,
  62. 'type': 'result'});
  63. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  64. return _converse.api.waitUntil('OMEMOInitialized');
  65. });
  66. }
  67. describe("The OMEMO module", function() {
  68. it("adds methods for encrypting and decrypting messages via AES GCM",
  69. mock.initConverseWithPromises(
  70. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  71. function (done, _converse) {
  72. const message = 'This message will be encrypted'
  73. let view;
  74. test_utils.createContacts(_converse, 'current', 1);
  75. _converse.emit('rosterContactsFetched');
  76. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  77. test_utils.openChatBoxFor(_converse, contact_jid)
  78. .then((v) => {
  79. view = v;
  80. return view.model.encryptMessage(message);
  81. }).then((payload) => {
  82. return view.model.decryptMessage(payload);
  83. }).then((result) => {
  84. expect(result).toBe(message);
  85. done();
  86. });
  87. }));
  88. it("enables encrypted messages to be sent and received",
  89. mock.initConverseWithPromises(
  90. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  91. function (done, _converse) {
  92. let view, sent_stanza;
  93. test_utils.createContacts(_converse, 'current', 1);
  94. _converse.emit('rosterContactsFetched');
  95. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  96. return test_utils.waitUntil(() => initializedOMEMO(_converse))
  97. .then(() => test_utils.openChatBoxFor(_converse, contact_jid))
  98. .then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)))
  99. .then(iq_stanza => {
  100. const stanza = $iq({
  101. 'from': contact_jid,
  102. 'id': iq_stanza.getAttribute('id'),
  103. 'to': _converse.connection.jid,
  104. 'type': 'result',
  105. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  106. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  107. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  108. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  109. .c('device', {'id': '555'});
  110. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  111. return test_utils.waitUntil(() => _converse.omemo_store);
  112. }).then(() => {
  113. const devicelist = _converse.devicelists.get({'jid': contact_jid});
  114. expect(devicelist.devices.length).toBe(1);
  115. view = _converse.chatboxviews.get(contact_jid);
  116. view.model.set('omemo_active', true);
  117. const textarea = view.el.querySelector('.chat-textarea');
  118. textarea.value = 'This message will be encrypted';
  119. view.keyPressed({
  120. target: textarea,
  121. preventDefault: _.noop,
  122. keyCode: 13 // Enter
  123. });
  124. return test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
  125. }).then((iq_stanza) => {
  126. const stanza = $iq({
  127. 'from': contact_jid,
  128. 'id': iq_stanza.getAttribute('id'),
  129. 'to': _converse.bare_jid,
  130. 'type': 'result',
  131. }).c('pubsub', {
  132. 'xmlns': 'http://jabber.org/protocol/pubsub'
  133. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  134. .c('item')
  135. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  136. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  137. .c('signedPreKeySignature').t(btoa('2222')).up()
  138. .c('identityKey').t(btoa('3333')).up()
  139. .c('prekeys')
  140. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  141. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  142. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  143. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  144. return test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
  145. }).then(iq_stanza => {
  146. const stanza = $iq({
  147. 'from': _converse.bare_jid,
  148. 'id': iq_stanza.getAttribute('id'),
  149. 'to': _converse.bare_jid,
  150. 'type': 'result',
  151. }).c('pubsub', {
  152. 'xmlns': 'http://jabber.org/protocol/pubsub'
  153. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
  154. .c('item')
  155. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  156. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
  157. .c('signedPreKeySignature').t(btoa('200000')).up()
  158. .c('identityKey').t(btoa('300000')).up()
  159. .c('prekeys')
  160. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
  161. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
  162. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
  163. spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
  164. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  165. return test_utils.waitUntil(() => sent_stanza);
  166. }).then(() => {
  167. expect(sent_stanza.toLocaleString()).toBe(
  168. `<message from='dummy@localhost/resource' to='max.frankfurter@localhost' `+
  169. `type='chat' id='${sent_stanza.nodeTree.getAttribute('id')}' xmlns='jabber:client'>`+
  170. `<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
  171. `<encrypted xmlns='eu.siacs.conversations.axolotl'>`+
  172. `<header sid='123456789'>`+
  173. `<key rid='482886413b977930064a5888b92134fe'>eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9</key>`+
  174. `<key rid='555'>eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9</key>`+
  175. `<iv>${sent_stanza.nodeTree.querySelector('iv').textContent}</iv>`+
  176. `</header>`+
  177. `<payload>${sent_stanza.nodeTree.querySelector('payload').textContent}</payload>`+
  178. `</encrypted>`+
  179. `<store xmlns='urn:xmpp:hints'/>`+
  180. `</message>`);
  181. // Test reception of an encrypted message
  182. return view.model.encryptMessage('This is an encrypted message from the contact')
  183. }).then((obj) => {
  184. // XXX: Normally the key will be encrypted via libsignal.
  185. // However, we're mocking libsignal in the tests, so we include
  186. // it as plaintext in the message.
  187. const key = btoa(JSON.stringify({
  188. 'type': 1,
  189. 'body': obj.key_and_tag,
  190. 'registrationId': '1337'
  191. }));
  192. const stanza = $msg({
  193. 'from': contact_jid,
  194. 'to': _converse.connection.jid,
  195. 'type': 'chat',
  196. 'id': 'qwerty'
  197. }).c('body').t('This is a fallback message').up()
  198. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  199. .c('header', {'sid': '555'})
  200. .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(key).up()
  201. .c('iv').t(obj.iv)
  202. .up().up()
  203. .c('payload').t(obj.payload);
  204. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  205. return test_utils.waitUntil(() => view.model.messages.length > 1);
  206. }).then(() => {
  207. expect(view.model.messages.length).toBe(2);
  208. const last_msg = view.model.messages.at(1);
  209. expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
  210. .toBe('This is an encrypted message from the contact');
  211. done();
  212. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
  213. }));
  214. it("can receive a PreKeySignalMessage",
  215. mock.initConverseWithPromises(
  216. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  217. function (done, _converse) {
  218. _converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
  219. let view, sent_stanza;
  220. test_utils.createContacts(_converse, 'current', 1);
  221. _converse.emit('rosterContactsFetched');
  222. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  223. return test_utils.waitUntil(() => initializedOMEMO(_converse))
  224. .then(() => _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact'))
  225. .then(obj => {
  226. // XXX: Normally the key will be encrypted via libsignal.
  227. // However, we're mocking libsignal in the tests, so we include
  228. // it as plaintext in the message.
  229. const key = btoa(JSON.stringify({
  230. 'type': 1,
  231. 'body': obj.key_and_tag,
  232. 'registrationId': '1337'
  233. }));
  234. const stanza = $msg({
  235. 'from': contact_jid,
  236. 'to': _converse.connection.jid,
  237. 'type': 'chat',
  238. 'id': 'qwerty'
  239. }).c('body').t('This is a fallback message').up()
  240. .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
  241. .c('header', {'sid': '555'})
  242. .c('key', {'prekey': 'true', 'rid': _converse.omemo_store.get('device_id')}).t(key).up()
  243. .c('iv').t(obj.iv)
  244. .up().up()
  245. .c('payload').t(obj.payload);
  246. const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
  247. spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
  248. // Since it's difficult to override
  249. // decryptPreKeyWhisperMessage, where a prekey will be
  250. // removed from the store, we do it here, before the
  251. // missing prekeys are generated.
  252. _converse.omemo_store.removePreKey(1);
  253. return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
  254. });
  255. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  256. return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid))
  257. }).then(iq_stanza => deviceListFetched(_converse, contact_jid))
  258. .then(iq_stanza => {
  259. const stanza = $iq({
  260. 'from': contact_jid,
  261. 'id': iq_stanza.getAttribute('id'),
  262. 'to': _converse.connection.jid,
  263. 'type': 'result',
  264. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  265. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  266. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  267. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  268. .c('device', {'id': '555'});
  269. // XXX: the bundle gets published twice, we want to make sure
  270. // that we wait for the 2nd, so we clear all the already sent
  271. // stanzas.
  272. _converse.connection.IQ_stanzas = [];
  273. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  274. return test_utils.waitUntil(() => _converse.omemo_store);
  275. }).then(() => test_utils.waitUntil(() => bundleHasBeenPublished(_converse)))
  276. .then(iq_stanza => {
  277. expect(iq_stanza.outerHTML).toBe(
  278. `<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
  279. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  280. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  281. `<item>`+
  282. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  283. `<signedPreKeyPublic signedPreKeyId="0">${btoa('1234')}</signedPreKeyPublic>`+
  284. `<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
  285. `<identityKey>${btoa('1234')}</identityKey>`+
  286. `<prekeys>`+
  287. `<preKeyPublic preKeyId="0">${btoa('1234')}</preKeyPublic>`+
  288. `<preKeyPublic preKeyId="1">${btoa('1234')}</preKeyPublic>`+
  289. `<preKeyPublic preKeyId="2">${btoa('1234')}</preKeyPublic>`+
  290. `<preKeyPublic preKeyId="3">${btoa('1234')}</preKeyPublic>`+
  291. `<preKeyPublic preKeyId="4">${btoa('1234')}</preKeyPublic>`+
  292. `</prekeys>`+
  293. `</bundle>`+
  294. `</item>`+
  295. `</publish>`+
  296. `</pubsub>`+
  297. `</iq>`)
  298. const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id'));
  299. expect(own_device.get('bundle').prekeys.length).toBe(5);
  300. expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled();
  301. done();
  302. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
  303. }));
  304. it("updates device lists based on PEP messages",
  305. mock.initConverseWithPromises(
  306. null, ['rosterGroupsFetched'], {},
  307. function (done, _converse) {
  308. test_utils.createContacts(_converse, 'current', 1);
  309. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  310. // Wait until own devices are fetched
  311. test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
  312. .then(iq_stanza => {
  313. expect(iq_stanza.outerHTML).toBe(
  314. '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  315. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  316. '<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
  317. '</pubsub>'+
  318. '</iq>');
  319. const stanza = $iq({
  320. 'from': _converse.bare_jid,
  321. 'id': iq_stanza.getAttribute('id'),
  322. 'to': _converse.bare_jid,
  323. 'type': 'result',
  324. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  325. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  326. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  327. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  328. .c('device', {'id': '555'});
  329. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  330. return test_utils.waitUntil(() => _converse.omemo_store);
  331. }).then(() => {
  332. expect(_converse.devicelists.length).toBe(1);
  333. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  334. expect(devicelist.devices.length).toBe(2);
  335. expect(devicelist.devices.at(0).get('id')).toBe('555');
  336. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  337. return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  338. }).then(iq_stanza => {
  339. const stanza = $iq({
  340. 'from': _converse.bare_jid,
  341. 'id': iq_stanza.getAttribute('id'),
  342. 'to': _converse.bare_jid,
  343. 'type': 'result'});
  344. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  345. return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  346. }).then(iq_stanza => {
  347. const stanza = $iq({
  348. 'from': _converse.bare_jid,
  349. 'id': iq_stanza.getAttribute('id'),
  350. 'to': _converse.bare_jid,
  351. 'type': 'result'});
  352. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  353. return _converse.api.waitUntil('OMEMOInitialized');
  354. }).then(() => {
  355. let stanza = $msg({
  356. 'from': contact_jid,
  357. 'to': _converse.bare_jid,
  358. 'type': 'headline',
  359. 'id': 'update_01',
  360. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  361. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  362. .c('item')
  363. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  364. .c('device', {'id': '1234'})
  365. .c('device', {'id': '4223'})
  366. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  367. expect(_converse.devicelists.length).toBe(2);
  368. let devices = _converse.devicelists.get(contact_jid).devices;
  369. expect(devices.length).toBe(2);
  370. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
  371. stanza = $msg({
  372. 'from': contact_jid,
  373. 'to': _converse.bare_jid,
  374. 'type': 'headline',
  375. 'id': 'update_02',
  376. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  377. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  378. .c('item')
  379. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  380. .c('device', {'id': '4223'})
  381. .c('device', {'id': '4224'})
  382. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  383. expect(_converse.devicelists.length).toBe(2);
  384. expect(devices.length).toBe(2);
  385. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224');
  386. // Check that own devicelist gets updated
  387. stanza = $msg({
  388. 'from': _converse.bare_jid,
  389. 'to': _converse.bare_jid,
  390. 'type': 'headline',
  391. 'id': 'update_03',
  392. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  393. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  394. .c('item')
  395. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  396. .c('device', {'id': '123456789'})
  397. .c('device', {'id': '555'})
  398. .c('device', {'id': '777'})
  399. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  400. expect(_converse.devicelists.length).toBe(2);
  401. devices = _converse.devicelists.get(_converse.bare_jid).devices;
  402. expect(devices.length).toBe(3);
  403. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
  404. _converse.connection.IQ_stanzas = [];
  405. // Check that own device gets re-added
  406. stanza = $msg({
  407. 'from': _converse.bare_jid,
  408. 'to': _converse.bare_jid,
  409. 'type': 'headline',
  410. 'id': 'update_04',
  411. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  412. .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
  413. .c('item')
  414. .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
  415. .c('device', {'id': '444'})
  416. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  417. return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  418. }).then(iq_stanza => {
  419. // Check that our own device is added again, but that removed
  420. // devices are not added.
  421. expect(iq_stanza.outerHTML).toBe(
  422. '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
  423. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  424. '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
  425. '<item>'+
  426. '<list xmlns="eu.siacs.conversations.axolotl">'+
  427. '<device id="123456789"/>'+
  428. '<device id="444"/>'+
  429. '</list>'+
  430. '</item>'+
  431. '</publish>'+
  432. '</pubsub>'+
  433. '</iq>');
  434. expect(_converse.devicelists.length).toBe(2);
  435. const devices = _converse.devicelists.get(_converse.bare_jid).devices;
  436. // The device id for this device (123456789) was also generated and added to the list,
  437. // which is why we have 2 devices now.
  438. expect(devices.length).toBe(2);
  439. expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
  440. done();
  441. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
  442. }));
  443. it("updates device bundles based on PEP messages",
  444. mock.initConverseWithPromises(
  445. null, ['rosterGroupsFetched'], {},
  446. function (done, _converse) {
  447. test_utils.createContacts(_converse, 'current');
  448. const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
  449. test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
  450. .then(iq_stanza => {
  451. expect(iq_stanza.outerHTML).toBe(
  452. '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  453. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  454. '<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
  455. '</pubsub>'+
  456. '</iq>');
  457. const stanza = $iq({
  458. 'from': contact_jid,
  459. 'id': iq_stanza.getAttribute('id'),
  460. 'to': _converse.bare_jid,
  461. 'type': 'result',
  462. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  463. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  464. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  465. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  466. .c('device', {'id': '555'});
  467. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  468. return test_utils.waitUntil(() => _converse.omemo_store);
  469. }).then(() => {
  470. expect(_converse.devicelists.length).toBe(1);
  471. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  472. expect(devicelist.devices.length).toBe(2);
  473. expect(devicelist.devices.at(0).get('id')).toBe('555');
  474. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  475. return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  476. }).then(iq_stanza => {
  477. const stanza = $iq({
  478. 'from': _converse.bare_jid,
  479. 'id': iq_stanza.getAttribute('id'),
  480. 'to': _converse.bare_jid,
  481. 'type': 'result'});
  482. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  483. return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  484. }).then(iq_stanza => {
  485. const stanza = $iq({
  486. 'from': _converse.bare_jid,
  487. 'id': iq_stanza.getAttribute('id'),
  488. 'to': _converse.bare_jid,
  489. 'type': 'result'});
  490. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  491. return _converse.api.waitUntil('OMEMOInitialized');
  492. }).then(() => {
  493. let stanza = $msg({
  494. 'from': contact_jid,
  495. 'to': _converse.bare_jid,
  496. 'type': 'headline',
  497. 'id': 'update_01',
  498. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  499. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  500. .c('item')
  501. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  502. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
  503. .c('signedPreKeySignature').t('2222').up()
  504. .c('identityKey').t('3333').up()
  505. .c('prekeys')
  506. .c('preKeyPublic', {'preKeyId': '1001'}).up()
  507. .c('preKeyPublic', {'preKeyId': '1002'}).up()
  508. .c('preKeyPublic', {'preKeyId': '1003'});
  509. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  510. expect(_converse.devicelists.length).toBe(2);
  511. let devicelist = _converse.devicelists.get(contact_jid);
  512. expect(devicelist.devices.length).toBe(1);
  513. let device = devicelist.devices.at(0);
  514. expect(device.get('bundle').identity_key).toBe('3333');
  515. expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
  516. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  517. expect(device.get('bundle').signed_prekey.signature).toBe('2222');
  518. expect(device.get('bundle').prekeys.length).toBe(3);
  519. expect(device.get('bundle').prekeys[0].id).toBe(1001);
  520. expect(device.get('bundle').prekeys[1].id).toBe(1002);
  521. expect(device.get('bundle').prekeys[2].id).toBe(1003);
  522. stanza = $msg({
  523. 'from': contact_jid,
  524. 'to': _converse.bare_jid,
  525. 'type': 'headline',
  526. 'id': 'update_02',
  527. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  528. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
  529. .c('item')
  530. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  531. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
  532. .c('signedPreKeySignature').t('6666').up()
  533. .c('identityKey').t('7777').up()
  534. .c('prekeys')
  535. .c('preKeyPublic', {'preKeyId': '2001'}).up()
  536. .c('preKeyPublic', {'preKeyId': '2002'}).up()
  537. .c('preKeyPublic', {'preKeyId': '2003'});
  538. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  539. expect(_converse.devicelists.length).toBe(2);
  540. devicelist = _converse.devicelists.get(contact_jid);
  541. expect(devicelist.devices.length).toBe(1);
  542. device = devicelist.devices.at(0);
  543. expect(device.get('bundle').identity_key).toBe('7777');
  544. expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
  545. expect(device.get('bundle').signed_prekey.id).toBe(4223);
  546. expect(device.get('bundle').signed_prekey.signature).toBe('6666');
  547. expect(device.get('bundle').prekeys.length).toBe(3);
  548. expect(device.get('bundle').prekeys[0].id).toBe(2001);
  549. expect(device.get('bundle').prekeys[1].id).toBe(2002);
  550. expect(device.get('bundle').prekeys[2].id).toBe(2003);
  551. stanza = $msg({
  552. 'from': _converse.bare_jid,
  553. 'to': _converse.bare_jid,
  554. 'type': 'headline',
  555. 'id': 'update_03',
  556. }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
  557. .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
  558. .c('item')
  559. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  560. .c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
  561. .c('signedPreKeySignature').t('3333').up()
  562. .c('identityKey').t('1111').up()
  563. .c('prekeys')
  564. .c('preKeyPublic', {'preKeyId': '3001'}).up()
  565. .c('preKeyPublic', {'preKeyId': '3002'}).up()
  566. .c('preKeyPublic', {'preKeyId': '3003'});
  567. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  568. expect(_converse.devicelists.length).toBe(2);
  569. devicelist = _converse.devicelists.get(_converse.bare_jid);
  570. expect(devicelist.devices.length).toBe(2);
  571. expect(devicelist.devices.at(0).get('id')).toBe('555');
  572. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  573. device = devicelist.devices.at(1);
  574. expect(device.get('bundle').identity_key).toBe('1111');
  575. expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
  576. expect(device.get('bundle').signed_prekey.id).toBe(9999);
  577. expect(device.get('bundle').signed_prekey.signature).toBe('3333');
  578. expect(device.get('bundle').prekeys.length).toBe(3);
  579. expect(device.get('bundle').prekeys[0].id).toBe(3001);
  580. expect(device.get('bundle').prekeys[1].id).toBe(3002);
  581. expect(device.get('bundle').prekeys[2].id).toBe(3003);
  582. done();
  583. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
  584. }));
  585. it("publishes a bundle with which an encrypted session can be created",
  586. mock.initConverseWithPromises(
  587. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  588. function (done, _converse) {
  589. _converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
  590. test_utils.createContacts(_converse, 'current', 1);
  591. _converse.emit('rosterContactsFetched');
  592. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  593. test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
  594. .then(iq_stanza => {
  595. const stanza = $iq({
  596. 'from': contact_jid,
  597. 'id': iq_stanza.getAttribute('id'),
  598. 'to': _converse.bare_jid,
  599. 'type': 'result',
  600. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  601. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  602. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  603. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  604. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  605. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  606. expect(_converse.devicelists.length).toBe(1);
  607. return test_utils.openChatBoxFor(_converse, contact_jid);
  608. }).then(() => ownDeviceHasBeenPublished(_converse))
  609. .then(iq_stanza => {
  610. const stanza = $iq({
  611. 'from': _converse.bare_jid,
  612. 'id': iq_stanza.getAttribute('id'),
  613. 'to': _converse.bare_jid,
  614. 'type': 'result'});
  615. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  616. return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  617. }).then(iq_stanza => {
  618. expect(iq_stanza.outerHTML).toBe(
  619. `<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
  620. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  621. `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
  622. `<item>`+
  623. `<bundle xmlns="eu.siacs.conversations.axolotl">`+
  624. `<signedPreKeyPublic signedPreKeyId="0">${btoa('1234')}</signedPreKeyPublic>`+
  625. `<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
  626. `<identityKey>${btoa('1234')}</identityKey>`+
  627. `<prekeys>`+
  628. `<preKeyPublic preKeyId="0">${btoa('1234')}</preKeyPublic>`+
  629. `<preKeyPublic preKeyId="1">${btoa('1234')}</preKeyPublic>`+
  630. `</prekeys>`+
  631. `</bundle>`+
  632. `</item>`+
  633. `</publish>`+
  634. `</pubsub>`+
  635. `</iq>`)
  636. const stanza = $iq({
  637. 'from': _converse.bare_jid,
  638. 'id': iq_stanza.getAttribute('id'),
  639. 'to': _converse.bare_jid,
  640. 'type': 'result'});
  641. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  642. return _converse.api.waitUntil('OMEMOInitialized');
  643. }).then(done).catch(_.partial(console.error, _));
  644. }));
  645. it("adds a toolbar button for starting an encrypted chat session",
  646. mock.initConverseWithPromises(
  647. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  648. function (done, _converse) {
  649. let modal;
  650. test_utils.createContacts(_converse, 'current', 1);
  651. _converse.emit('rosterContactsFetched');
  652. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  653. test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid))
  654. .then(iq_stanza => {
  655. expect(iq_stanza.outerHTML).toBe(
  656. '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  657. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  658. '<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
  659. '</pubsub>'+
  660. '</iq>');
  661. const stanza = $iq({
  662. 'from': _converse.bare_jid,
  663. 'id': iq_stanza.getAttribute('id'),
  664. 'to': _converse.bare_jid,
  665. 'type': 'result',
  666. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  667. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  668. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  669. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  670. .c('device', {'id': '482886413b977930064a5888b92134fe'});
  671. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  672. return test_utils.waitUntil(() => _converse.omemo_store);
  673. }).then(() => {
  674. expect(_converse.devicelists.length).toBe(1);
  675. const devicelist = _converse.devicelists.get(_converse.bare_jid);
  676. expect(devicelist.devices.length).toBe(2);
  677. expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
  678. expect(devicelist.devices.at(1).get('id')).toBe('123456789');
  679. // Check that own device was published
  680. return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
  681. }).then(iq_stanza => {
  682. expect(iq_stanza.outerHTML).toBe(
  683. '<iq from="dummy@localhost" type="set" xmlns="jabber:client" id="'+iq_stanza.getAttribute('id')+'">'+
  684. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  685. '<publish node="eu.siacs.conversations.axolotl.devicelist">'+
  686. '<item>'+
  687. '<list xmlns="eu.siacs.conversations.axolotl">'+
  688. '<device id="482886413b977930064a5888b92134fe"/>'+
  689. '<device id="123456789"/>'+
  690. '</list>'+
  691. '</item>'+
  692. '</publish>'+
  693. '</pubsub>'+
  694. '</iq>');
  695. const stanza = $iq({
  696. 'from': _converse.bare_jid,
  697. 'id': iq_stanza.getAttribute('id'),
  698. 'to': _converse.bare_jid,
  699. 'type': 'result'});
  700. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  701. return test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
  702. }).then(iq_stanza => {
  703. expect(iq_stanza.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
  704. expect(iq_stanza.querySelector('prekeys').childNodes.length).toBe(100);
  705. const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
  706. expect(signed_prekeys.length).toBe(1);
  707. const signed_prekey = signed_prekeys[0];
  708. expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
  709. expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
  710. expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
  711. const stanza = $iq({
  712. 'from': _converse.bare_jid,
  713. 'id': iq_stanza.getAttribute('id'),
  714. 'to': _converse.bare_jid,
  715. 'type': 'result'});
  716. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  717. return _converse.api.waitUntil('OMEMOInitialized', 1000);
  718. }).then(() => {
  719. return test_utils.openChatBoxFor(_converse, contact_jid);
  720. }).then(() => {
  721. return test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
  722. }).then(iq_stanza => {
  723. expect(iq_stanza.outerHTML).toBe(
  724. '<iq type="get" from="dummy@localhost" to="'+contact_jid+'" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
  725. '<pubsub xmlns="http://jabber.org/protocol/pubsub">'+
  726. '<items node="eu.siacs.conversations.axolotl.devicelist"/>'+
  727. '</pubsub>'+
  728. '</iq>');
  729. const stanza = $iq({
  730. 'from': contact_jid,
  731. 'id': iq_stanza.getAttribute('id'),
  732. 'to': _converse.bare_jid,
  733. 'type': 'result',
  734. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  735. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  736. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  737. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  738. .c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
  739. .c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
  740. .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
  741. .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
  742. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  743. const devicelist = _converse.devicelists.get(contact_jid);
  744. return test_utils.waitUntil(() => devicelist.devices.length);
  745. }).then(() => {
  746. expect(_converse.devicelists.length).toBe(2);
  747. const devicelist = _converse.devicelists.get(contact_jid);
  748. expect(devicelist.devices.length).toBe(4);
  749. expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
  750. expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
  751. expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
  752. expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
  753. return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
  754. }).then(() => {
  755. const view = _converse.chatboxviews.get(contact_jid);
  756. const toolbar = view.el.querySelector('.chat-toolbar');
  757. expect(view.model.get('omemo_active')).toBe(undefined);
  758. const toggle = toolbar.querySelector('.toggle-omemo');
  759. expect(_.isNull(toggle)).toBe(false);
  760. expect(u.hasClass('fa-unlock', toggle)).toBe(true);
  761. expect(u.hasClass('fa-lock', toggle)).toBe(false);
  762. spyOn(view, 'toggleOMEMO').and.callThrough();
  763. view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
  764. toolbar.querySelector('.toggle-omemo').click();
  765. expect(view.toggleOMEMO).toHaveBeenCalled();
  766. expect(view.model.get('omemo_active')).toBe(true);
  767. return test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
  768. }).then(() => {
  769. const view = _converse.chatboxviews.get(contact_jid);
  770. const toolbar = view.el.querySelector('.chat-toolbar');
  771. const toggle = toolbar.querySelector('.toggle-omemo');
  772. expect(u.hasClass('fa-unlock', toggle)).toBe(false);
  773. expect(u.hasClass('fa-lock', toggle)).toBe(true);
  774. const textarea = view.el.querySelector('.chat-textarea');
  775. textarea.value = 'This message will be sent encrypted';
  776. view.keyPressed({
  777. target: textarea,
  778. preventDefault: _.noop,
  779. keyCode: 13
  780. });
  781. done();
  782. }).catch(_.partial(console.error, _));
  783. }));
  784. it("shows OMEMO device fingerprints in the user details modal",
  785. mock.initConverseWithPromises(
  786. null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
  787. function (done, _converse) {
  788. let modal;
  789. test_utils.createContacts(_converse, 'current', 1);
  790. _converse.emit('rosterContactsFetched');
  791. const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
  792. test_utils.openChatBoxFor(_converse, contact_jid)
  793. .then(() => {
  794. // We simply emit, to avoid doing all the setup work
  795. _converse.emit('OMEMOInitialized');
  796. const view = _converse.chatboxviews.get(contact_jid);
  797. const show_modal_button = view.el.querySelector('.show-user-details-modal');
  798. show_modal_button.click();
  799. modal = view.user_details_modal;
  800. return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  801. }).then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)))
  802. .then(iq_stanza => {
  803. expect(iq_stanza.outerHTML).toBe(
  804. `<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
  805. `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
  806. `</iq>`);
  807. const stanza = $iq({
  808. 'from': contact_jid,
  809. 'id': iq_stanza.getAttribute('id'),
  810. 'to': _converse.bare_jid,
  811. 'type': 'result',
  812. }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
  813. .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
  814. .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
  815. .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
  816. .c('device', {'id': '555'});
  817. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  818. return test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
  819. }).then(() => test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555')))
  820. .then(iq_stanza => {
  821. expect(iq_stanza.outerHTML).toBe(
  822. `<iq type="get" from="dummy@localhost" to="max.frankfurter@localhost" xmlns="jabber:client" id="${iq_stanza.getAttribute('id')}">`+
  823. `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
  824. `<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
  825. `</pubsub>`+
  826. `</iq>`);
  827. const stanza = $iq({
  828. 'from': contact_jid,
  829. 'id': iq_stanza.getAttribute('id'),
  830. 'to': _converse.bare_jid,
  831. 'type': 'result',
  832. }).c('pubsub', {
  833. 'xmlns': 'http://jabber.org/protocol/pubsub'
  834. }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
  835. .c('item')
  836. .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
  837. .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
  838. .c('signedPreKeySignature').t(btoa('2222')).up()
  839. .c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up()
  840. .c('prekeys')
  841. .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
  842. .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
  843. .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
  844. _converse.connection._dataRecv(test_utils.createRequest(stanza));
  845. const view = _converse.chatboxviews.get(contact_jid);
  846. const modal = view.user_details_modal;
  847. return test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
  848. }).then(() => {
  849. const view = _converse.chatboxviews.get(contact_jid);
  850. const modal = view.user_details_modal;
  851. expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
  852. const el = modal.el.querySelector('.fingerprints .fingerprint');
  853. expect(el.textContent.trim()).toBe(
  854. u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
  855. );
  856. expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
  857. const devicelist = _converse.devicelists.get(contact_jid);
  858. expect(devicelist.devices.get('555').get('trusted')).toBe(0);
  859. let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
  860. expect(trusted_radio.checked).toBe(true);
  861. let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
  862. expect(untrusted_radio.checked).toBe(false);
  863. // Test that the device can be set to untrusted
  864. untrusted_radio.click();
  865. trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
  866. expect(trusted_radio.hasAttribute('checked')).toBe(false);
  867. expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
  868. untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
  869. expect(untrusted_radio.hasAttribute('checked')).toBe(true);
  870. trusted_radio.click();
  871. expect(devicelist.devices.get('555').get('trusted')).toBe(1);
  872. done();
  873. }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
  874. }));
  875. });
  876. describe("A chatbox with an active OMEMO session", function() {
  877. it("will not show the spoiler toolbar button",
  878. mock.initConverseWithPromises(
  879. null, ['rosterGroupsFetched'], {},
  880. function (done, _converse) {
  881. // TODO
  882. done()
  883. }));
  884. });
  885. }));