Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/refreshcertificatescommand.cpp b/src/commands/refreshcertificatescommand.cpp
index 4a60358b4..e0140bf49 100644
--- a/src/commands/refreshcertificatescommand.cpp
+++ b/src/commands/refreshcertificatescommand.cpp
@@ -1,456 +1,457 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "command_p.h"
#include "refreshcertificatescommand.h"
#include <settings.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyHelpers>
#include <KLocalizedString>
#include <KMessageBox>
#include <QGpgME/Protocol>
#include <QGpgME/ReceiveKeysJob>
#include <QGpgME/RefreshKeysJob>
#include <QGpgME/WKDRefreshJob>
#include <gpgme++/importresult.h>
#include <QProgressDialog>
#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<RefreshCertificatesCommand *>(q);
}
public:
explicit Private(RefreshCertificatesCommand *qq);
explicit Private(RefreshCertificatesCommand *qq, KeyListController *c);
~Private() override;
void start();
void cancel();
std::unique_ptr<QGpgME::ReceiveKeysJob> startKeyserverJob();
std::unique_ptr<QGpgME::RefreshKeysJob> startSMIMEJob();
std::unique_ptr<QGpgME::WKDRefreshJob> startWKDRefreshJob();
void onKeyserverJobResult(const ImportResult &result);
void onWKDRefreshJobResult(const ImportResult &result);
void onSMIMEJobResult(const Error &err);
void checkFinished();
private:
QPointer<QGpgME::Job> pgpJob;
QPointer<QGpgME::Job> smimeJob;
QPointer<QGpgME::Job> wkdJob;
std::vector<Key> pgpKeys;
std::vector<Key> smimeKeys;
std::vector<Key> wkdKeys;
ImportResult keyserverResult;
ImportResult wkdRefreshResult;
std::optional<Error> smimeError;
QPointer<QProgressDialog> progressDialog;
};
RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RefreshCertificatesCommand::Private *RefreshCertificatesCommand::d_func() const
{
return static_cast<const Private *>(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<QGpgME::Job> pgpRefreshJob;
std::unique_ptr<QGpgME::Job> smimeRefreshJob;
std::unique_ptr<QGpgME::Job> wkdRefreshJob;
auto keysByProtocol = Kleo::partitionKeysByProtocol(keys());
progressDialog = new QProgressDialog(i18ncp("@info", "Updating certificate…", "Updating %1 certificates…", keys().size()),
i18nc("@action:button", "Cancel"),
0,
0,
parentWidgetOrView());
+ progressDialog->setValue(0);
connect(progressDialog.get(), &QProgressDialog::canceled, q, &RefreshCertificatesCommand::cancel);
progressDialog->setMinimumDuration(500);
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)};
}
wkdRefreshJob = startWKDRefreshJob();
}
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();
delete progressDialog;
smimeError = Error::fromCode(GPG_ERR_CANCELED);
}
std::unique_ptr<QGpgME::ReceiveKeysJob> RefreshCertificatesCommand::Private::startKeyserverJob()
{
std::unique_ptr<QGpgME::ReceiveKeysJob> 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<QGpgME::RefreshKeysJob> RefreshCertificatesCommand::Private::startSMIMEJob()
{
std::unique_ptr<QGpgME::RefreshKeysJob> 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;
}
std::unique_ptr<QGpgME::WKDRefreshJob> RefreshCertificatesCommand::Private::startWKDRefreshJob()
{
std::unique_ptr<QGpgME::WKDRefreshJob> 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 which keys are eligible for WKD refresh, i.e. for which key a user ID has WKD as origin
for (const auto &key : pgpKeys) {
if (Kleo::any_of(key.userIDs(), [](const auto &userId) {
return !userId.isRevoked() && !userId.addrSpec().empty() && userId.origin() == Key::OriginWKD;
})) {
wkdKeys.push_back(key);
}
}
if (!wkdKeys.empty()) {
err = refreshJob->start(wkdKeys);
}
} else {
std::vector<UserID> userIds;
for (const auto &key : pgpKeys) {
bool userIdAdded = false;
Kleo::copy_if(key.userIDs(), std::back_inserter(userIds), [&userIdAdded](const auto &userId) {
const bool addUserId = !userId.isRevoked() && !userId.addrSpec().empty();
userIdAdded |= addUserId;
return addUserId;
});
if (userIdAdded) {
wkdKeys.push_back(key);
}
}
if (!userIds.empty()) {
err = refreshJob->start(userIds);
}
}
if (wkdKeys.empty()) {
wkdRefreshResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)};
QMetaObject::invokeMethod(
q,
[this]() {
checkFinished();
},
Qt::QueuedConnection);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Updating key..."));
return refreshJob;
}
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{"<br><br>"} + details.join(QLatin1StringView{"<br>"});
}
}
text = QLatin1StringView{"<p>"} + text + QLatin1StringView{"</p>"};
if (result.numImported() > 0) {
text += QLatin1StringView{"<p>"}
+ i18np("Additionally, one new key has been retrieved.", "Additionally, %1 new keys have been retrieved.", result.numImported())
+ QLatin1StringView{"</p>"};
}
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;
}
delete progressDialog;
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.empty()) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of OpenPGP certificate update from keyserver, LDAP server, or Active Directory")
+ QLatin1StringView{"</strong></p>"};
if (hasPgpError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(keyserverResult.error()));
} else if (pgpSkipped) {
text += xi18nc("@info", "<para>Update skipped because no OpenPGP keyserver is configured.</para>");
} else if (pgpKeyNotFound) {
text += xi18ncp("@info", "<para>The certificate was not found.</para>", "<para>The certificates were not found.</para>", pgpKeys.size());
} else if (pgpKeys.size() > 1) {
success = true;
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", pgpKeys.size());
} else if (pgpKeys.size() == 1) {
success = true;
text += informationOnChanges(keyserverResult);
}
}
if (!wkdKeys.empty() && !wkdSkipped) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of update from Web Key Directory") + QLatin1StringView{"</strong></p>"};
if (hasWkdError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(wkdRefreshResult.error()));
} else if (wkdRefreshResult.numConsidered() == 0) {
// explicitly use pgpKeys.size() also for WKD to avoid confusion caused by different plural forms for keyserver result and WKD result
text += xi18ncp("@info", "<para>The certificate was not found.</para>", "<para>The certificates were not found.</para>", pgpKeys.size());
} else if (pgpKeys.size() > 1) {
success = true;
// explicitly use pgpKeys.size() also for WKD to avoid confusion caused by different plural forms for keyserver result and WKD result
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", pgpKeys.size());
} else if (pgpKeys.size() == 1) {
success = true;
text += informationOnChanges(wkdRefreshResult);
}
}
if (!smimeKeys.empty()) {
text += QLatin1StringView{"<p><strong>"} + i18nc("@info", "Result of S/MIME certificate update") + QLatin1StringView{"</strong></p>"};
if (hasSmimeError) {
text += xi18nc("@info", "<para>Update failed:</para><para><message>%1</message></para>", Formatting::errorAsString(*smimeError));
} else {
success = true;
text += xi18ncp("@info", "<para>The certificate was updated.</para>", "<para>The certificates were updated.</para>", 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"

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 3, 3:43 AM (1 d, 22 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c5/c4/614cc5070f28feb2206bfd5c2e7b

Event Timeline