diff --git a/src/commands/refreshcertificatescommand.cpp b/src/commands/refreshcertificatescommand.cpp index 58c0177db..edbc0ea25 100644 --- a/src/commands/refreshcertificatescommand.cpp +++ b/src/commands/refreshcertificatescommand.cpp @@ -1,495 +1,491 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/refreshcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "refreshcertificatescommand.h" #include #include #include +#include #include #include #include #include #include #if QGPGME_SUPPORTS_WKD_REFRESH_JOB #include #endif #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; class RefreshCertificatesCommand::Private : public Command::Private { friend class ::RefreshCertificatesCommand; RefreshCertificatesCommand *q_func() const { return static_cast(q); } public: explicit Private(RefreshCertificatesCommand *qq); explicit Private(RefreshCertificatesCommand *qq, KeyListController *c); ~Private() override; void start(); void cancel(); - std::unique_ptr startReceiveKeysJob(const std::vector keys); - std::unique_ptr startSMIMEJob(const std::vector keys); + std::unique_ptr startKeyserverJob(); + std::unique_ptr startSMIMEJob(); #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr startWKDRefreshJob(); #endif - void onReceiveKeysJobResult(const ImportResult &result); + void onKeyserverJobResult(const ImportResult &result); void onWKDRefreshJobResult(const ImportResult &result); void onSMIMEJobResult(const Error &err); void showOpenPGPResult(); void showError(const Error &err); void checkFinished(); private: QPointer pgpJob; QPointer smimeJob; QPointer wkdJob; + std::vector pgpKeys; + std::vector smimeKeys; + ImportResult receiveKeysResult; ImportResult wkdRefreshResult; std::optional smimeError; }; RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func() { return static_cast(d.get()); } const RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RefreshCertificatesCommand::Private::Private(RefreshCertificatesCommand *qq) : Command::Private{qq} { } RefreshCertificatesCommand::Private::Private(RefreshCertificatesCommand *qq, KeyListController *c) : Command::Private{qq, c} { } RefreshCertificatesCommand::Private::~Private() = default; void RefreshCertificatesCommand::Private::start() { if (std::ranges::any_of(keys(), [](const auto &key) { return key.protocol() == GpgME::UnknownProtocol; })) { qCWarning(KLEOPATRA_LOG) << "Key has unknown protocol"; finished(); return; } std::unique_ptr pgpRefreshJob; std::unique_ptr smimeRefreshJob; + std::unique_ptr wkdRefreshJob; - std::vector pgpKeys; - std::vector smimeKeys; + auto keysByProtocol = Kleo::partitionKeysByProtocol(keys()); - for (const auto &key : keys()) { - if (key.protocol() == GpgME::OpenPGP) { - pgpKeys.push_back(key); - } else { - smimeKeys.push_back(key); - } - } + pgpKeys = keysByProtocol.openpgp; + smimeKeys = keysByProtocol.cms; - if (smimeKeys.size() > 0) { - smimeRefreshJob = startSMIMEJob(smimeKeys); + if (!smimeKeys.empty()) { + smimeRefreshJob = startSMIMEJob(); } - if (pgpKeys.size() > 0) { + if (!pgpKeys.empty()) { if (haveKeyserverConfigured()) { - pgpRefreshJob = startReceiveKeysJob(pgpKeys); - } else { - QMetaObject::invokeMethod( - q, - [this]() { - // use GPG_ERR_USER_1 to signal skipped key server lookup - onReceiveKeysJobResult(ImportResult{Error::fromCode(GPG_ERR_USER_1)}); - }, - Qt::QueuedConnection); + pgpRefreshJob = startKeyserverJob(); } +#if QGPGME_SUPPORTS_WKD_REFRESH_JOB + wkdRefreshJob = startWKDRefreshJob(); +#endif } - if (!pgpRefreshJob && !smimeRefreshJob) { + if (!pgpRefreshJob && !smimeRefreshJob && !wkdRefreshJob) { finished(); return; } pgpJob = pgpRefreshJob.release(); smimeJob = smimeRefreshJob.release(); + wkdJob = wkdRefreshJob.release(); } void RefreshCertificatesCommand::Private::cancel() { if (pgpJob) { pgpJob->slotCancel(); } if (smimeJob) { smimeJob->slotCancel(); } + + if (wkdJob) { + wkdJob->slotCancel(); + } + pgpJob.clear(); smimeJob.clear(); - smimeError = Error(GPG_ERR_CANCELED); + wkdJob.clear(); + + smimeError = Error::fromCode(GPG_ERR_CANCELED); } -std::unique_ptr RefreshCertificatesCommand::Private::startReceiveKeysJob(const std::vector keys) +std::unique_ptr RefreshCertificatesCommand::Private::startKeyserverJob() { std::unique_ptr refreshJob{QGpgME::openpgp()->receiveKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::ReceiveKeysJob::result, q, [this](const GpgME::ImportResult &result) { - onReceiveKeysJobResult(result); + onKeyserverJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - QStringList fingerprints; - - for (const auto &key : keys) { - fingerprints += QString::fromLatin1(key.primaryFingerprint()); - } - - const GpgME::Error err = refreshJob->start(fingerprints); + const GpgME::Error err = refreshJob->start(Kleo::getFingerprints(pgpKeys)); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } -std::unique_ptr RefreshCertificatesCommand::Private::startSMIMEJob(const std::vector keys) +std::unique_ptr RefreshCertificatesCommand::Private::startSMIMEJob() { std::unique_ptr refreshJob{QGpgME::smime()->refreshKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::RefreshKeysJob::result, q, [this](const GpgME::Error &err) { onSMIMEJobResult(err); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - const GpgME::Error err = refreshJob->start(keys); + const GpgME::Error err = refreshJob->start(smimeKeys); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating certificate...")); return refreshJob; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr RefreshCertificatesCommand::Private::startWKDRefreshJob() { std::vector pgpKeys; std::vector wkdKeys; for (const auto &key : keys()) { if (key.protocol() == GpgME::OpenPGP) { pgpKeys.push_back(key); const auto userIds = key.userIDs(); if (std::any_of(userIds.begin(), userIds.end(), [](const auto &userId) { return !userId.isRevoked() && !userId.addrSpec().empty() && userId.origin() == Key::OriginWKD; })) { wkdKeys.push_back(key); } } } if (!Settings{}.queryWKDsForAllUserIDs()) { // check if key is eligible for WKD refresh, i.e. if any user ID has WKD as origin if (wkdKeys.size() == 0) { wkdRefreshResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)}; return {}; } pgpKeys = wkdKeys; } std::unique_ptr refreshJob{QGpgME::openpgp()->wkdRefreshJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::WKDRefreshJob::result, q, [this](const GpgME::ImportResult &result) { onWKDRefreshJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); Error err; if (Settings{}.queryWKDsForAllUserIDs()) { std::vector userIds; for (const auto &key : pgpKeys) { const auto newUserIds = key.userIDs(); userIds.insert(userIds.end(), newUserIds.begin(), newUserIds.end()); } err = refreshJob->start(userIds); } else { err = refreshJob->start(pgpKeys); } if (err) { wkdRefreshResult = ImportResult{err}; return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } #endif namespace { static auto informationOnChanges(const ImportResult &result) { QString text; // if additional keys have been retrieved via WKD, then most of the below // details are just a guess and may concern the additional keys instead of // the refresh keys; this could only be clarified by a thorough comparison of // unrefreshed and refreshed key if (result.numUnchanged() == result.numConsidered()) { // if numUnchanged < numConsidered, then it is not clear whether the refreshed key // hasn't changed or whether another key retrieved via WKD hasn't changed text = i18n("The key hasn't changed."); } else if (result.newRevocations() > 0) { // it is possible that a revoked key has been newly imported via WKD, // but it is much more likely that the refreshed key was revoked text = i18n("The key has been revoked."); } else { // it doesn't make much sense to list below details if the key has been revoked text = i18n("The key has been updated."); QStringList details; if (result.newUserIDs() > 0) { details.push_back(i18n("New user IDs: %1", result.newUserIDs())); } if (result.newSubkeys() > 0) { details.push_back(i18n("New subkeys: %1", result.newSubkeys())); } if (result.newSignatures() > 0) { details.push_back(i18n("New signatures: %1", result.newSignatures())); } if (!details.empty()) { text += QLatin1StringView{"

"} + details.join(QLatin1String{"
"}); } } text = QLatin1StringView{"

"} + text + QLatin1String{"

"}; if (result.numImported() > 0) { text += QLatin1StringView{"

"} + i18np("Additionally, one new key has been retrieved.", "Additionally, %1 new keys have been retrieved.", result.numImported()) + QLatin1StringView{"

"}; } return text; } } -void RefreshCertificatesCommand::Private::onReceiveKeysJobResult(const ImportResult &result) +void RefreshCertificatesCommand::Private::onKeyserverJobResult(const ImportResult &result) { receiveKeysResult = result; if (result.error().isCanceled()) { - pgpJob = nullptr; - checkFinished(); + pgpJob.clear(); + finished(); return; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr refreshJob = startWKDRefreshJob(); if (!refreshJob) { showOpenPGPResult(); return; } wkdJob = refreshJob.release(); #else if (receiveKeysResult.error()) { if (receiveKeysResult.error().code() == GPG_ERR_USER_1) { information(i18nc("@info", "The OpenPGP certificate update was skipped because no keyserver is configured."), i18nc("@title:window", "Update Skipped")); } else { showError(receiveKeysResult.error()); } - } else if (keys().size() == 0) { + } else if (keys().size() == 1) { information(informationOnChanges(receiveKeysResult), i18nc("@title:window", "Key Updated")); } - pgpJob = nullptr; + pgpJob.clear(); checkFinished(); #endif } void RefreshCertificatesCommand::Private::onWKDRefreshJobResult(const ImportResult &result) { wkdRefreshResult = result; - wkdJob = nullptr; + wkdJob.clear(); showOpenPGPResult(); } void RefreshCertificatesCommand::Private::onSMIMEJobResult(const Error &err) { smimeError = err; if (err) { showError(err); } - smimeJob = nullptr; + smimeJob.clear(); checkFinished(); } void RefreshCertificatesCommand::Private::showOpenPGPResult() { if (wkdRefreshResult.error().code() == GPG_ERR_USER_1 || wkdRefreshResult.error().isCanceled()) { if (receiveKeysResult.error()) { if (receiveKeysResult.error().code() == GPG_ERR_USER_1) { information(i18nc("@info", "The OpenPGP certificate update was skipped because no keyserver is configured."), i18nc("@title:window", "Update Skipped")); } else { showError(receiveKeysResult.error()); } } else if (keys().size() == 1) { information(informationOnChanges(receiveKeysResult), i18nc("@title:window", "Key Updated")); } - pgpJob = nullptr; + pgpJob.clear(); checkFinished(); return; } if (receiveKeysResult.error() && (receiveKeysResult.error().code() != GPG_ERR_USER_1) && wkdRefreshResult.error()) { error(xi18nc("@info", "Updating the OpenPGP certificate from a keyserver, an LDAP server, or Active Directory failed:" "%1" "Updating the certificate via Web Key Directory failed:" "%2", Formatting::errorAsString(receiveKeysResult.error()), Formatting::errorAsString(wkdRefreshResult.error())), i18nc("@title:window", "Update Failed")); - pgpJob = nullptr; + pgpJob.clear(); checkFinished(); return; } QString text; text += QLatin1StringView{"

