From 1c4ce1dfb23d75ef7484e6ea0920b1322a7343d2 Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Fri, 21 Mar 2014 03:47:13 +0400 Subject: [PATCH] OpenPGP (Compose) (#53) UNSTABLE --- dev/Boots/RainLoopApp.js | 27 +- dev/Common/NewHtmlEditorWrapper.js | 3 +- dev/Models/OpenPgpKeyModel.js | 5 +- dev/Settings/OpenPGP.js | 14 +- dev/Storages/WebMailData.js | 14 +- .../PopupsComposeOpenPgpViewModel.js | 203 +++++++-- dev/ViewModels/PopupsComposeViewModel.js | 7 +- .../templates/Views/PopupsComposeOpenPgp.html | 2 +- rainloop/v/0.0.0/static/css/app.css | 18 +- rainloop/v/0.0.0/static/js/admin.js | 104 ++--- rainloop/v/0.0.0/static/js/admin.min.js | 6 +- rainloop/v/0.0.0/static/js/app.js | 431 ++++++++++++------ rainloop/v/0.0.0/static/js/app.min.js | 16 +- rainloop/v/0.0.0/static/js/libs.js | 102 ++--- 14 files changed, 631 insertions(+), 321 deletions(-) diff --git a/dev/Boots/RainLoopApp.js b/dev/Boots/RainLoopApp.js index 5797a33ae..119b778f3 100644 --- a/dev/Boots/RainLoopApp.js +++ b/dev/Boots/RainLoopApp.js @@ -328,6 +328,7 @@ RainLoopApp.prototype.reloadOpenPgpKeys = function () { var aKeys = [], + oEmail = new EmailModel(), oOpenpgpKeyring = RL.data().openpgpKeyring, oOpenpgpKeys = oOpenpgpKeyring ? oOpenpgpKeyring.keys : [] ; @@ -335,20 +336,28 @@ RainLoopApp.prototype.reloadOpenPgpKeys = function () _.each(oOpenpgpKeys, function (oItem, iIndex) { if (oItem && oItem.primaryKey) { - var + var + oPrimaryUser = oItem.getPrimaryUser(), sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid : (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '') ; - aKeys.push(new OpenPgpKeyModel( - iIndex, - oItem.primaryKey.getFingerprint(), - oItem.primaryKey.getKeyId().toHex().toLowerCase(), - sUser, - oItem.isPrivate(), - oItem.armor()) - ); + oEmail.clear(); + oEmail.mailsoParse(sUser); + + if (oEmail.validate()) + { + aKeys.push(new OpenPgpKeyModel( + iIndex, + oItem.primaryKey.getFingerprint(), + oItem.primaryKey.getKeyId().toHex().toLowerCase(), + sUser, + oEmail.email, + oItem.isPrivate(), + oItem.armor()) + ); + } } }); diff --git a/dev/Common/NewHtmlEditorWrapper.js b/dev/Common/NewHtmlEditorWrapper.js index 0e97131b6..0c36fd542 100644 --- a/dev/Common/NewHtmlEditorWrapper.js +++ b/dev/Common/NewHtmlEditorWrapper.js @@ -182,6 +182,7 @@ NewHtmlEditorWrapper.prototype.init = function () self.editor.setKeystroke(window.CKEDITOR.CTRL + 65/* A */, 'selectAll'); self.fOnReady(); + self.__resizable = true; self.resize(); }); } @@ -206,7 +207,7 @@ NewHtmlEditorWrapper.prototype.blur = function () NewHtmlEditorWrapper.prototype.resize = function () { - if (this.editor) + if (this.editor && this.__resizable) { this.editor.resize(this.$element.width(), this.$element.innerHeight()); } diff --git a/dev/Models/OpenPgpKeyModel.js b/dev/Models/OpenPgpKeyModel.js index 5dd8e803f..7c1bc0096 100644 --- a/dev/Models/OpenPgpKeyModel.js +++ b/dev/Models/OpenPgpKeyModel.js @@ -5,16 +5,18 @@ * @param {string} sGuID * @param {string} sID * @param {string} sUserID + * @param {string} sEmail * @param {boolean} bIsPrivate * @param {string} sArmor * @constructor */ -function OpenPgpKeyModel(iIndex, sGuID, sID, sUserID, bIsPrivate, sArmor) +function OpenPgpKeyModel(iIndex, sGuID, sID, sUserID, sEmail, bIsPrivate, sArmor) { this.index = iIndex; this.id = sID; this.guid = sGuID; this.user = sUserID; + this.email = sEmail; this.armor = sArmor; this.isPrivate = !!bIsPrivate; @@ -25,5 +27,6 @@ OpenPgpKeyModel.prototype.index = 0; OpenPgpKeyModel.prototype.id = ''; OpenPgpKeyModel.prototype.guid = ''; OpenPgpKeyModel.prototype.user = ''; +OpenPgpKeyModel.prototype.email = ''; OpenPgpKeyModel.prototype.armor = ''; OpenPgpKeyModel.prototype.isPrivate = false; diff --git a/dev/Settings/OpenPGP.js b/dev/Settings/OpenPGP.js index 99f56b7c3..2071d98f9 100644 --- a/dev/Settings/OpenPGP.js +++ b/dev/Settings/OpenPGP.js @@ -6,18 +6,8 @@ function SettingsOpenPGP() { this.openpgpkeys = RL.data().openpgpkeys; - - this.openpgpkeysPublic = ko.computed(function () { - return _.filter(this.openpgpkeys(), function (oItem) { - return !!(oItem && !oItem.isPrivate); - }); - }, this); - - this.openpgpkeysPrivate = ko.computed(function () { - return _.filter(this.openpgpkeys(), function (oItem) { - return !!(oItem && oItem.isPrivate); - }); - }, this); + this.openpgpkeysPublic = RL.data().openpgpkeysPublic; + this.openpgpkeysPrivate = RL.data().openpgpkeysPrivate; this.openPgpKeyForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [this, function (oPrev) { diff --git a/dev/Storages/WebMailData.js b/dev/Storages/WebMailData.js index e6dd57e24..5620febed 100644 --- a/dev/Storages/WebMailData.js +++ b/dev/Storages/WebMailData.js @@ -98,7 +98,6 @@ function WebMailDataStorage() return bLoading || bCreating || bDeleting || bRenaming; }, this); - this.foldersInboxUnreadCount = ko.observable(0); this.currentFolder = ko.observable(null).extend({'toggleSubscribe': [null, @@ -348,6 +347,18 @@ function WebMailDataStorage() this.openpgpkeys = ko.observableArray([]); this.openpgpKeyring = null; + this.openpgpkeysPublic = ko.computed(function () { + return _.filter(this.openpgpkeys(), function (oItem) { + return !!(oItem && !oItem.isPrivate); + }); + }, this); + + this.openpgpkeysPrivate = ko.computed(function () { + return _.filter(this.openpgpkeys(), function (oItem) { + return !!(oItem && oItem.isPrivate); + }); + }, this); + // google this.googleActions = ko.observable(false); this.googleLoggined = ko.observable(false); @@ -716,7 +727,6 @@ WebMailDataStorage.prototype.getNextFolderNames = function (bBoot) }; /** - * @param {Function} fCallback * @param {string} sFromFolderFullNameRaw * @param {Array} aUidForRemove * @param {string=} sToFolderFullNameRaw = '' diff --git a/dev/ViewModels/PopupsComposeOpenPgpViewModel.js b/dev/ViewModels/PopupsComposeOpenPgpViewModel.js index 2033a7ec6..8fcd209bf 100644 --- a/dev/ViewModels/PopupsComposeOpenPgpViewModel.js +++ b/dev/ViewModels/PopupsComposeOpenPgpViewModel.js @@ -16,13 +16,168 @@ function PopupsComposeOpenPgpViewModel() this.password = ko.observable(''); this.password.focus = ko.observable(true); + this.from = ko.observable(''); + this.to = ko.observableArray([]); + this.text = ko.observable(''); + + this.resultCallback = null; + + this.submitRequest = ko.observable(false); + // commands this.doCommand = Utils.createCommand(this, function () { - - this.cancelCommand(); + + var + self = this, + bResult = true, + aOpenpgpkeysPublic = RL.data().openpgpkeysPublic(), + oKey = null, + oPrivateKey = null, + aPublicKeys = [], + fFindPublicKey = function (sEmail) { + + var + oResult = null, + oKey = _.find(aOpenpgpkeysPublic, function (oItem) { + return oItem && sEmail === oItem.email; + }) + ; + + if (oKey) + { + try + { + oResult = window.openpgp.key.readArmored(oKey.armor); + if (oResult && !oResult.err && oResult.keys && oResult.keys[0]) + { + oResult = oResult.keys[0]; + } + else + { + oResult = null; + } + } + catch (e) + { + oResult = null; + } + } + + return oResult; + } + ; + + this.submitRequest(true); + + if (bResult && this.sign() && '' === this.from()) + { + this.notification('Please specify From email address'); + bResult = false; + } + + if (bResult && this.sign()) + { + oKey = _.find(RL.data().openpgpkeysPrivate(), function (oItem) { + return oItem && self.from() === oItem.email; + }); + + if (oKey) + { + try + { + oPrivateKey = window.openpgp.key.readArmored(oKey.armor); + if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0]) + { + oPrivateKey = oPrivateKey.keys[0]; + oPrivateKey.decrypt(this.password()); + } + else + { + oPrivateKey = null; + } + } + catch (e) + { + oPrivateKey = null; + } + } + + if (!oPrivateKey) + { + this.notification('No private key found for "' + this.from() + '" email'); + bResult = false; + } + } + + if (bResult && this.encrypt() && 0 === this.to().length) + { + this.notification('Please specify at least one recipient'); + bResult = false; + } + + if (bResult && this.encrypt()) + { + aPublicKeys = _.compact(_.map(this.to(), function (sEmail) { + var oKey = fFindPublicKey(sEmail); + if (!oKey && bResult) + { + self.notification('No public key found for "' + sEmail + '" email'); + bResult = false; + } + + return oKey; + + })); + + if (0 === aPublicKeys.length || this.to().length !== aPublicKeys.length) + { + bResult = false; + } + } + + _.delay(function () { + + if (self.resultCallback && bResult) + { + try { + + if (oPrivateKey && 0 === aPublicKeys.length) + { + self.resultCallback( + window.openpgp.signClearMessage([oPrivateKey], self.text()) + ); + } + else if (oPrivateKey && 0 < aPublicKeys.length) + { + self.resultCallback( + window.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text()) + ); + } + else if (!oPrivateKey && 0 < aPublicKeys.length) + { + self.resultCallback( + window.openpgp.encryptMessage(aPublicKeys, self.text()) + ); + } + } + catch (e) + { + self.notification('OpenPGP error: ' + e); + bResult = false; + } + } + + if (bResult) + { + self.cancelCommand(); + } + + self.submitRequest(false); + + }, 10); }, function () { - return '' === this.notification(); + return !this.submitRequest() && (this.sign() || this.encrypt()); }); Knoin.constructorEnd(this); @@ -36,6 +191,14 @@ PopupsComposeOpenPgpViewModel.prototype.clearPopup = function () this.password(''); this.password.focus(false); + + this.from(''); + this.to([]); + this.text(''); + + this.submitRequest(false); + + this.resultCallback = null; }; PopupsComposeOpenPgpViewModel.prototype.onHide = function () @@ -53,20 +216,11 @@ PopupsComposeOpenPgpViewModel.prototype.onShow = function (fCallback, sText, sFr aRec = [] ; - if ('' === sTo + sCc + sBcc) - { - this.notification('Please specify at least one recipient'); - return false; - } + this.resultCallback = fCallback; oEmail.clear(); oEmail.mailsoParse(sFromEmail); - if ('' === oEmail.email) - { - this.notification('Please specify From email address'); - return false; - } - else + if ('' !== oEmail.email) { sResultFromEmail = oEmail.email; } @@ -93,22 +247,7 @@ PopupsComposeOpenPgpViewModel.prototype.onShow = function (fCallback, sText, sFr return '' === oEmail.email ? false : oEmail.email; })); - if (0 === aRec.length) - { - this.notification('Please specify at least one recipient'); - return false; - } - - window.console.log(sResultFromEmail); - window.console.log(aRec); - - // TODO -}; - -PopupsComposeOpenPgpViewModel.prototype.onFocus = function () -{ - if (this.sign()) - { - this.password.focus(true); - } + this.from(sResultFromEmail); + this.to(aRec); + this.text(sText); }; diff --git a/dev/ViewModels/PopupsComposeViewModel.js b/dev/ViewModels/PopupsComposeViewModel.js index ae94b28a5..4fbabffef 100644 --- a/dev/ViewModels/PopupsComposeViewModel.js +++ b/dev/ViewModels/PopupsComposeViewModel.js @@ -362,9 +362,12 @@ PopupsComposeViewModel.prototype.openOpenPgpPopup = function () { if (this.allowOpenPGP() && this.oEditor && !this.oEditor.isHtml()) { + var self = this; kn.showScreenPopup(PopupsComposeOpenPgpViewModel, [ - function () { - + function (sResult) { + self.editor(function (oEditor) { + oEditor.setPlain(sResult); + }); }, this.oEditor.getData(), this.currentIdentityResultEmail(), diff --git a/rainloop/v/0.0.0/app/templates/Views/PopupsComposeOpenPgp.html b/rainloop/v/0.0.0/app/templates/Views/PopupsComposeOpenPgp.html index 9b910af93..15f84e3fa 100644 --- a/rainloop/v/0.0.0/app/templates/Views/PopupsComposeOpenPgp.html +++ b/rainloop/v/0.0.0/app/templates/Views/PopupsComposeOpenPgp.html @@ -36,7 +36,7 @@