mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-27 19:19:21 +03:00
Revamp PGP management due to implementing Mailvelop and PEAR Crypt_GPG
This commit is contained in:
parent
43a1196dbb
commit
a47397ef09
16 changed files with 885 additions and 633 deletions
|
|
@ -72,7 +72,7 @@ class AddOpenPgpKeyPopupView extends AbstractViewPopup {
|
|||
|
||||
openpgpKeyring.store();
|
||||
|
||||
rl.app.reloadOpenPgpKeys();
|
||||
PgpUserStore.reloadOpenPgpKeys();
|
||||
|
||||
if (this.keyError()) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { MessageUserStore } from 'Stores/User/Message';
|
|||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
||||
import { EmailModel } from 'Model/Email';
|
||||
|
||||
import { decorateKoCommands, isPopupVisible, showScreenPopup } from 'Knoin/Knoin';
|
||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||
|
|
@ -41,7 +42,6 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
|||
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
||||
import { AskPopupView } from 'View/Popup/Ask';
|
||||
import { ContactsPopupView } from 'View/Popup/Contacts';
|
||||
import { ComposeOpenPgpPopupView } from 'View/Popup/ComposeOpenPgp';
|
||||
|
||||
import { ThemeStore } from 'Stores/Theme';
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
|
||||
this.bSkipNextHide = false;
|
||||
|
||||
this.capaOpenPGP = !!PgpUserStore.openpgp;
|
||||
this.capaOpenPGP = PgpUserStore.isSupported();
|
||||
|
||||
this.identities = IdentityUserStore;
|
||||
|
||||
|
|
@ -169,6 +169,11 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
showBcc: false,
|
||||
showReplyTo: false,
|
||||
|
||||
pgpSign: false,
|
||||
pgpEncrypt: false,
|
||||
canPgpSign: false,
|
||||
canPgpEncrypt: false,
|
||||
|
||||
draftsFolder: '',
|
||||
draftUid: 0,
|
||||
sending: false,
|
||||
|
|
@ -265,7 +270,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
},
|
||||
|
||||
canBeSentOrSaved: () => !this.sending() && !this.saving()
|
||||
|
||||
});
|
||||
|
||||
this.addSubscribables({
|
||||
|
|
@ -275,16 +279,26 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
|
||||
sendSuccessButSaveError: value => !value && this.savedErrorDesc(''),
|
||||
|
||||
currentIdentity: value => {
|
||||
this.canPgpSign(false);
|
||||
value && PgpUserStore.hasPrivateKeyForEmail(value.email()).then(result => {
|
||||
console.log({canPgpSign:result});
|
||||
this.canPgpSign(result)
|
||||
});
|
||||
},
|
||||
|
||||
cc: value => {
|
||||
if (false === this.showCc() && value.length) {
|
||||
this.showCc(true);
|
||||
}
|
||||
this.initPgpEncrypt();
|
||||
},
|
||||
|
||||
bcc: value => {
|
||||
if (false === this.showBcc() && value.length) {
|
||||
this.showBcc(true);
|
||||
}
|
||||
this.initPgpEncrypt();
|
||||
},
|
||||
|
||||
replyTo: value => {
|
||||
|
|
@ -303,6 +317,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
if (this.emptyToError() && value.length) {
|
||||
this.emptyToError(false);
|
||||
}
|
||||
this.initPgpEncrypt();
|
||||
},
|
||||
|
||||
attachmentsInProcess: value => {
|
||||
|
|
@ -551,19 +566,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
rl.app.getAutocomplete(oData.term, aData => fResponse(aData.map(oEmailItem => oEmailItem.toLine(false))));
|
||||
}
|
||||
|
||||
openOpenPgpPopup() {
|
||||
if (PgpUserStore.openpgp && !this.oEditor.isHtml()) {
|
||||
showScreenPopup(ComposeOpenPgpPopupView, [
|
||||
result => this.editor(editor => editor.setPlain(result)),
|
||||
this.oEditor.getData(false),
|
||||
this.currentIdentity(),
|
||||
this.to(),
|
||||
this.cc(),
|
||||
this.bcc()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
reloadDraftFolder() {
|
||||
const draftsFolder = FolderUserStore.draftsFolder();
|
||||
if (draftsFolder && UNUSED_OPTION_VALUE !== draftsFolder) {
|
||||
|
|
@ -1419,6 +1421,9 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
this.showBcc(false);
|
||||
this.showReplyTo(false);
|
||||
|
||||
this.pgpSign(false);
|
||||
this.pgpEncrypt(false);
|
||||
|
||||
delegateRunOnDestroy(this.attachments());
|
||||
this.attachments([]);
|
||||
|
||||
|
|
@ -1442,6 +1447,31 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
item => item.id
|
||||
);
|
||||
}
|
||||
|
||||
initPgpEncrypt() {
|
||||
const email = new EmailModel();
|
||||
return PgpUserStore.hasPublicKeyForEmails([
|
||||
// this.currentIdentity.email(),
|
||||
this.to(),
|
||||
this.cc(),
|
||||
this.bcc()
|
||||
].join(',').split(',').map(value => {
|
||||
email.clear();
|
||||
email.parse(value.trim());
|
||||
return email.email || false;
|
||||
}).filter(v => v), 1).then(result => {
|
||||
console.log({canPgpEncrypt:result});
|
||||
this.canPgpEncrypt(result);
|
||||
});
|
||||
}
|
||||
|
||||
togglePgpSign() {
|
||||
this.pgpSign(!this.pgpSign()/* && this.canPgpSign()*/);
|
||||
}
|
||||
|
||||
togglePgpEncrypt() {
|
||||
this.pgpEncrypt(!this.pgpEncrypt()/* && this.canPgpEncrypt()*/);
|
||||
}
|
||||
}
|
||||
|
||||
export { ComposePopupView, ComposePopupView as default };
|
||||
|
|
|
|||
|
|
@ -1,385 +0,0 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { pString, defaultOptionsAfterRender } from 'Common/Utils';
|
||||
|
||||
import { Scope } from 'Common/Enums';
|
||||
import { i18n } from 'Common/Translator';
|
||||
|
||||
import { PgpUserStore } from 'Stores/User/Pgp';
|
||||
|
||||
import { EmailModel } from 'Model/Email';
|
||||
|
||||
import { decorateKoCommands } from 'Knoin/Knoin';
|
||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||
|
||||
const KEY_NAME_SUBSTR = -8,
|
||||
i18nPGP = (key, params) => i18n('PGP_NOTIFICATIONS/' + key, params);
|
||||
|
||||
class ComposeOpenPgpPopupView extends AbstractViewPopup {
|
||||
constructor() {
|
||||
super('ComposeOpenPgp');
|
||||
|
||||
this.publicKeysOptionsCaption = i18nPGP('ADD_A_PUBLICK_KEY');
|
||||
this.privateKeysOptionsCaption = i18nPGP('SELECT_A_PRIVATE_KEY');
|
||||
|
||||
this.addObservables({
|
||||
notification: '',
|
||||
|
||||
sign: false,
|
||||
encrypt: false,
|
||||
|
||||
password: '',
|
||||
|
||||
text: '',
|
||||
selectedPrivateKey: null,
|
||||
selectedPublicKey: null,
|
||||
|
||||
signKey: null,
|
||||
|
||||
submitRequest: false
|
||||
});
|
||||
this.encryptKeys = ko.observableArray();
|
||||
|
||||
this.addComputables({
|
||||
encryptKeysView: () => this.encryptKeys.map(oKey => (oKey ? oKey.key : null)).filter(v => v),
|
||||
|
||||
privateKeysOptions: () => {
|
||||
const opts = PgpUserStore.openpgpkeysPrivate().map(oKey => {
|
||||
if (this.signKey() && this.signKey().key.id === oKey.id) {
|
||||
return null;
|
||||
}
|
||||
return oKey.users.map(user => ({
|
||||
id: oKey.guid,
|
||||
name: '(' + oKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
|
||||
key: oKey
|
||||
}));
|
||||
});
|
||||
|
||||
return opts.flat().filter(v => v);
|
||||
},
|
||||
|
||||
publicKeysOptions: () => {
|
||||
const opts = PgpUserStore.openpgpkeysPublic().map(oKey => {
|
||||
if (this.encryptKeysView().includes(oKey)) {
|
||||
return null;
|
||||
}
|
||||
return oKey.users.map(user => ({
|
||||
id: oKey.guid,
|
||||
name: '(' + oKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
|
||||
key: oKey
|
||||
}));
|
||||
});
|
||||
return opts.flat().filter(v => v);
|
||||
}
|
||||
});
|
||||
|
||||
this.resultCallback = null;
|
||||
|
||||
this.selectedPrivateKey.subscribe((value) => {
|
||||
if (value) {
|
||||
this.selectCommand();
|
||||
this.updateCommand();
|
||||
}
|
||||
});
|
||||
|
||||
this.selectedPublicKey.subscribe((value) => {
|
||||
if (value) {
|
||||
this.addCommand();
|
||||
}
|
||||
});
|
||||
|
||||
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
||||
|
||||
this.deletePublickKey = this.deletePublickKey.bind(this);
|
||||
|
||||
decorateKoCommands(this, {
|
||||
doCommand: self => !self.submitRequest() && (self.sign() || self.encrypt()),
|
||||
selectCommand: 1,
|
||||
addCommand: 1,
|
||||
updateCommand: 1,
|
||||
});
|
||||
}
|
||||
|
||||
doCommand() {
|
||||
let result = true,
|
||||
privateKey = null,
|
||||
aPublicKeys = [];
|
||||
|
||||
this.submitRequest(true);
|
||||
|
||||
if (result && this.sign()) {
|
||||
if (!this.signKey()) {
|
||||
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
|
||||
result = false;
|
||||
} else if (!this.signKey().key) {
|
||||
this.notification(
|
||||
i18nPGP('NO_PRIVATE_KEY_FOUND_FOR', {
|
||||
EMAIL: this.signKey().email
|
||||
})
|
||||
);
|
||||
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
const privateKeys = this.signKey().key.getNativeKeys();
|
||||
privateKey = privateKeys[0] || null;
|
||||
|
||||
try {
|
||||
if (privateKey) {
|
||||
privateKey.decrypt(pString(this.password()));
|
||||
}
|
||||
} catch (e) {
|
||||
privateKey = null;
|
||||
}
|
||||
|
||||
if (!privateKey) {
|
||||
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result && this.encrypt()) {
|
||||
if (this.encryptKeys.length) {
|
||||
aPublicKeys = [];
|
||||
|
||||
this.encryptKeys.forEach(oKey => {
|
||||
if (oKey && oKey.key) {
|
||||
aPublicKeys = aPublicKeys.concat(oKey.key.getNativeKeys().flat(Infinity).filter(v => v));
|
||||
} else if (oKey && oKey.email) {
|
||||
this.notification(
|
||||
i18nPGP('NO_PUBLIC_KEYS_FOUND_FOR', {
|
||||
EMAIL: oKey.email
|
||||
})
|
||||
);
|
||||
|
||||
result = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (result && (!aPublicKeys.length || this.encryptKeys.length !== aPublicKeys.length)) {
|
||||
result = false;
|
||||
}
|
||||
} else {
|
||||
this.notification(i18nPGP('NO_PUBLIC_KEYS_FOUND'));
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (result && this.resultCallback) {
|
||||
setTimeout(() => {
|
||||
let pgpPromise = null;
|
||||
|
||||
try {
|
||||
if (aPublicKeys.length) {
|
||||
if (privateKey) {
|
||||
pgpPromise = PgpUserStore.openpgp.encrypt({
|
||||
data: this.text(),
|
||||
publicKeys: aPublicKeys,
|
||||
privateKeys: [privateKey]
|
||||
});
|
||||
} else {
|
||||
pgpPromise = PgpUserStore.openpgp.encrypt({
|
||||
data: this.text(),
|
||||
publicKeys: aPublicKeys
|
||||
});
|
||||
}
|
||||
} else if (privateKey) {
|
||||
pgpPromise = PgpUserStore.openpgp.sign({
|
||||
data: this.text(),
|
||||
privateKeys: [privateKey]
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
||||
this.notification(
|
||||
i18nPGP('PGP_ERROR', {
|
||||
ERROR: '' + e
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (pgpPromise) {
|
||||
try {
|
||||
pgpPromise
|
||||
.then((mData) => {
|
||||
this.resultCallback(mData.data);
|
||||
this.cancelCommand();
|
||||
})
|
||||
.catch((e) => {
|
||||
this.notification(
|
||||
i18nPGP('PGP_ERROR', {
|
||||
ERROR: '' + e
|
||||
})
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
this.notification(
|
||||
i18nPGP('PGP_ERROR', {ERROR: '' + e})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.submitRequest(false);
|
||||
}, 20);
|
||||
} else {
|
||||
this.submitRequest(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
selectCommand() {
|
||||
const keyId = this.selectedPrivateKey(),
|
||||
option = keyId ? this.privateKeysOptions().find(item => item && keyId === item.id) : null;
|
||||
|
||||
if (option) {
|
||||
this.signKey({
|
||||
empty: !option.key,
|
||||
selected: ko.observable(!!option.key),
|
||||
users: option.key.users,
|
||||
hash: option.key.id.slice(KEY_NAME_SUBSTR).toUpperCase(),
|
||||
key: option.key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addCommand() {
|
||||
const keyId = this.selectedPublicKey(),
|
||||
option = keyId ? this.publicKeysOptions().find(item => item && keyId === item.id) : null;
|
||||
|
||||
if (option) {
|
||||
this.encryptKeys.push({
|
||||
empty: !option.key,
|
||||
selected: ko.observable(!!option.key),
|
||||
removable: ko.observable(!this.sign() || !this.signKey() || this.signKey().key.id !== option.key.id),
|
||||
users: option.key.users,
|
||||
hash: option.key.id.slice(KEY_NAME_SUBSTR).toUpperCase(),
|
||||
key: option.key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateCommand() {
|
||||
this.encryptKeys.forEach(oKey =>
|
||||
oKey.removable(!this.sign() || !this.signKey() || this.signKey().key.id !== oKey.key.id)
|
||||
);
|
||||
}
|
||||
|
||||
deletePublickKey(publicKey) {
|
||||
this.encryptKeys.remove(publicKey);
|
||||
}
|
||||
|
||||
clearPopup() {
|
||||
this.notification('');
|
||||
|
||||
this.sign(false);
|
||||
this.encrypt(false);
|
||||
|
||||
this.password('');
|
||||
|
||||
this.signKey(null);
|
||||
this.encryptKeys([]);
|
||||
this.text('');
|
||||
|
||||
this.resultCallback = null;
|
||||
}
|
||||
|
||||
onBuild() {
|
||||
// shortcuts.add('tab', 'shift', Scope.ComposeOpenPgp, () => {
|
||||
shortcuts.add('tab', '', Scope.ComposeOpenPgp, () => {
|
||||
let btn = this.querySelector('.inputPassword');
|
||||
if (btn.matches(':focus')) {
|
||||
btn = this.querySelector('.buttonDo');
|
||||
}
|
||||
btn.focus();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
onHideWithDelay() {
|
||||
this.clearPopup();
|
||||
}
|
||||
|
||||
onShowWithDelay() {
|
||||
this.querySelector(this.sign() ? '.inputPassword' : '.buttonDo').focus();
|
||||
}
|
||||
|
||||
onShow(fCallback, sText, identity, sTo, sCc, sBcc) {
|
||||
this.clearPopup();
|
||||
|
||||
let rec = [],
|
||||
emailLine = '';
|
||||
|
||||
const email = new EmailModel();
|
||||
|
||||
this.resultCallback = fCallback;
|
||||
|
||||
if (sTo) {
|
||||
rec.push(sTo);
|
||||
}
|
||||
|
||||
if (sCc) {
|
||||
rec.push(sCc);
|
||||
}
|
||||
|
||||
if (sBcc) {
|
||||
rec.push(sBcc);
|
||||
}
|
||||
|
||||
rec = rec.join(', ').split(',');
|
||||
rec = rec.map(value => {
|
||||
email.clear();
|
||||
email.parse(value.trim());
|
||||
return email.email || false;
|
||||
}).filter(v => v);
|
||||
|
||||
if (identity && identity.email()) {
|
||||
emailLine = identity.email();
|
||||
rec.unshift(emailLine);
|
||||
|
||||
const keys = PgpUserStore.findAllPrivateKeysByEmailNotNative(emailLine);
|
||||
if (keys && keys[0]) {
|
||||
this.signKey({
|
||||
users: keys[0].users || [emailLine],
|
||||
hash: keys[0].id.slice(KEY_NAME_SUBSTR).toUpperCase(),
|
||||
key: keys[0]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.signKey()) {
|
||||
this.sign(true);
|
||||
}
|
||||
|
||||
if (rec.length) {
|
||||
this.encryptKeys(
|
||||
rec.map(recEmail => {
|
||||
const keys = PgpUserStore.findAllPublicKeysByEmailNotNative(recEmail);
|
||||
return keys
|
||||
? keys.map(publicKey => ({
|
||||
empty: !publicKey,
|
||||
selected: ko.observable(!!publicKey),
|
||||
removable: ko.observable(
|
||||
!this.sign() || !this.signKey() || this.signKey().key.id !== publicKey.id
|
||||
),
|
||||
users: publicKey ? publicKey.users || [recEmail] : [recEmail],
|
||||
hash: publicKey ? publicKey.id.slice(KEY_NAME_SUBSTR).toUpperCase() : '',
|
||||
key: publicKey
|
||||
}))
|
||||
: [];
|
||||
}).flat().validUnique(encryptKey => encryptKey.hash)
|
||||
);
|
||||
|
||||
if (this.encryptKeys.length) {
|
||||
this.encrypt(true);
|
||||
}
|
||||
}
|
||||
|
||||
this.text(sText);
|
||||
}
|
||||
}
|
||||
|
||||
export { ComposeOpenPgpPopupView, ComposeOpenPgpPopupView as default };
|
||||
|
|
@ -47,7 +47,7 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
|
|||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
PgpUserStore.openpgp
|
||||
openpgp
|
||||
.generateKey({
|
||||
userIds: [userId],
|
||||
numBits: pInt(this.keyBitLength()),
|
||||
|
|
@ -62,7 +62,7 @@ class NewOpenPgpKeyPopupView extends AbstractViewPopup {
|
|||
|
||||
openpgpKeyring.store();
|
||||
|
||||
rl.app.reloadOpenPgpKeys();
|
||||
PgpUserStore.reloadOpenPgpKeys();
|
||||
this.cancelCommand();
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export class MailMessageView extends AbstractViewRight {
|
|||
|
||||
pgpSigned: () => MessageUserStore.message() && !!MessageUserStore.message().pgpSigned(),
|
||||
|
||||
pgpEncrypted: () => PgpUserStore.openpgp
|
||||
pgpEncrypted: () => PgpUserStore.isSupported()
|
||||
&& MessageUserStore.message() && MessageUserStore.message().isPgpEncrypted(),
|
||||
|
||||
messageListOrViewLoading:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue