diff --git a/src/commands/refreshcertificatescommand.cpp b/src/commands/refreshcertificatescommand.cpp index edbc0ea25..acb118bfb 100644 --- a/src/commands/refreshcertificatescommand.cpp +++ b/src/commands/refreshcertificatescommand.cpp @@ -1,491 +1,437 @@ /* -*- 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 startKeyserverJob(); std::unique_ptr startSMIMEJob(); #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr startWKDRefreshJob(); #endif + 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; + std::vector wkdKeys; - ImportResult receiveKeysResult; + ImportResult keyserverResult; 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; auto keysByProtocol = Kleo::partitionKeysByProtocol(keys()); pgpKeys = keysByProtocol.openpgp; smimeKeys = keysByProtocol.cms; if (!smimeKeys.empty()) { smimeRefreshJob = startSMIMEJob(); } if (!pgpKeys.empty()) { if (haveKeyserverConfigured()) { pgpRefreshJob = startKeyserverJob(); + } else { + keyserverResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)}; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB wkdRefreshJob = startWKDRefreshJob(); #endif } 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(); wkdJob.clear(); smimeError = Error::fromCode(GPG_ERR_CANCELED); } 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) { onKeyserverJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - const GpgME::Error err = refreshJob->start(Kleo::getFingerprints(pgpKeys)); - if (err) { - showError(err); - return {}; - } + refreshJob->start(Kleo::getFingerprints(pgpKeys)); + Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } 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(smimeKeys); - if (err) { - showError(err); - return {}; - } + refreshJob->start(smimeKeys); + 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); - } + for (const auto &key : pgpKeys) { + 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()) { + + 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)}; + QMetaObject::invokeMethod( + q, + [this]() { + checkFinished(); + }, + Qt::QueuedConnection); + return {}; + } + err = refreshJob->start(wkdKeys); + } else { std::vector userIds; + wkdKeys = pgpKeys; 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."); + text = i18n("The certificate has not 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."); + text = i18n("The certificate 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."); + text = i18n("The certificate 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{"

"}; + // 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::onKeyserverJobResult(const ImportResult &result) { - receiveKeysResult = result; + keyserverResult = result; if (result.error().isCanceled()) { 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() == 1) { - information(informationOnChanges(receiveKeysResult), i18nc("@title:window", "Key Updated")); - } pgpJob.clear(); checkFinished(); -#endif } void RefreshCertificatesCommand::Private::onWKDRefreshJobResult(const ImportResult &result) { wkdRefreshResult = result; - wkdJob.clear(); - showOpenPGPResult(); -} - -void RefreshCertificatesCommand::Private::onSMIMEJobResult(const Error &err) -{ - smimeError = err; - if (err) { - showError(err); + if (result.error().isCanceled()) { + pgpJob.clear(); + finished(); + return; } - smimeJob.clear(); + + wkdJob.clear(); checkFinished(); } -void RefreshCertificatesCommand::Private::showOpenPGPResult() +void RefreshCertificatesCommand::Private::onSMIMEJobResult(const Error &error) { - 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.clear(); - checkFinished(); - return; - } + smimeError = error; - 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.clear(); - checkFinished(); + if (error.isCanceled()) { + smimeJob.clear(); + finished(); 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.clear(); + smimeJob.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; + const auto pgpSkipped = keyserverResult.error().code() == GPG_ERR_USER_1; + const auto pgpKeyNotFound = keyserverResult.error().code() == GPG_ERR_NO_DATA; + const auto wkdSkipped = wkdRefreshResult.error().code() == GPG_ERR_USER_1; + + const auto hasSmimeError = smimeError && *smimeError; + const auto hasPgpError = !keyserverResult.isNull() && keyserverResult.error() && !pgpSkipped && !pgpKeyNotFound; + const auto hasWkdError = !wkdRefreshResult.isNull() && wkdRefreshResult.error() && !wkdSkipped; + + bool success = false; + QString text; + + if (pgpKeys.size() > 0) { + text += QLatin1StringView{"

"} + i18nc("@info", "Result of update from keyserver") + QLatin1String{"

"}; + if (hasPgpError) { + text += xi18nc("@info", + "Certificate update failed:%1", + Formatting::errorAsString(keyserverResult.error())); + } else if (pgpSkipped) { + text += xi18nc("@info", "The OpenPGP keyserver lookup was skipped because no keyserver is configured."); + } else if (pgpKeyNotFound && pgpKeys.size() == 1) { + text += xi18nc("@info", "The certificate was not found on the OpenPGP keyserver."); + } else if (pgpKeys.size() > 1) { + success = true; + text += xi18ncp("@info", "The certificate was updated.", "The certificates were updated.", pgpKeys.size()); + } else if (pgpKeys.size() == 1) { + success = true; + text += informationOnChanges(keyserverResult); + } + } + + if (wkdKeys.size() > 0 && !wkdSkipped) { + text += QLatin1StringView{"

"} + i18nc("@info", "Result of update from web key directory") + QLatin1String{"

"}; + if (hasWkdError) { + text += xi18nc("@info", + "The web key directory lookup failed:%1", + Formatting::errorAsString(wkdRefreshResult.error())); + } else { + success = true; + text += xi18ncp("@info", "The certificate was updated.", "The certificates were updated.", pgpKeys.size()); + } } - 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")); + if (smimeKeys.size() > 0) { + text += QLatin1StringView{"

"} + i18nc("@info", "Result of S/MIME certificate update") + QLatin1String{"

"}; + if (hasSmimeError) { + text += xi18nc("@info", "The certificate update failed:%1", Formatting::errorAsString(*smimeError)); } else { - information(i18ncp("@info", "The OpenPGP certificate has been updated.", "The OpenPGP certificates have been updated", keys().size()), - i18nc("@title:window", "Certificate Updated")); + success = true; + text += xi18ncp("@info", "The certificate was updated.", "The certificates were updated.", smimeKeys.size()); } } + + information(text, + success ? i18ncp("@title:window", "Certificate Updated", "Certificates Updated", keys().size()) : i18nc("@title:window", "Update Failed")); finished(); } #undef d #undef q #include "moc_refreshcertificatescommand.cpp"