diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mail/extensions/openpgp/content/ui/keyAssistant.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mail/extensions/openpgp/content/ui/keyAssistant.js')
-rw-r--r-- | comm/mail/extensions/openpgp/content/ui/keyAssistant.js | 956 |
1 files changed, 956 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/content/ui/keyAssistant.js b/comm/mail/extensions/openpgp/content/ui/keyAssistant.js new file mode 100644 index 0000000000..ca6104c2cb --- /dev/null +++ b/comm/mail/extensions/openpgp/content/ui/keyAssistant.js @@ -0,0 +1,956 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* global MozElements */ +/* import-globals-from ../../../../components/compose/content/MsgComposeCommands.js */ +/* import-globals-from commonWorkflows.js */ +/* globals goDoCommand */ // From globalOverlay.js + +"use strict"; + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + EnigmailKeyRing: "chrome://openpgp/content/modules/keyRing.jsm", + KeyLookupHelper: "chrome://openpgp/content/modules/keyLookupHelper.jsm", + OpenPGPAlias: "chrome://openpgp/content/modules/OpenPGPAlias.jsm", + PgpSqliteDb2: "chrome://openpgp/content/modules/sqliteDb.jsm", + // FIXME: using this creates a conflict with another file where this symbol + // was imported with ChromeUtils instead of defined as lazy getter. + // EnigmailWindows: "chrome://openpgp/content/modules/windows.jsm", +}); +var { EnigmailWindows } = ChromeUtils.import( + "chrome://openpgp/content/modules/windows.jsm" +); + +window.addEventListener("load", () => { + gKeyAssistant.onLoad(); +}); +window.addEventListener("unload", () => { + gKeyAssistant.onUnload(); +}); + +var gKeyAssistant = { + dialog: null, + recipients: [], + currentRecip: null, + + /* + * Variable ignoreExternal should be set to true whenever a + * keyAsssistant window is open that cannot tolerate changes to + * the keyAsssistant's own variables, that track the current user + * interaction. + * + * While the key assistant is showing, it takes care to update the + * elements on screen, based on the expected changes. Usually, + * it will perform a refresh after a current action is completed. + * + * Without this protection, you'd get data races and side effects like + * email addresses being shown twice, and worse. + */ + ignoreExternal: false, + + /** + * Initialize the main notification box for the account setup process. + */ + get notificationBox() { + if (!this._notificationBox) { + this._notificationBox = new MozElements.NotificationBox(element => { + element.setAttribute("notificationside", "bottom"); + document.getElementById("modalDialogNotification").append(element); + }); + } + return this._notificationBox; + }, + + onLoad() { + this.dialog = document.getElementById("keyAssistant"); + + this._setupEventListeners(); + }, + + _setupEventListeners() { + document + .getElementById("disableEncryptionButton") + .addEventListener("click", () => { + gSendEncrypted = false; + gUserTouchedSendEncrypted = true; + checkEncryptionState(); + this.close(); + }); + document + .getElementById("sendEncryptedButton") + .addEventListener("click", () => { + goDoCommand("cmd_sendWithCheck"); + this.close(); + }); + document + .getElementById("toggleRecipientsButton") + .addEventListener("click", () => { + this.toggleRecipientsList(); + }); + + this.dialog.addEventListener("close", () => { + this.close(); + }); + }, + + async close() { + await checkEncryptionState(); + this.dialog.close(); + }, + + onUnload() { + this.recipients = []; + }, + + setMainDisableButton() { + document.getElementById("disableEncryptionButton").hidden = + !gSendEncrypted || (this.usableKeys && !this.problematicKeys); + }, + + /** + * Open the key assistant modal dialog. + * + * @param {string[]} recipients - An array of strings containing all currently + * written recipients. + * @param {boolean} isSending - If the key assistant was triggered during a + * sending attempt. + */ + show(recipients, isSending) { + this.recipients = recipients; + this.buildMainView(); + this.resetViews(); + + document.getElementById("sendEncryptedButton").hidden = !isSending; + this.setMainDisableButton(); + this.dialog.showModal(); + }, + + resetViews() { + this.notificationBox.removeAllNotifications(); + this.dialog.removeAttribute("style"); + + for (let view of document.querySelectorAll(".dialog-body-view")) { + view.hidden = true; + } + + document.getElementById("mainButtons").hidden = false; + document.getElementById("mainView").hidden = false; + }, + + changeView(view, context) { + this.resetViews(); + + this.dialog.setAttribute( + "style", + `min-height: ${this.dialog.getBoundingClientRect().height}px` + ); + + document.getElementById("mainView").hidden = true; + document.getElementById(`${view}View`).hidden = false; + + switch (view) { + case "discover": + this.hideMainButtons(); + this.initOnlineDiscovery(context); + break; + + case "resolve": + this.hideMainButtons(); + break; + + default: + break; + } + }, + + hideMainButtons() { + document.getElementById("mainButtons").hidden = true; + }, + + usableKeys: 0, + problematicKeys: 0, + + /** + * Populate the main view of the key assistant with the list of recipients and + * its keys, separating the recipients that have issues from those without + * issues. + */ + async buildMainView() { + // Restore empty UI state. + document.getElementById("keyAssistantIssues").hidden = true; + document.getElementById("keysListIssues").replaceChildren(); + document.getElementById("keyAssistantValid").hidden = true; + document.getElementById("keysListValid").replaceChildren(); + + this.usableKeys = 0; + this.problematicKeys = 0; + + for (let addr of this.recipients) { + // Fetch all keys for the current recipient. + let keyMetas = await EnigmailKeyRing.getEncryptionKeyMeta(addr); + if (keyMetas.some(k => k.readiness == "alias")) { + let aliasKeyList = EnigmailKeyRing.getAliasKeyList(addr); + let aliasKeys = EnigmailKeyRing.getAliasKeys(aliasKeyList); + if (!aliasKeys.length) { + // failure, at least one alias key is unusable/unavailable + + let descriptionDiv = document.createElement("div"); + document.l10n.setAttributes( + descriptionDiv, + "openpgp-compose-alias-status-error" + ); + + this.addToProblematicList(addr, descriptionDiv, null); + this.problematicKeys++; + } else { + let aliasText = document.createElement("div"); + document.l10n.setAttributes( + aliasText, + "openpgp-compose-alias-status-direct", + { count: aliasKeys.length } + ); + + this.addToReadyList(addr, aliasText); + this.usableKeys++; + } + } else { + // not alias + + let acceptedKeys = keyMetas.filter(k => k.readiness == "accepted"); + if (acceptedKeys.length) { + let button = document.createElement("button"); + document.l10n.setAttributes( + button, + "openpgp-key-assistant-view-key-button" + ); + button.addEventListener("click", () => { + gKeyAssistant.viewKeyFromOverview(addr, acceptedKeys[0]); + }); + + this.addToReadyList(addr, button); + this.usableKeys++; + } else { + let descriptionDiv = document.createElement("div"); + + let canOfferResolving = keyMetas.some( + k => + k.readiness == "collected" || + k.readiness == "expiredAccepted" || + k.readiness == "expiredUndecided" || + k.readiness == "expiredOtherAccepted" || + k.readiness == "undecided" || + k.readiness == "otherAccepted" || + k.readiness == "expiredRejected" || + k.readiness == "rejected" + ); + + let button = null; + if (canOfferResolving) { + this.fillKeysStatus(descriptionDiv, keyMetas); + + button = document.createElement("button"); + document.l10n.setAttributes( + button, + "openpgp-key-assistant-issue-resolve-button" + ); + button.addEventListener("click", () => { + this.buildResolveView(addr, keyMetas); + }); + } else { + document.l10n.setAttributes( + descriptionDiv, + "openpgp-key-assistant-no-key-available" + ); + } + + this.addToProblematicList(addr, descriptionDiv, button); + this.problematicKeys++; + } + } + } + + document.getElementById("keyAssistantIssues").hidden = + !this.problematicKeys; + document.l10n.setAttributes( + document.getElementById("keyAssistantIssuesDescription"), + "openpgp-key-assistant-recipients-issue-description", + { count: this.problematicKeys } + ); + + document.getElementById("keyAssistantValid").hidden = !this.usableKeys; + + if (!this.problematicKeys && this.usableKeys) { + document.l10n.setAttributes( + document.getElementById("keyAssistantValidDescription"), + "openpgp-key-assistant-recipients-description-no-issues" + ); + document.getElementById("toggleRecipientsButton").click(); + } else { + document.l10n.setAttributes( + document.getElementById("keyAssistantValidDescription"), + "openpgp-key-assistant-recipients-description", + { count: this.usableKeys } + ); + } + + document.getElementById("sendEncryptedButton").disabled = + this.problematicKeys || !this.usableKeys; + this.setMainDisableButton(); + }, + + isAccepted(acc) { + return ( + acc.emailDecided && + (acc.fingerprintAcceptance == "verified" || + acc.fingerprintAcceptance == "unverified") + ); + }, + + async viewKeyFromResolve(keyMeta) { + let oldAccept = {}; + await PgpSqliteDb2.getAcceptance( + keyMeta.keyObj.fpr, + this.currentRecip, + oldAccept + ); + + this.ignoreExternal = true; + await this._viewKey(keyMeta); + this.ignoreExternal = false; + + // If the key is not yet accepted, then we want to automatically + // close the email-resolve view, if the user accepts the key + // while viewing the key details. + let autoCloseOnAccept = !this.isAccepted(oldAccept); + + let newAccept = {}; + await PgpSqliteDb2.getAcceptance( + keyMeta.keyObj.fpr, + this.currentRecip, + newAccept + ); + + if (autoCloseOnAccept && this.isAccepted(newAccept)) { + this.resetViews(); + this.buildMainView(); + } else { + // While viewing the key, the user could have triggered a refresh, + // which could have changed the validity of the key. + let keyMetas = await EnigmailKeyRing.getEncryptionKeyMeta( + this.currentRecip + ); + this.buildResolveView(this.currentRecip, keyMetas); + } + }, + + async viewKeyFromOverview(recip, keyMeta) { + this.ignoreExternal = true; + await this._viewKey(keyMeta); + this.ignoreExternal = false; + + // While viewing the key, the user could have triggered a refresh, + // which could have changed the validity of the key. + // In theory it would be sufficient to refresh the main view + // for the single email address. + await checkEncryptionState("openpgp-key-assistant-refresh"); + this.buildMainView(); + }, + + async _viewKey(keyMeta) { + let exists = EnigmailKeyRing.getKeyById(keyMeta.keyObj.keyId); + + if (!exists) { + if (keyMeta.readiness != "collected") { + return; + } + await EnigmailKeyRing.importKeyDataSilent( + window, + keyMeta.collectedKey.pubKey, + true + ); + } + + EnigmailWindows.openKeyDetails(window, keyMeta.keyObj.keyId, false); + }, + + addToReadyList(recipient, detailElement) { + let list = document.getElementById("keysListValid"); + let row = document.createElement("li"); + row.classList.add("key-row"); + + let info = document.createElement("div"); + info.classList.add("key-info"); + let title = document.createElement("b"); + title.textContent = recipient; + + info.appendChild(title); + row.append(info, detailElement); + list.appendChild(row); + }, + + fillKeysStatus(element, keyMetas) { + let unaccepted = keyMetas.filter( + k => + k.readiness == "undecided" || + k.readiness == "rejected" || + k.readiness == "otherAccepted" + ); + let collected = keyMetas.filter(k => k.readiness == "collected"); + + // Multiple keys available. + if (unaccepted.length + collected.length > 1) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-multiple-keys" + ); + // TODO: add note to be careful? + return; + } + + // Not expired but not accepted keys. + if (unaccepted.length) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-unaccepted", + { + count: unaccepted.length, + } + ); + if (unaccepted.length == 1) { + element.before("0x" + unaccepted[0].keyObj.keyId); + } + return; + } + + let expiredAccepted = keyMetas.filter( + k => k.readiness == "expiredAccepted" + ); + + // Key accepted but expired. + if (expiredAccepted.length) { + if (expiredAccepted.length == 1) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-accepted-expired", + { + date: expiredAccepted[0].keyObj.effectiveExpiry, + } + ); + element.before("0x" + expiredAccepted[0].keyObj.keyId); + } else { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-keys-accepted-expired" + ); + } + return; + } + + let expiredUnaccepted = keyMetas.filter( + k => + k.readiness == "expiredUndecided" || + k.readiness == "expiredRejected" || + k.readiness == "expiredOtherAccepted" + ); + + // Key not accepted and expired. + if (expiredUnaccepted.length) { + if (expiredUnaccepted.length == 1) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-unaccepted-expired-one", + { + date: expiredUnaccepted[0].keyObj.effectiveExpiry, + } + ); + element.before("0x" + expiredUnaccepted[0].keyObj.keyId); + } else { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-unaccepted-expired-many" + ); + } + return; + } + + let unacceptedNotYetImported = keyMetas.filter( + k => k.readiness == "collected" + ); + + if (unacceptedNotYetImported.length) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-keys-has-collected", + { + count: unacceptedNotYetImported.length, + } + ); + if (unacceptedNotYetImported.length == 1) { + element.before("0x" + unacceptedNotYetImported[0].keyObj.keyId); + } + return; + } + + // We found nothing, so let's return a default message. + document.l10n.setAttributes( + element, + "openpgp-key-assistant-no-key-available" + ); + }, + + addToProblematicList(recipient, descriptionDiv, resolveButton) { + let list = document.getElementById("keysListIssues"); + let row = document.createElement("li"); + row.classList.add("key-row"); + + let info = document.createElement("div"); + info.classList.add("key-info"); + let title = document.createElement("b"); + title.textContent = recipient; + info.append(title, descriptionDiv); + + if (resolveButton) { + row.append(info, resolveButton); + } else { + row.appendChild(info); + } + + list.appendChild(row); + }, + + fillKeyOriginAndStatus(element, keyMeta) { + // The key was collected from somewhere. + if (keyMeta.collectedKey) { + let sourceSpan = document.createElement("span"); + document.l10n.setAttributes( + sourceSpan, + "openpgp-key-assistant-key-source", + { + count: keyMeta.collectedKey.sources.length, + } + ); + element.append(sourceSpan, ": "); + let linkSpan = document.createElement("span"); + linkSpan.classList.add("comma-separated"); + + let sourceLinks = keyMeta.collectedKey.sources.map(source => { + source.type = source.type.toLowerCase(); // Earlier "WKD" was "wkd". + let a = document.createElement("a"); + if (source.uri) { + a.href = source.uri; + a.title = source.uri; + } + if (source.description) { + if (a.title) { + a.title += " - "; + } + a.title += source.description; + } + let span = document.createElement("span"); + // openpgp-key-assistant-key-collected-attachment + // openpgp-key-assistant-key-collected-autocrypt + // openpgp-key-assistant-key-collected-keyserver + // openpgp-key-assistant-key-collected-wkd + document.l10n.setAttributes( + span, + `openpgp-key-assistant-key-collected-${source.type}` + ); + a.appendChild(span); + return a; + }); + linkSpan.append(...sourceLinks); + element.appendChild(linkSpan); + return; + } + + // The key was rejected. + if (keyMeta.readiness == "rejected") { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-rejected" + ); + return; + } + + // Key is expired. + if ( + keyMeta.readiness == "expiredAccepted" || + keyMeta.readiness == "expiredUndecided" || + keyMeta.readiness == "expiredOtherAccepted" || + keyMeta.readiness == "expiredRejected" + ) { + document.l10n.setAttributes( + element, + "openpgp-key-assistant-this-key-accepted-expired", + { + date: keyMeta.keyObj.effectiveExpiry, + } + ); + return; + } + + if (keyMeta.readiness == "otherAccepted") { + // Was the key already accepted for another email address? + document.l10n.setAttributes( + element, + "openpgp-key-assistant-key-accepted-other", + { + date: keyMeta.keyObj.effectiveExpiry, + } + ); + } + }, + + async buildResolveView(recipient, keyMetas) { + this.currentRecip = recipient; + document.getElementById("resolveViewAcceptKey").disabled = true; + + let unaccepted = keyMetas.filter( + k => + k.readiness == "undecided" || + k.readiness == "rejected" || + k.readiness == "otherAccepted" + ); + let collected = keyMetas.filter(k => k.readiness == "collected"); + let expiredAccepted = keyMetas.filter( + k => k.readiness == "expiredAccepted" + ); + let expiredUnaccepted = keyMetas.filter( + k => + k.readiness == "expiredUndecided" || + k.readiness == "expiredRejected" || + k.readiness == "expiredOtherAccepted" + ); + + this.usableKeys = unaccepted.length + collected.length; + let problematicKeys = expiredAccepted.length + expiredUnaccepted.length; + let numKeys = this.usableKeys + problematicKeys; + + document.l10n.setAttributes( + document.getElementById("resolveViewTitle"), + "openpgp-key-assistant-resolve-title", + { + recipient, + numKeys, + } + ); + + document.l10n.setAttributes( + document.getElementById("resolveViewExpiredDescription"), + "openpgp-key-assistant-invalid-title", + { numKeys } + ); + + document.getElementById("resolveViewValid").hidden = !this.usableKeys; + let usableList = document.getElementById("resolveValidKeysList"); + usableList.replaceChildren(); + + function createKeyRow(keyMeta, isValid) { + let row = document.createElement("li"); + let label = document.createElement("label"); + label.classList.add("flex-center"); + + let input = document.createElement("input"); + input.type = "radio"; + input.name = isValid ? "valid-key" : "invalid-key"; + input.value = keyMeta.keyObj.keyId; + input.disabled = !isValid; + + if (isValid) { + input.addEventListener("change", () => { + document.getElementById("resolveViewAcceptKey").disabled = false; + }); + } + label.appendChild(input); + + let keyId = document.createElement("b"); + keyId.textContent = "0x" + keyMeta.keyObj.keyId; + + let creationTime = document.createElement("time"); + creationTime.setAttribute( + "datetime", + new Date(keyMeta.keyObj.keyCreated * 1000).toISOString() + ); + document.l10n.setAttributes( + creationTime, + "openpgp-key-assistant-key-created", + { date: keyMeta.keyObj.created } + ); + label.append(keyId, " - ", creationTime); + row.appendChild(label); + + let fingerprint = document.createElement("div"); + fingerprint.classList.add("key-info-block"); + let fpDesc = document.createElement("span"); + let fpLink = document.createElement("a"); + fpLink.href = "#"; + fpLink.textContent = EnigmailKey.formatFpr(keyMeta.keyObj.fpr); + fpLink.addEventListener("click", event => { + event.preventDefault(); + gKeyAssistant.viewKeyFromResolve(keyMeta); + }); + document.l10n.setAttributes( + fpDesc, + "openpgp-key-assistant-key-fingerprint" + ); + fingerprint.append(fpDesc, ": ", fpLink); + row.appendChild(fingerprint); + + let info = document.createElement("div"); + info.classList.add("key-info-block"); + row.append(info); + + gKeyAssistant.fillKeyOriginAndStatus(info, keyMeta); + return row; + } + + for (let meta of unaccepted) { + usableList.appendChild(createKeyRow(meta, true)); + } + + for (let meta of collected) { + usableList.appendChild(createKeyRow(meta, true)); + } + + document.getElementById("resolveViewInvalid").hidden = !problematicKeys; + let problematicList = document.getElementById("resolveInvalidKeysList"); + problematicList.replaceChildren(); + + for (let meta of expiredAccepted) { + problematicList.appendChild(createKeyRow(meta, false)); + } + for (let meta of expiredUnaccepted) { + problematicList.appendChild(createKeyRow(meta, false)); + } + + document.getElementById("resolveViewAcceptKey").onclick = () => { + this.acceptSelectedKey(recipient, keyMetas); + }; + this.changeView("resolve"); + }, + + async acceptSelectedKey(recipient, keyMetas) { + let selectedKey = document.querySelector( + 'input[name="valid-key"]:checked' + )?.value; + if (!selectedKey) { + // The accept button was enabled but nothing was selected. + return; + } + let fingerprint; + + this.ignoreExternal = true; + + let existingKey = EnigmailKeyRing.getKeyById(selectedKey); + if (existingKey) { + fingerprint = existingKey.fpr; + } else { + let unacceptedNotYetImported = keyMetas.filter( + k => k.readiness == "collected" + ); + + for (let keyMeta of unacceptedNotYetImported) { + if (keyMeta.keyObj.keyId != selectedKey) { + continue; + } + await EnigmailKeyRing.importKeyDataSilent( + window, + keyMeta.collectedKey.pubKey, + true + ); + fingerprint = keyMeta.keyObj.fpr; + } + } + + if (!fingerprint) { + throw new Error(`Key not found for id=${selectedKey}`); + } + + await PgpSqliteDb2.addAcceptedEmail(fingerprint, recipient).catch( + console.error + ); + + // Trigger the UI refresh of the compose window. + await checkEncryptionState("openpgp-key-assistant-refresh"); + + this.ignoreExternal = false; + this.resetViews(); + this.buildMainView(); + }, + + async initOnlineDiscovery(context) { + let container = document.getElementById("discoveryOutput"); + container.replaceChildren(); + + function write(recipient) { + let p = document.createElement("p"); + let span = document.createElement("span"); + document.l10n.setAttributes(span, "openpgp-key-assistant-discover-keys", { + recipient, + }); + let span2 = document.createElement("span"); + span2.classList.add("loading-inline"); + p.append(span, " ", span2); + container.appendChild(p); + } + + let gotNewData = false; // XXX: not used for anything atm + + // Checking gotNewData isn't really sufficient, because the discovery could + // find an update for an existing key, which was expired, and is now valid + // again. Let's always rebuild for now. + + if (context == "overview") { + this.ignoreExternal = true; + for (let email of this.recipients) { + if (OpenPGPAlias.hasAliasDefinition(email)) { + continue; + } + write(email); + let rv = await KeyLookupHelper.fullOnlineDiscovery( + "silent-collection", + window, + email, + null + ); + gotNewData = gotNewData || rv; + } + + // Wait a sec before closing the view, so the user has time to see what + // happened. + await new Promise(resolve => setTimeout(resolve, 1000)); + + this.resetViews(); + this.buildMainView(); + + // Online discovery and key collection triggered key change + // notifications. We must allow those notifications arrive while + // ignoreExternal is still true. + // Use settimeout to reset ignoreExternal to false afterwards. + setTimeout(function () { + this.ignoreExternal = false; + }); + return; + } + + // We should never arrive here for an email address that has an + // alias rule, because for those we don't want to perform online + // discovery. + + if (OpenPGPAlias.hasAliasDefinition(this.currentRecip)) { + throw new Error(`${this.currentRecip} has an alias rule`); + } + + write(this.currentRecip); + + this.ignoreExternal = true; + gotNewData = await KeyLookupHelper.fullOnlineDiscovery( + "silent-collection", + window, + this.currentRecip, + null + ); + // Online discovery and key collection triggered key change + // notifications. We must allow those notifications arrive while + // ignoreExternal is still true. + // Use settimeout to reset ignoreExternal to false afterwards. + setTimeout(function () { + this.ignoreExternal = false; + }); + + // If the recipient now has a usable previously accepted key, go back to + // the main view and show a successful notification. + let keyMetas = await EnigmailKeyRing.getEncryptionKeyMeta( + this.currentRecip + ); + + if (keyMetas.some(k => k.readiness == "accepted")) { + // Trigger the UI refresh of the compose window. + await checkEncryptionState("openpgp-key-assistant-refresh"); + + // Wait a sec before closing the view, so the user has time to see what + // happened. + await new Promise(resolve => setTimeout(resolve, 1000)); + this.resetViews(); + this.buildMainView(); + + let notification = + this.notificationBox.getNotificationWithValue("acceptedKeyUpdated"); + + // If a notification already exists, simply update the message. + if (notification) { + document.l10n.setAttributes( + notification.messageText, + "openpgp-key-assistant-expired-key-update", + { + recipient: this.currentRecip, + } + ); + return; + } + + notification = this.notificationBox.appendNotification( + "acceptedKeyUpdated", + { + label: { + "l10n-id": "openpgp-key-assistant-expired-key-update", + "l10n-args": { recipient: this.currentRecip }, + }, + priority: this.notificationBox.PRIORITY_INFO_HIGH, + }, + null + ); + notification.setAttribute("type", "success"); + return; + } + + this.buildResolveView(this.currentRecip, keyMetas); + gKeyAssistant.changeView("resolve"); + }, + + toggleRecipientsList() { + let list = document.getElementById("keysListValid"); + list.hidden = !list.hidden; + + document.l10n.setAttributes( + document.getElementById("toggleRecipientsButton"), + list.hidden + ? "openpgp-key-assistant-recipients-show-button" + : "openpgp-key-assistant-recipients-hide-button" + ); + }, + + async importFromFile(context) { + await EnigmailCommon_importObjectFromFile("pub"); + if (context == "overview") { + this.buildMainView(); + } else { + this.buildResolveView( + this.currentRecip, + await EnigmailKeyRing.getEncryptionKeyMeta(this.currentRecip) + ); + } + }, + + onExternalKeyChange() { + if (!this.dialog || !this.dialog.open) { + return; + } + + if (this.ignoreExternal) { + return; + } + + // Refresh the "overview", which will potentially close a currently + // shown "resolve" view. + this.resetViews(); + this.buildMainView(); + }, +}; |