"} + i18nc("@info", "Result of update from keyserver, LDAP server, or Active Directory") + QLatin1String{"

"}; if (receiveKeysResult.error()) { if (receiveKeysResult.error().code() == GPG_ERR_USER_1) { text += xi18nc("@info", "The OpenPGP certificate update was skipped because no keyserver is configured."); } else { text += xi18nc("@info", "The OpenPGP certificate update failed: %1", Formatting::errorAsString(receiveKeysResult.error())); } } else if (keys().size() == 1) { text += informationOnChanges(receiveKeysResult); } text += QLatin1StringView{"

"} + i18nc("@info", "Result of update via Web Key Directory") + QLatin1String{"

"}; if (wkdRefreshResult.error()) { text += xi18nc("@info", "The OpenPGP certificate update failed: %1", Formatting::errorAsString(wkdRefreshResult.error())); } else if (keys().size() == 1) { text += informationOnChanges(wkdRefreshResult); } if (keys().size() == 1) { information(text, i18nc("@title:window", "Key Updated")); } - pgpJob = nullptr; + pgpJob.clear(); checkFinished(); } void RefreshCertificatesCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred while updating the certificate:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Update Failed")); } RefreshCertificatesCommand::RefreshCertificatesCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { } RefreshCertificatesCommand::RefreshCertificatesCommand(const Key &key) : Command(key, new Private(this)) { } RefreshCertificatesCommand::~RefreshCertificatesCommand() = default; void RefreshCertificatesCommand::doStart() { d->start(); } void RefreshCertificatesCommand::doCancel() { d->cancel(); } void RefreshCertificatesCommand::Private::checkFinished() { if (smimeJob || pgpJob || wkdJob) { return; } if (smimeError && smimeError->code() == GPG_ERR_CANCELED) { finished(); return; } if (receiveKeysResult.error().code() == GPG_ERR_USER_1) { receiveKeysResult = {}; return; } if (!receiveKeysResult.error() && !receiveKeysResult.isNull() || smimeError && !*smimeError || !wkdRefreshResult.error() && !wkdRefreshResult.isNull()) { if (!smimeError || !*smimeError && !receiveKeysResult.error() && (!wkdRefreshResult.error() || wkdRefreshResult.error().code() == GPG_ERR_USER_1)) { if (keys().size() > 1 || keys()[0].protocol() != GpgME::OpenPGP) { information(i18ncp("@info", "The certificate has been updated.", "The certificates have been updated", keys().size()), i18nc("@title:window", "Certificate Updated")); } } else if (!smimeError || !*smimeError) { information(i18ncp("@info", "The S/MIME certificate has been updated.", "The S/MIME certificates have been updated", keys().size()), i18nc("@title:window", "Certificate Updated")); } else { information(i18ncp("@info", "The OpenPGP certificate has been updated.", "The OpenPGP certificates have been updated", keys().size()), i18nc("@title:window", "Certificate Updated")); } } finished(); } #undef d #undef q #include "moc_refreshcertificatescommand.cpp" diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index 573bf0211..1be1efb8d 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,1031 +1,1031 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keylistcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "kleopatra_debug.h" #include "tooltippreferences.h" #include #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #include "commands/adduseridcommand.h" #include "commands/certifycertificatecommand.h" #include "commands/changeexpirycommand.h" #include "commands/changeownertrustcommand.h" #include "commands/changepassphrasecommand.h" #include "commands/changeroottrustcommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/clearcrlcachecommand.h" #include "commands/creategroupcommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/deletecertificatescommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/dumpcrlcachecommand.h" #include "commands/exportpaperkeycommand.h" #include "commands/exportsecretkeycommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/importcrlcommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/newcertificatesigningrequestcommand.h" #include "commands/newopenpgpcertificatecommand.h" #include "commands/refreshcertificatescommand.h" #include "commands/refreshopenpgpcertscommand.h" #include "commands/refreshx509certscommand.h" #include "commands/reloadkeyscommand.h" #include "commands/revokecertificationcommand.h" #include "commands/revokekeycommand.h" #include "commands/signencryptfilescommand.h" #include "commands/signencryptfoldercommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; class KeyListController::Private { friend class ::Kleo::KeyListController; KeyListController *const q; public: explicit Private(KeyListController *qq); ~Private(); void connectView(QAbstractItemView *view); void connectCommand(Command *cmd); void connectTabWidget(); void disconnectTabWidget(); void addCommand(Command *cmd) { connectCommand(cmd); commands.insert(std::lower_bound(commands.begin(), commands.end(), cmd), cmd); } void addView(QAbstractItemView *view) { connectView(view); views.insert(std::lower_bound(views.begin(), views.end(), view), view); } void removeView(QAbstractItemView *view) { view->disconnect(q); view->selectionModel()->disconnect(q); views.erase(std::remove(views.begin(), views.end(), view), views.end()); } public: void slotDestroyed(QObject *o) { qCDebug(KLEOPATRA_LOG) << (void *)o; views.erase(std::remove(views.begin(), views.end(), o), views.end()); commands.erase(std::remove(commands.begin(), commands.end(), o), commands.end()); } void slotDoubleClicked(const QModelIndex &idx); void slotActivated(const QModelIndex &idx); void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_); void slotContextMenu(const QPoint &pos); void slotCommandFinished(); void slotActionTriggered(QAction *action); void slotCurrentViewChanged(QAbstractItemView *view) { if (view && !std::binary_search(views.cbegin(), views.cend(), view)) { qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!"; addView(view); } currentView = view; q->enableDisableActions(view ? view->selectionModel() : nullptr); } private: int toolTipOptions() const; private: static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm); private: struct action_item { QPointer action; Command::Restrictions restrictions; Command *(*createCommand)(QAbstractItemView *, KeyListController *); }; std::vector actions; std::vector views; std::vector commands; QPointer parentWidget; QPointer tabWidget; QPointer currentView; QPointer flatModel, hierarchicalModel; std::vector m_connections; }; KeyListController::Private::Private(KeyListController *qq) : q(qq) , actions() , views() , commands() , parentWidget() , tabWidget() , flatModel() , hierarchicalModel() { } KeyListController::Private::~Private() { } KeyListController::KeyListController(QObject *p) : QObject(p) , d(new Private(this)) { } KeyListController::~KeyListController() { } void KeyListController::addView(QAbstractItemView *view) { if (!view || std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->addView(view); } void KeyListController::removeView(QAbstractItemView *view) { if (!view || !std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->removeView(view); } void KeyListController::setCurrentView(QAbstractItemView *view) { d->slotCurrentViewChanged(view); } std::vector KeyListController::views() const { return d->views; } void KeyListController::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; if (model) { model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; if (model) { model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setTabWidget(TabWidget *tabWidget) { if (tabWidget == d->tabWidget) { return; } d->disconnectTabWidget(); d->tabWidget = tabWidget; d->connectTabWidget(); d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr); } void KeyListController::setParentWidget(QWidget *parent) { d->parentWidget = parent; } QWidget *KeyListController::parentWidget() const { return d->parentWidget; } void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); m_connections.reserve(3); m_connections.push_back(connect(tabWidget, &TabWidget::viewAdded, q, &KeyListController::addView)); m_connections.push_back(connect(tabWidget, &TabWidget::viewAboutToBeRemoved, q, &KeyListController::removeView)); m_connections.push_back(connect(tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) { slotCurrentViewChanged(view); })); } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } for (const auto &connection : m_connections) { disconnect(connection); } m_connections.clear(); const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { removeView(view); }); } AbstractKeyListModel *KeyListController::flatModel() const { return d->flatModel; } AbstractKeyListModel *KeyListController::hierarchicalModel() const { return d->hierarchicalModel; } QAbstractItemView *KeyListController::currentView() const { return d->currentView; } TabWidget *KeyListController::tabWidget() const { return d->tabWidget; } void KeyListController::createActions(KActionCollection *coll) { const std::vector common_and_openpgp_action_data = { // File menu { "file_new_certificate", i18n("New OpenPGP Key Pair..."), i18n("Create a new OpenPGP certificate"), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), }, { "file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"), "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"), }, #ifdef MAILAKONADI_ENABLED { "file_export_certificate_to_provider", i18n("Publish at Mail Provider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"), "view-certificate-export", nullptr, nullptr, QString(), }, #endif // MAILAKONADI_ENABLED { "file_export_secret_keys", i18n("Backup Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), "folder-edit-sign-encrypt-symbolic", nullptr, nullptr, QString(), }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr /*"document-checksum-create"*/, nullptr, nullptr, QString(), }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr /*"document-checksum-verify"*/, nullptr, nullptr, QString(), }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, [this](bool) { cancelCommands(); }, QStringLiteral("Escape"), RegularQAction, Disabled, }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), }, // Certificate menu { "certificates_revoke", i18n("Revoke Certificate..."), i18n("Revoke the selected OpenPGP certificate"), "view-certificate-revoke", nullptr, nullptr, {}, }, { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), }, { "certificates_refresh", - i18n("Refresh Certificates"), - i18n("Refresh selected certificates"), + i18n("Update Certificates"), + i18n("Update selected certificates"), "view-refresh", nullptr, nullptr, QString(), }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), }, { "certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"), "view-certificate-revoke", nullptr, nullptr, QString(), }, { "certificates_change_expiry", i18n("Change End of Validity Period..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_change_owner_trust", i18nc("@action:inmenu", "Change Certification Power..."), i18nc("@info:tooltip", "Grant or revoke the certification power of the selected certificate"), nullptr, nullptr, nullptr, QString(), }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_add_userid", i18n("Add User ID..."), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_create_group", i18nc("@action:inmenu", "Create Group..."), i18nc("@info:tooltip", "Create a group from the selected certificates"), "resource-group-new", nullptr, nullptr, QString(), }, // Tools menu { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; static const action_data cms_create_csr_action_data = { "file_new_certificate_signing_request", i18n("New S/MIME Certification Request..."), i18n("Create a new S/MIME certificate signing request (CSR)"), "view-certificate-add", nullptr, nullptr, {}, }; static const std::vector cms_action_data = { // Certificate menu { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), }, }; std::vector action_data = common_and_openpgp_action_data; if (const Kleo::Settings settings{}; settings.cmsEnabled()) { if (settings.cmsCertificateCreationAllowed()) { action_data.push_back(cms_create_csr_action_data); } action_data.reserve(action_data.size() + cms_action_data.size()); std::copy(std::begin(cms_action_data), std::end(cms_action_data), std::back_inserter(action_data)); } make_actions_from_data(action_data, coll); if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) { connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled); } // ### somehow make this better... registerActionForCommand(coll->action(QStringLiteral("file_new_certificate"))); registerActionForCommand(coll->action(QStringLiteral("file_new_certificate_signing_request"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_lookup_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_import_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_export_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_export_secret_keys"))); registerActionForCommand(coll->action(QStringLiteral("file_export_paper_key"))); registerActionForCommand(coll->action(QStringLiteral("file_export_certificates_to_server"))); #ifdef MAILAKONADI_ENABLED registerActionForCommand(coll->action(QStringLiteral("file_export_certificate_to_provider"))); #endif // MAILAKONADI_ENABLED //--- registerActionForCommand(coll->action(QStringLiteral("file_decrypt_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_folder"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_checksum_create_files"))); registerActionForCommand(coll->action(QStringLiteral("file_checksum_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("view_redisplay"))); // coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data() registerActionForCommand(coll->action(QStringLiteral("view_certificate_details"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_owner_trust"))); registerActionForCommand(coll->action(QStringLiteral("certificates_trust_root"))); registerActionForCommand(coll->action(QStringLiteral("certificates_distrust_root"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_certify_certificate"))); if (RevokeCertificationCommand::isSupported()) { registerActionForCommand(coll->action(QStringLiteral("certificates_revoke_certification"))); } //--- registerActionForCommand(coll->action(QStringLiteral("certificates_change_expiry"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_passphrase"))); registerActionForCommand(coll->action(QStringLiteral("certificates_add_userid"))); registerActionForCommand(coll->action(QStringLiteral("certificates_create_group"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_revoke"))); registerActionForCommand(coll->action(QStringLiteral("certificates_delete"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_refresh"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_dump_certificate"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_x509_certificates"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_openpgp_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_import_crl"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_clear_crl_cache"))); registerActionForCommand(coll->action(QStringLiteral("crl_dump_crl_cache"))); enableDisableActions(nullptr); } void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command *(*create)(QAbstractItemView *, KeyListController *)) { if (!action) { return; } Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow const Private::action_item ai = {action, restrictions, create}; connect(action, &QAction::triggered, this, [this, action]() { d->slotActionTriggered(action); }); d->actions.push_back(ai); } void KeyListController::registerCommand(Command *cmd) { if (!cmd || std::binary_search(d->commands.cbegin(), d->commands.cend(), cmd)) { return; } d->addCommand(cmd); qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (d->commands.size() == 1) { Q_EMIT commandsExecuting(true); } } bool KeyListController::hasRunningCommands() const { return !d->commands.empty(); } bool KeyListController::shutdownWarningRequired() const { return std::any_of(d->commands.cbegin(), d->commands.cend(), std::mem_fn(&Command::warnWhenRunningAtShutdown)); } // slot void KeyListController::cancelCommands() { std::for_each(d->commands.begin(), d->commands.end(), std::mem_fn(&Command::cancel)); } void KeyListController::Private::connectView(QAbstractItemView *view) { connect(view, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(view, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) { slotDoubleClicked(index); }); connect(view, &QAbstractItemView::activated, q, [this](const QModelIndex &index) { slotActivated(index); }); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &oldSel, const QItemSelection &newSel) { slotSelectionChanged(oldSel, newSel); }); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) { slotContextMenu(pos); }); } void KeyListController::Private::connectCommand(Command *cmd) { if (!cmd) { return; } connect(cmd, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(cmd, &Command::finished, q, [this] { slotCommandFinished(); }); // connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) ); connect(cmd, &Command::progress, q, &KeyListController::progress); } void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } if (const auto *const keyListModel = dynamic_cast(view->model())) { DetailsCommand *const c = new DetailsCommand{keyListModel->key(idx)}; c->setParentWidget(parentWidget ? parentWidget : view); c->start(); } } void KeyListController::Private::slotActivated(const QModelIndex &idx) { Q_UNUSED(idx) QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } } void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_) { Q_UNUSED(old) Q_UNUSED(new_) const QItemSelectionModel *const sm = qobject_cast(q->sender()); if (!sm) { return; } q->enableDisableActions(sm); } void KeyListController::Private::slotContextMenu(const QPoint &p) { QAbstractItemView *const view = qobject_cast(q->sender()); if (view && std::binary_search(views.cbegin(), views.cend(), view)) { Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p)); } else { qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!"; } } void KeyListController::Private::slotCommandFinished() { Command *const cmd = qobject_cast(q->sender()); if (!cmd || !std::binary_search(commands.cbegin(), commands.cend(), cmd)) { return; } qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (commands.size() == 1) { Q_EMIT q->commandsExecuting(false); } } void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const { const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm); for (const Private::action_item &ai : std::as_const(d->actions)) if (ai.action) { ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask)); } } static bool all_secret_are_not_owner_trust_ultimate(const std::vector &keys) { for (const Key &key : keys) if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) { return false; } return true; } Command::Restrictions find_root_restrictions(const std::vector &keys) { bool trusted = false, untrusted = false; for (const Key &key : keys) if (key.isRoot()) if (key.userID(0).validity() == UserID::Ultimate) { trusted = true; } else { untrusted = true; } else { return Command::NoRestriction; } if (trusted) if (untrusted) { return Command::NoRestriction; } else { return Command::MustBeTrustedRoot; } else if (untrusted) { return Command::MustBeUntrustedRoot; } else { return Command::NoRestriction; } } Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm) { if (!sm) { return Command::NoRestriction; } const KeyListModelInterface *const m = dynamic_cast(sm->model()); if (!m) { return Command::NoRestriction; } const std::vector keys = m->keys(sm->selectedRows()); if (keys.empty()) { return Command::NoRestriction; } Command::Restrictions result = Command::NeedSelection; if (keys.size() == 1) { result |= Command::OnlyOneKey; } #if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2 // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret(); }; #else // older versions of GpgME did not always set the secret flag for card keys const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret() || k.subkey(0).isCardKey(); }; #endif if (std::all_of(keys.cbegin(), keys.cend(), primaryKeyCanBeUsedForSecretKeyOperations)) { result |= Command::NeedSecretKey; } if (std::all_of(std::begin(keys), std::end(keys), [](const auto &k) { return k.subkey(0).isSecret() && !k.subkey(0).isCardKey(); })) { result |= Command::NeedSecretKeyData; } if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == OpenPGP; })) { result |= Command::MustBeOpenPGP; } else if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == CMS; })) { result |= Command::MustBeCMS; } if (Kleo::all_of(keys, [](const auto &key) { return !key.isBad(); })) { result |= Command::MustBeValid; } if (all_secret_are_not_owner_trust_ultimate(keys)) { result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate; } result |= find_root_restrictions(keys); if (const ReaderStatus *rs = ReaderStatus::instance()) { if (!rs->firstCardWithNullPin().empty()) { result |= Command::AnyCardHasNullPin; } } return result; } void KeyListController::Private::slotActionTriggered(QAction *sender) { const auto it = std::find_if(actions.cbegin(), actions.cend(), [sender](const action_item &item) { return item.action == sender; }); if (it != actions.end()) if (Command *const c = it->createCommand(this->currentView, q)) { if (parentWidget) { c->setParentWidget(parentWidget); } c->start(); } else qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" << qPrintable(sender->objectName()) << "\""; else { qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(sender->objectName()); } } int KeyListController::Private::toolTipOptions() const { using namespace Kleo::Formatting; static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage; static const int ownerFlags = Subject | UserIDs | OwnerTrust; static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint; const TooltipPreferences prefs; int flags = KeyID; flags |= prefs.showValidity() ? validityFlags : 0; flags |= prefs.showOwnerInformation() ? ownerFlags : 0; flags |= prefs.showCertificateDetails() ? detailsFlags : 0; return flags; } void KeyListController::updateConfig() { const int opts = d->toolTipOptions(); if (d->flatModel) { d->flatModel->setToolTipOptions(opts); } if (d->hierarchicalModel) { d->hierarchicalModel->setToolTipOptions(opts); } } #include "moc_keylistcontroller.cpp"