diff --git a/src/commands/refreshcertificatescommand.cpp b/src/commands/refreshcertificatescommand.cpp index acb118bfb..1db7b17e7 100644 --- a/src/commands/refreshcertificatescommand.cpp +++ b/src/commands/refreshcertificatescommand.cpp @@ -1,437 +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 checkFinished(); private: QPointer pgpJob; QPointer smimeJob; QPointer wkdJob; std::vector pgpKeys; std::vector smimeKeys; std::vector wkdKeys; 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); 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); 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() { 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); } } 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()) { // 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); } 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 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 certificate has been revoked."); } else { // it doesn't make much sense to list below details if the key has been revoked 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{"

"}; 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) { keyserverResult = result; if (result.error().isCanceled()) { pgpJob.clear(); finished(); return; } pgpJob.clear(); checkFinished(); } void RefreshCertificatesCommand::Private::onWKDRefreshJobResult(const ImportResult &result) { wkdRefreshResult = result; if (result.error().isCanceled()) { pgpJob.clear(); finished(); return; } wkdJob.clear(); checkFinished(); } void RefreshCertificatesCommand::Private::onSMIMEJobResult(const Error &error) { smimeError = error; if (error.isCanceled()) { smimeJob.clear(); finished(); return; } smimeJob.clear(); checkFinished(); } 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; } 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) { + if (!pgpKeys.empty()) { 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) { + if (!wkdKeys.empty() && !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 (smimeKeys.size() > 0) { + if (!smimeKeys.empty()) { 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 { 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"