enigma.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. /* Enigma Plugin */
  2. window.rcmail && rcmail.addEventListener('init', function(evt) {
  3. if (rcmail.env.task == 'settings') {
  4. if (rcmail.gui_objects.keyslist) {
  5. rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist,
  6. {multiselect:true, draggable:false, keyboard:false});
  7. rcmail.keys_list
  8. .addEventListener('select', function(o) { rcmail.enigma_keylist_select(o); })
  9. .addEventListener('keypress', function(o) { rcmail.enigma_keylist_keypress(o); })
  10. .init()
  11. .focus();
  12. rcmail.enigma_list();
  13. rcmail.register_command('firstpage', function(props) { return rcmail.enigma_list_page('first'); });
  14. rcmail.register_command('previouspage', function(props) { return rcmail.enigma_list_page('previous'); });
  15. rcmail.register_command('nextpage', function(props) { return rcmail.enigma_list_page('next'); });
  16. rcmail.register_command('lastpage', function(props) { return rcmail.enigma_list_page('last'); });
  17. }
  18. if (rcmail.env.action == 'plugin.enigmakeys') {
  19. rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true);
  20. rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true);
  21. rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import(); }, true);
  22. rcmail.register_command('plugin.enigma-import-search', function() { rcmail.enigma_import_search(); }, true);
  23. rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_export(); });
  24. rcmail.register_command('plugin.enigma-key-export-selected', function() { rcmail.enigma_export(true); });
  25. rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import(); }, true);
  26. rcmail.register_command('plugin.enigma-key-delete', function(props) { return rcmail.enigma_delete(); });
  27. rcmail.register_command('plugin.enigma-key-create', function(props) { return rcmail.enigma_key_create(); }, true);
  28. rcmail.register_command('plugin.enigma-key-save', function(props) { return rcmail.enigma_key_create_save(); }, true);
  29. rcmail.addEventListener('responseafterplugin.enigmakeys', function() {
  30. rcmail.enable_command('plugin.enigma-key-export', rcmail.env.rowcount > 0);
  31. });
  32. if (rcmail.gui_objects.importform) {
  33. // make sure Enter key in search input starts searching
  34. // instead of submitting the form
  35. $('#rcmimportsearch').keydown(function(e) {
  36. if (e.which == 13) {
  37. rcmail.enigma_import_search();
  38. return false;
  39. }
  40. });
  41. $('input[type="button"]:first').focus();
  42. }
  43. }
  44. }
  45. else if (rcmail.env.task == 'mail') {
  46. if (rcmail.env.action == 'compose') {
  47. rcmail.addEventListener('beforesend', function(props) { rcmail.enigma_beforesend_handler(props); })
  48. .addEventListener('beforesavedraft', function(props) { rcmail.enigma_beforesavedraft_handler(props); });
  49. $('input,label', $('#enigmamenu')).mouseup(function(e) {
  50. // don't close the menu on mouse click inside
  51. e.stopPropagation();
  52. });
  53. $('a.button.enigma').prop('tabindex', $('#messagetoolbar > a:first').prop('tabindex'));
  54. }
  55. $.each(['encrypt', 'sign'], function() {
  56. if (rcmail.env['enigma_force_' + this])
  57. $('[name="_enigma_' + this + '"]').prop('checked', true);
  58. });
  59. if (rcmail.env.enigma_password_request) {
  60. rcmail.enigma_password_request(rcmail.env.enigma_password_request);
  61. }
  62. }
  63. });
  64. /*********************************************************/
  65. /********* Enigma Settings/Keys/Certs UI *********/
  66. /*********************************************************/
  67. // Display key(s) import form
  68. rcube_webmail.prototype.enigma_key_import = function()
  69. {
  70. this.enigma_loadframe('&_action=plugin.enigmakeys&_a=import');
  71. };
  72. // Display key(s) generation form
  73. rcube_webmail.prototype.enigma_key_create = function()
  74. {
  75. this.enigma_loadframe('&_action=plugin.enigmakeys&_a=create');
  76. };
  77. // Generate key(s) and submit them
  78. rcube_webmail.prototype.enigma_key_create_save = function()
  79. {
  80. var options, lock, users = [],
  81. password = $('#key-pass').val(),
  82. confirm = $('#key-pass-confirm').val(),
  83. size = $('#key-size').val();
  84. $('[name="identity[]"]:checked').each(function() {
  85. users.push(this.value);
  86. });
  87. // validate the form
  88. if (!password || !confirm)
  89. return alert(this.get_label('enigma.formerror'));
  90. if (password != confirm)
  91. return alert(this.get_label('enigma.passwordsdiffer'));
  92. if (!users.length)
  93. return alert(this.get_label('enigma.noidentselected'));
  94. // generate keys
  95. // use OpenPGP.js if browser supports required features
  96. if (window.openpgp && window.crypto && (window.crypto.getRandomValues || window.crypto.subtle)) {
  97. lock = this.set_busy(true, 'enigma.keygenerating');
  98. options = {
  99. numBits: size,
  100. userId: users,
  101. passphrase: password
  102. };
  103. openpgp.generateKeyPair(options).then(function(keypair) {
  104. // success
  105. var post = {_a: 'import', _keys: keypair.privateKeyArmored, _generated: 1,
  106. _passwd: password, _keyid: keypair.key.primaryKey.fingerprint};
  107. // send request to server
  108. rcmail.http_post('plugin.enigmakeys', post, lock);
  109. }, function(error) {
  110. // failure
  111. rcmail.set_busy(false, null, lock);
  112. rcmail.display_message(rcmail.get_label('enigma.keygenerateerror'), 'error');
  113. });
  114. }
  115. else {
  116. rcmail.display_message(rcmail.get_label('enigma.keygennosupport'), 'error');
  117. }
  118. };
  119. // Action executed after successful key generation and import
  120. rcube_webmail.prototype.enigma_key_create_success = function()
  121. {
  122. parent.rcmail.enigma_list(1);
  123. };
  124. // Delete key(s)
  125. rcube_webmail.prototype.enigma_delete = function()
  126. {
  127. var keys = this.keys_list.get_selection();
  128. if (!keys.length || !confirm(this.get_label('enigma.keyremoveconfirm')))
  129. return;
  130. var lock = this.display_message(this.get_label('enigma.keyremoving'), 'loading'),
  131. post = {_a: 'delete', _keys: keys};
  132. // send request to server
  133. this.http_post('plugin.enigmakeys', post, lock);
  134. };
  135. // Export key(s)
  136. rcube_webmail.prototype.enigma_export = function(selected)
  137. {
  138. var priv = false,
  139. list = this.keys_list,
  140. keys = selected ? list.get_selection().join(',') : '*',
  141. args = {_keys: keys};
  142. if (!keys.length)
  143. return;
  144. // find out whether selected keys are private
  145. if (keys == '*')
  146. priv = true;
  147. else
  148. $.each(list.get_selection(), function() {
  149. flags = $(list.rows[this].obj).data('flags');
  150. if (flags && flags.indexOf('p') >= 0) {
  151. priv = true;
  152. return false;
  153. }
  154. });
  155. // ask the user about including private key in the export
  156. if (priv)
  157. return this.show_popup_dialog(
  158. this.get_label('enigma.keyexportprompt'),
  159. this.get_label('enigma.exportkeys'),
  160. [{
  161. text: this.get_label('enigma.onlypubkeys'),
  162. click: function(e) {
  163. rcmail.enigma_export_submit(args);
  164. $(this).remove();
  165. }
  166. },
  167. {
  168. text: this.get_label('enigma.withprivkeys'),
  169. click: function(e) {
  170. args._priv = 1;
  171. rcmail.enigma_export_submit(args);
  172. $(this).remove();
  173. }
  174. }],
  175. {width: 400}
  176. );
  177. this.enigma_export_submit(args);
  178. };
  179. // Sumbitting request for key(s) export
  180. // Done this way to handle password input
  181. rcube_webmail.prototype.enigma_export_submit = function(data)
  182. {
  183. var id = 'keyexport-' + new Date().getTime(),
  184. form = $('<form>').attr({target: id, method: 'post', style: 'display:none',
  185. action: '?_action=plugin.enigmakeys&_task=settings&_a=export'}),
  186. iframe = $('<iframe>').attr({name: id, style: 'display:none'})
  187. form.append($('<input>').attr({name: '_token', value: this.env.request_token}));
  188. $.each(data, function(i, v) {
  189. form.append($('<input>').attr({name: i, value: v}));
  190. });
  191. iframe.appendTo(document.body);
  192. form.appendTo(document.body).submit();
  193. };
  194. // Submit key(s) import form
  195. rcube_webmail.prototype.enigma_import = function()
  196. {
  197. var form, file, lock,
  198. id = 'keyexport-' + new Date().getTime(),
  199. iframe = $('<iframe>').attr({name: id, style: 'display:none'});
  200. if (form = this.gui_objects.importform) {
  201. file = document.getElementById('rcmimportfile');
  202. if (file && !file.value) {
  203. alert(this.get_label('selectimportfile'));
  204. return;
  205. }
  206. lock = this.set_busy(true, 'importwait');
  207. iframe.appendTo(document.body);
  208. $(form).attr({target: id, action: this.add_url(form.action, '_unlock', lock)})
  209. .submit();
  210. }
  211. };
  212. // Ssearch for key(s) for import
  213. rcube_webmail.prototype.enigma_import_search = function()
  214. {
  215. var form, search;
  216. if (form = this.gui_objects.importform) {
  217. search = $('#rcmimportsearch').val();
  218. if (!search) {
  219. return;
  220. }
  221. this.enigma_find_publickey(search);
  222. }
  223. };
  224. // list row selection handler
  225. rcube_webmail.prototype.enigma_keylist_select = function(list)
  226. {
  227. var id = list.get_single_selection(), url;
  228. if (id)
  229. url = '&_action=plugin.enigmakeys&_a=info&_id=' + id;
  230. this.enigma_loadframe(url);
  231. this.enable_command('plugin.enigma-key-delete', 'plugin.enigma-key-export-selected', list.selection.length > 0);
  232. };
  233. rcube_webmail.prototype.enigma_keylist_keypress = function(list)
  234. {
  235. if (list.modkey == CONTROL_KEY)
  236. return;
  237. if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
  238. this.command('plugin.enigma-key-delete');
  239. else if (list.key_pressed == 33)
  240. this.command('previouspage');
  241. else if (list.key_pressed == 34)
  242. this.command('nextpage');
  243. };
  244. // load key frame
  245. rcube_webmail.prototype.enigma_loadframe = function(url)
  246. {
  247. var frm, win;
  248. if (this.env.contentframe && window.frames && (frm = window.frames[this.env.contentframe])) {
  249. if (!url && (win = window.frames[this.env.contentframe])) {
  250. if (win.location && win.location.href.indexOf(this.env.blankpage) < 0)
  251. win.location.href = this.env.blankpage;
  252. if (this.env.frame_lock)
  253. this.set_busy(false, null, this.env.frame_lock);
  254. return;
  255. }
  256. this.env.frame_lock = this.set_busy(true, 'loading');
  257. frm.location.href = this.env.comm_path + '&_framed=1&' + url;
  258. }
  259. };
  260. // Search keys/certs
  261. rcube_webmail.prototype.enigma_search = function(props)
  262. {
  263. if (!props && this.gui_objects.qsearchbox)
  264. props = this.gui_objects.qsearchbox.value;
  265. if (props || this.env.search_request) {
  266. var params = {'_a': 'search', '_q': props},
  267. lock = this.set_busy(true, 'searching');
  268. // if (this.gui_objects.search_filter)
  269. // addurl += '&_filter=' + this.gui_objects.search_filter.value;
  270. this.env.current_page = 1;
  271. this.enigma_loadframe();
  272. this.enigma_clear_list();
  273. this.http_post('plugin.enigmakeys', params, lock);
  274. }
  275. return false;
  276. };
  277. // Reset search filter and the list
  278. rcube_webmail.prototype.enigma_search_reset = function(props)
  279. {
  280. var s = this.env.search_request;
  281. this.reset_qsearch();
  282. if (s) {
  283. this.enigma_loadframe();
  284. this.enigma_clear_list();
  285. // refresh the list
  286. this.enigma_list();
  287. }
  288. return false;
  289. };
  290. // Keys/certs listing
  291. rcube_webmail.prototype.enigma_list = function(page, reset_frame)
  292. {
  293. if (this.is_framed())
  294. return parent.rcmail.enigma_list(page, reset_frame);
  295. var params = {'_a': 'list'},
  296. lock = this.set_busy(true, 'loading');
  297. this.env.current_page = page ? page : 1;
  298. if (this.env.search_request)
  299. params._q = this.env.search_request;
  300. if (page)
  301. params._p = page;
  302. this.enigma_clear_list(reset_frame);
  303. this.http_post('plugin.enigmakeys', params, lock);
  304. };
  305. // Change list page
  306. rcube_webmail.prototype.enigma_list_page = function(page)
  307. {
  308. if (page == 'next')
  309. page = this.env.current_page + 1;
  310. else if (page == 'last')
  311. page = this.env.pagecount;
  312. else if (page == 'prev' && this.env.current_page > 1)
  313. page = this.env.current_page - 1;
  314. else if (page == 'first' && this.env.current_page > 1)
  315. page = 1;
  316. this.enigma_list(page);
  317. };
  318. // Remove list rows
  319. rcube_webmail.prototype.enigma_clear_list = function(reset_frame)
  320. {
  321. if (reset_frame !== false)
  322. this.enigma_loadframe();
  323. if (this.keys_list)
  324. this.keys_list.clear(true);
  325. this.enable_command('plugin.enigma-key-delete', 'plugin.enigma-key-delete-selected', false);
  326. };
  327. // Adds a row to the list
  328. rcube_webmail.prototype.enigma_add_list_row = function(r)
  329. {
  330. if (!this.gui_objects.keyslist || !this.keys_list)
  331. return false;
  332. var list = this.keys_list,
  333. tbody = this.gui_objects.keyslist.tBodies[0],
  334. rowcount = tbody.rows.length,
  335. even = rowcount%2,
  336. css_class = 'message'
  337. + (even ? ' even' : ' odd'),
  338. // for performance use DOM instead of jQuery here
  339. row = document.createElement('tr'),
  340. col = document.createElement('td');
  341. row.id = 'rcmrow' + r.id;
  342. row.className = css_class;
  343. if (r.flags) $(row).data('flags', r.flags);
  344. col.innerHTML = r.name;
  345. row.appendChild(col);
  346. list.insert_row(row);
  347. };
  348. /*********************************************************/
  349. /********* Enigma Message methods *********/
  350. /*********************************************************/
  351. // handle message send/save action
  352. rcube_webmail.prototype.enigma_beforesend_handler = function(props)
  353. {
  354. this.env.last_action = 'send';
  355. this.enigma_compose_handler(props);
  356. };
  357. rcube_webmail.prototype.enigma_beforesavedraft_handler = function(props)
  358. {
  359. this.env.last_action = 'savedraft';
  360. this.enigma_compose_handler(props);
  361. };
  362. rcube_webmail.prototype.enigma_compose_handler = function(props)
  363. {
  364. var form = this.gui_objects.messageform;
  365. // copy inputs from enigma menu to the form
  366. $('#enigmamenu input').each(function() {
  367. var id = this.id + '_cpy', input = $('#' + id);
  368. if (!input.length) {
  369. input = $(this).clone();
  370. input.prop({id: id, type: 'hidden'}).appendTo(form);
  371. }
  372. input.val(this.checked ? '1' : '');
  373. });
  374. // disable signing when saving drafts
  375. if (this.env.last_action == 'savedraft') {
  376. $('input[name="_enigma_sign"]', form).val(0);
  377. }
  378. };
  379. // Import attached keys/certs file
  380. rcube_webmail.prototype.enigma_import_attachment = function(mime_id)
  381. {
  382. var lock = this.set_busy(true, 'loading'),
  383. post = {_uid: this.env.uid, _mbox: this.env.mailbox, _part: mime_id};
  384. this.http_post('plugin.enigmaimport', post, lock);
  385. return false;
  386. };
  387. // password request popup
  388. rcube_webmail.prototype.enigma_password_request = function(data)
  389. {
  390. if (!data || !data.keyid) {
  391. return;
  392. }
  393. var ref = this,
  394. msg = this.get_label('enigma.enterkeypass'),
  395. myprompt = $('<div class="prompt">'),
  396. myprompt_content = $('<div class="message">')
  397. .appendTo(myprompt),
  398. myprompt_input = $('<input>').attr({type: 'password', size: 30})
  399. .keypress(function(e) {
  400. if (e.which == 13)
  401. (ref.is_framed() ? window.parent.$ : $)('.ui-dialog-buttonpane button.mainaction:visible').click();
  402. })
  403. .appendTo(myprompt);
  404. data.key = data.keyid;
  405. if (data.keyid.length > 8)
  406. data.keyid = data.keyid.substr(data.keyid.length - 8);
  407. $.each(['keyid', 'user'], function() {
  408. msg = msg.replace('$' + this, data[this]);
  409. });
  410. myprompt_content.text(msg);
  411. this.show_popup_dialog(myprompt, this.get_label('enigma.enterkeypasstitle'),
  412. [{
  413. text: this.get_label('save'),
  414. 'class': 'mainaction',
  415. click: function(e) {
  416. e.stopPropagation();
  417. var jq = ref.is_framed() ? window.parent.$ : $;
  418. data.password = myprompt_input.val();
  419. if (!data.password) {
  420. myprompt_input.focus();
  421. return;
  422. }
  423. ref.enigma_password_submit(data);
  424. jq(this).remove();
  425. }
  426. },
  427. {
  428. text: this.get_label('cancel'),
  429. click: function(e) {
  430. var jq = ref.is_framed() ? window.parent.$ : $;
  431. e.stopPropagation();
  432. jq(this).remove();
  433. }
  434. }], {width: 400});
  435. if (this.is_framed() && parent.rcmail.message_list) {
  436. // this fixes bug when pressing Enter on "Save" button in the dialog
  437. parent.rcmail.message_list.blur();
  438. }
  439. };
  440. // submit entered password
  441. rcube_webmail.prototype.enigma_password_submit = function(data)
  442. {
  443. var lock, form;
  444. if (this.env.action == 'compose' && !data['compose-init']) {
  445. return this.enigma_password_compose_submit(data);
  446. }
  447. else if (this.env.action == 'plugin.enigmakeys' && (form = this.gui_objects.importform)) {
  448. if (!$('input[name="_keyid"]', form).length) {
  449. $(form).append($('<input>').attr({type: 'hidden', name: '_keyid', value: data.key}))
  450. .append($('<input>').attr({type: 'hidden', name: '_passwd', value: data.password}))
  451. }
  452. return this.enigma_import();
  453. }
  454. lock = data.nolock ? null : this.set_busy(true, 'loading');
  455. form = $('<form>')
  456. .attr({method: 'post', action: data.action || location.href, style: 'display:none'})
  457. .append($('<input>').attr({type: 'hidden', name: '_keyid', value: data.key}))
  458. .append($('<input>').attr({type: 'hidden', name: '_passwd', value: data.password}))
  459. .append($('<input>').attr({type: 'hidden', name: '_token', value: this.env.request_token}))
  460. .append($('<input>').attr({type: 'hidden', name: '_unlock', value: lock}));
  461. // Additional form fields for request parameters
  462. $.each(data, function(i, v) {
  463. if (i.indexOf('input') == 0)
  464. form.append($('<input>').attr({type: 'hidden', name: i.substring(5), value: v}))
  465. });
  466. if (data.iframe) {
  467. var name = 'enigma_frame_' + (new Date()).getTime(),
  468. frame = $('<iframe>').attr({style: 'display:none', name: name}).appendTo(document.body);
  469. form.attr('target', name);
  470. }
  471. form.appendTo(document.body).submit();
  472. };
  473. // submit entered password - in mail compose page
  474. rcube_webmail.prototype.enigma_password_compose_submit = function(data)
  475. {
  476. var form = this.gui_objects.messageform;
  477. if (!$('input[name="_keyid"]', form).length) {
  478. $(form).append($('<input>').attr({type: 'hidden', name: '_keyid', value: data.key}))
  479. .append($('<input>').attr({type: 'hidden', name: '_passwd', value: data.password}));
  480. }
  481. else {
  482. $('input[name="_keyid"]', form).val(data.key);
  483. $('input[name="_passwd"]', form).val(data.password);
  484. }
  485. this.submit_messageform(this.env.last_action == 'savedraft');
  486. };
  487. // Display no-key error with key search button
  488. rcube_webmail.prototype.enigma_key_not_found = function(data)
  489. {
  490. return this.show_popup_dialog(
  491. data.text,
  492. data.title,
  493. [{
  494. text: data.button,
  495. click: function(e) {
  496. $(this).remove();
  497. rcmail.enigma_find_publickey(data.email);
  498. }
  499. }],
  500. {width: 400, dialogClass: 'error'}
  501. );
  502. };
  503. // Search for a public key on the key server
  504. rcube_webmail.prototype.enigma_find_publickey = function(email)
  505. {
  506. this.mailvelope_search_pubkeys([email],
  507. function(status) {},
  508. function(key) {
  509. var lock = rcmail.set_busy(true, 'enigma.importwait'),
  510. post = {_a: 'import', _keys: key};
  511. if (rcmail.env.action == 'plugin.enigmakeys')
  512. post._refresh = 1;
  513. // send request to server
  514. rcmail.http_post('plugin.enigmakeys', post, lock);
  515. }
  516. );
  517. };