2
0

omemo.js 55 KB

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