omemo.js 52 KB

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