diff --git a/src/commands/command_p.h b/src/commands/command_p.h
index 0d58868ab..27846b343 100644
--- a/src/commands/command_p.h
+++ b/src/commands/command_p.h
@@ -1,121 +1,119 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/command_p.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "command.h"
#include "view/keylistcontroller.h"
#include
#include
#include
#include
#include
-#include
-#include
#include
#include
#include
class Kleo::Command::Private
{
friend class ::Kleo::Command;
protected:
Command *const q;
public:
explicit Private(Command *qq);
explicit Private(Command *qq, KeyListController *controller);
explicit Private(Command *qq, QWidget *parent);
virtual ~Private();
QAbstractItemView *view() const
{
return view_;
}
QWidget *parentWidgetOrView() const
{
if (parentWidget_) {
return parentWidget_;
} else {
return view_;
}
}
WId parentWId() const
{
return parentWId_;
}
GpgME::Key key() const
{
return keys_.empty() ? GpgME::Key{} : keys_.front();
}
std::vector keys() const
{
return keys_;
}
void finished()
{
Q_EMIT q->finished();
if (autoDelete) {
q->deleteLater();
}
}
void canceled()
{
Q_EMIT q->canceled();
finished();
}
void error(const QString &text, const QString &caption = QString(), KMessageBox::Options options = KMessageBox::Notify) const
{
if (parentWId_) {
KMessageBox::errorWId(parentWId_, text, caption, options);
} else {
KMessageBox::error(parentWidgetOrView(), text, caption, options);
}
}
void success(const QString &text, const QString &caption = {}, KMessageBox::Options options = KMessageBox::Notify) const
{
static const QString noDontShowAgainName{};
const QString title = caption.isEmpty() ? i18nc("@title:window", "Success") : caption;
if (parentWId_) {
KMessageBox::informationWId(parentWId_, text, title, noDontShowAgainName, options);
} else {
KMessageBox::information(parentWidgetOrView(), text, title, noDontShowAgainName, options);
}
}
void information(const QString &text, const QString &caption = QString(), const QString &dontShowAgainName = QString(), KMessageBox::Options options = KMessageBox::Notify) const
{
if (parentWId_) {
KMessageBox::informationWId(parentWId_, text, caption, dontShowAgainName, options);
} else {
KMessageBox::information(parentWidgetOrView(), text, caption, dontShowAgainName, options);
}
}
void applyWindowID(QWidget *w) const
{
q->applyWindowID(w);
}
private:
bool autoDelete : 1;
bool warnWhenRunningAtShutdown : 1;
std::vector keys_;
QPointer view_;
QPointer parentWidget_;
WId parentWId_ = 0;
QPointer controller_;
};
diff --git a/src/commands/exportgroupscommand.cpp b/src/commands/exportgroupscommand.cpp
index a53375af7..6de8b1bc0 100644
--- a/src/commands/exportgroupscommand.cpp
+++ b/src/commands/exportgroupscommand.cpp
@@ -1,298 +1,297 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportgroupscommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "exportgroupscommand.h"
#include "command_p.h"
#include
#include "utils/filedialog.h"
#include
#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
namespace
{
static const QString certificateGroupFileExtension{QLatin1String{".kgrp"}};
QString proposeFilename(const std::vector &groups)
{
QString filename;
filename = ApplicationState::lastUsedExportDirectory() + QLatin1Char{'/'};
if (groups.size() == 1) {
filename += groups.front().name().replace(QLatin1Char{'/'}, QLatin1Char{'_'});
} else {
filename += i18nc("A generic filename for exported certificate groups", "certificate groups");
}
return filename + certificateGroupFileExtension;
}
QString requestFilename(QWidget *parent, const std::vector &groups)
{
const QString proposedFilename = proposeFilename(groups);
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18ncp("@title:window", "Export Certificate Group", "Export Certificate Groups", groups.size()),
QStringLiteral("imp"),
proposedFilename,
i18nc("filename filter like Certificate Groups (*.foo)", "Certificate Groups (*%1)", certificateGroupFileExtension));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += certificateGroupFileExtension;
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
}
class ExportGroupsCommand::Private : public Command::Private
{
friend class ::ExportGroupsCommand;
ExportGroupsCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(ExportGroupsCommand *qq);
~Private() override;
void start();
bool exportGroups();
bool startExportJob(GpgME::Protocol protocol, const std::vector &keys);
void onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData);
void cancelJobs();
void showError(const GpgME::Error &err);
void finishedIfLastJob(const QGpgME::Job *job);
private:
std::vector groups;
QString filename;
std::vector> exportJobs;
};
ExportGroupsCommand::Private *ExportGroupsCommand::d_func()
{
return static_cast(d.get());
}
const ExportGroupsCommand::Private *ExportGroupsCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
ExportGroupsCommand::Private::Private(ExportGroupsCommand *qq)
: Command::Private(qq)
{
}
ExportGroupsCommand::Private::~Private() = default;
void ExportGroupsCommand::Private::start()
{
if (groups.empty()) {
finished();
return;
}
filename = requestFilename(parentWidgetOrView(), groups);
if (filename.isEmpty()) {
canceled();
return;
}
const auto groupKeys = std::accumulate(std::begin(groups), std::end(groups),
KeyGroup::Keys{},
[](auto allKeys, const auto &group) {
const auto keys = group.keys();
allKeys.insert(std::begin(keys), std::end(keys));
return allKeys;
});
std::vector openpgpKeys;
std::vector cmsKeys;
std::partition_copy(std::begin(groupKeys), std::end(groupKeys),
std::back_inserter(openpgpKeys),
std::back_inserter(cmsKeys),
[](const GpgME::Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
// remove/overwrite existing file
if (QFile::exists(filename) && !QFile::remove(filename)) {
error(xi18n("Cannot overwrite existing %1 .", filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
if (!exportGroups()) {
finished();
return;
}
if (!openpgpKeys.empty()) {
if (!startExportJob(GpgME::OpenPGP, openpgpKeys)) {
finished();
return;
}
}
if (!cmsKeys.empty()) {
if (!startExportJob(GpgME::CMS, cmsKeys)) {
finishedIfLastJob(nullptr);
}
}
}
bool ExportGroupsCommand::Private::exportGroups()
{
const auto result = writeKeyGroups(filename, groups);
if (result != WriteKeyGroups::Success) {
error(xi18n("Writing groups to file %1 failed.", filename),
i18nc("@title:window", "Export Failed"));
}
return result == WriteKeyGroups::Success;
}
bool ExportGroupsCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector &keys)
{
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr jobOwner(backend->publicKeyExportJob(/*armor=*/ true));
auto job = jobOwner.get();
Q_ASSERT(job);
connect(job, &ExportJob::result,
q, [this, job](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(job, err, keyData);
});
connect(job, &Job::progress,
q, &Command::progress);
const GpgME::Error err = job->start(Kleo::getFingerprints(keys));
if (err) {
showError(err);
return false;
}
Q_EMIT q->info(i18n("Exporting certificate groups..."));
exportJobs.push_back(jobOwner.release());
return true;
}
void ExportGroupsCommand::Private::onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData)
{
Q_ASSERT(Kleo::contains(exportJobs, job));
if (err) {
showError(err);
finishedIfLastJob(job);
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) {
error(xi18n("Cannot open file %1 for writing.", filename),
i18nc("@title:window", "Export Failed"));
finishedIfLastJob(job);
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18n("Writing certificates to file %1 failed.", filename),
i18nc("@title:window", "Export Failed"));
}
finishedIfLastJob(job);
}
void ExportGroupsCommand::Private::showError(const GpgME::Error &err)
{
error(xi18n("An error occurred during the export: "
"%1 ",
QString::fromLocal8Bit(err.asString())),
i18nc("@title:window", "Export Failed"));
}
void ExportGroupsCommand::Private::finishedIfLastJob(const QGpgME::Job *job)
{
if (job) {
exportJobs.erase(std::remove(exportJobs.begin(), exportJobs.end(), job), exportJobs.end());
}
if (exportJobs.size() == 0) {
finished();
}
}
void ExportGroupsCommand::Private::cancelJobs()
{
std::for_each(std::cbegin(exportJobs), std::cend(exportJobs),
[](const auto &job) {
if (job) {
job->slotCancel();
}
});
exportJobs.clear();
}
ExportGroupsCommand::ExportGroupsCommand(const std::vector &groups)
: Command{new Private{this}}
{
d->groups = groups;
}
ExportGroupsCommand::~ExportGroupsCommand() = default;
void ExportGroupsCommand::doStart()
{
d->start();
}
void ExportGroupsCommand::doCancel()
{
d->cancelJobs();
}
#undef d
#undef q
#include "moc_exportgroupscommand.cpp"
diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp
index aa0fea99c..ba5e733aa 100644
--- a/src/commands/exportsecretkeycommand.cpp
+++ b/src/commands/exportsecretkeycommand.cpp
@@ -1,312 +1,311 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretkeycommand.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 "exportsecretkeycommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include
#include "utils/filedialog.h"
#include
#include
-#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace
{
QString openPGPCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate,
FileOperationsPreferences().usePGPFileExt())};
}
QString cmsCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM,
/*usePGPFileExt=*/false)};
}
QString certificateFileExtension(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return openPGPCertificateFileExtension();
case GpgME::CMS:
return cmsCertificateFileExtension();
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return QStringLiteral("txt");
}
}
QString proposeFilename(const Key &key)
{
QString filename;
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
/* Not translated so it's better to use in tutorials etc. */
filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID);
filename.replace(u'/', u'_');
return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol());
}
QString secretKeyFileFilters(GpgME::Protocol protocol)
{
switch (protocol) {
case GpgME::OpenPGP:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"};
case GpgME::CMS:
return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.p12)"};
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol;
return i18nc("description of filename filter", "All Files") + QLatin1String{" (*)"};
}
}
QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18nc("@title:window", "Secret Key Backup"),
QStringLiteral("imp"),
proposedFilename,
secretKeyFileFilters(key.protocol()));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += u'.' + certificateFileExtension(key.protocol());
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
QString errorCaption()
{
return i18nc("@title:window", "Secret Key Backup Error");
}
}
class ExportSecretKeyCommand::Private : public Command::Private
{
friend class ::ExportSecretKeyCommand;
ExportSecretKeyCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr startExportJob(const Key &key);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
QString filename;
QPointer job;
};
ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func()
{
return static_cast(d.get());
}
const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ExportSecretKeyCommand::Private::~Private() = default;
void ExportSecretKeyCommand::Private::start()
{
const Key key = this->key();
if (key.isNull()) {
finished();
return;
}
filename = requestFilename(key, proposeFilename(key), parentWidgetOrView());
if (filename.isEmpty()) {
canceled();
return;
}
auto exportJob = startExportJob(key);
if (!exportJob) {
finished();
return;
}
job = exportJob.release();
}
void ExportSecretKeyCommand::Private::cancel()
{
if (job) {
job->slotCancel();
}
job.clear();
}
std::unique_ptr ExportSecretKeyCommand::Private::startExportJob(const Key &key)
{
#ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT
const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive);
const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr exportJob{backend->secretKeyExportJob(armor)};
Q_ASSERT(exportJob);
if (key.protocol() == GpgME::CMS) {
exportJob->setExportFlags(GpgME::Context::ExportPKCS12);
}
connect(exportJob.get(), &QGpgME::ExportJob::result,
q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
connect(exportJob.get(), &QGpgME::Job::progress,
q, &Command::progress);
const GpgME::Error err = exportJob->start({QLatin1String{key.primaryFingerprint()}});
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Backing up secret key..."));
return exportJob;
#else
Q_UNUSED(key)
return {};
#endif
}
void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData)
{
if (err.isCanceled()) {
finished();
return;
}
if (err) {
showError(err);
finished();
return;
}
if (keyData.isEmpty()) {
error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."),
errorCaption());
finished();
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open file %1 for writing.", filename),
errorCaption());
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18nc("@info", "Writing key to file %1 failed.", filename),
errorCaption());
finished();
return;
}
information(i18nc("@info", "The backup of the secret key was created successfully."),
i18nc("@title:window", "Secret Key Backup"));
finished();
}
void ExportSecretKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"An error occurred during the backup of the secret key: "
"%1 ",
QString::fromLocal8Bit(err.asString())),
errorCaption());
}
ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller)
: Command{view, new Private{this, controller}}
{
}
ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
ExportSecretKeyCommand::~ExportSecretKeyCommand() = default;
void ExportSecretKeyCommand::doStart()
{
d->start();
}
void ExportSecretKeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_exportsecretkeycommand.cpp"
diff --git a/src/commands/exportsecretsubkeycommand.cpp b/src/commands/exportsecretsubkeycommand.cpp
index e9905603f..ca93f2271 100644
--- a/src/commands/exportsecretsubkeycommand.cpp
+++ b/src/commands/exportsecretsubkeycommand.cpp
@@ -1,292 +1,290 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretsubkeycommand.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 "exportsecretsubkeycommand.h"
#include "command_p.h"
#include "fileoperationspreferences.h"
#include
#include "utils/filedialog.h"
#include
#include
-#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
using namespace Kleo;
using namespace GpgME;
namespace
{
#ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
QString openPGPCertificateFileExtension()
{
return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate,
FileOperationsPreferences().usePGPFileExt())};
}
QString proposeFilename(const std::vector &subkeys)
{
QString filename;
if (subkeys.size() == 1) {
const auto subkey = subkeys.front();
const auto key = subkey.parent();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID());
const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData());
const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"});
/* Not translated so it's better to use in tutorials etc. */
filename = QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg(
name, shortKeyID, shortSubkeyID, usage);
} else {
filename = i18nc("Generic filename for exported subkeys", "subkeys");
}
filename.replace(u'/', u'_');
return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension();
}
QString requestFilename(const std::vector &subkeys, const QString &proposedFilename, QWidget *parent)
{
auto filename = FileDialog::getSaveFileNameEx(
parent,
i18ncp("@title:window", "Export Subkey", "Export Subkeys", subkeys.size()),
QStringLiteral("imp"),
proposedFilename,
i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"});
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += u'.' + openPGPCertificateFileExtension();
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
template
QStringList getSubkeyFingerprints(const SubkeyContainer &subkeys)
{
QStringList fingerprints;
fingerprints.reserve(subkeys.size());
std::transform(std::begin(subkeys), std::end(subkeys),
std::back_inserter(fingerprints),
[](const auto &subkey) {
return QLatin1String{subkey.fingerprint()} + u'!';
});
return fingerprints;
}
#endif
}
class ExportSecretSubkeyCommand::Private : public Command::Private
{
friend class ::ExportSecretSubkeyCommand;
ExportSecretSubkeyCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(ExportSecretSubkeyCommand *qq);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr startExportJob(const std::vector &subkeys);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
std::vector subkeys;
QString filename;
QPointer job;
};
ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func()
{
return static_cast(d.get());
}
const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
ExportSecretSubkeyCommand::Private::Private(ExportSecretSubkeyCommand *qq)
: Command::Private{qq}
{
}
ExportSecretSubkeyCommand::Private::~Private() = default;
void ExportSecretSubkeyCommand::Private::start()
{
#ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
if (subkeys.empty()) {
finished();
return;
}
filename = requestFilename(subkeys, proposeFilename(subkeys), parentWidgetOrView());
if (filename.isEmpty()) {
canceled();
return;
}
auto exportJob = startExportJob(subkeys);
if (!exportJob) {
finished();
return;
}
job = exportJob.release();
#else
Q_ASSERT(!"This command is not supported by the backend it was compiled against");
finished();
return;
#endif
}
void ExportSecretSubkeyCommand::Private::cancel()
{
if (job) {
job->slotCancel();
}
job.clear();
}
std::unique_ptr ExportSecretSubkeyCommand::Private::startExportJob(const std::vector &subkeys)
{
#ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive);
std::unique_ptr exportJob{QGpgME::openpgp()->secretSubkeyExportJob(armor)};
Q_ASSERT(exportJob);
connect(exportJob.get(), &QGpgME::ExportJob::result,
q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
connect(exportJob.get(), &QGpgME::Job::progress,
q, &Command::progress);
const GpgME::Error err = exportJob->start(getSubkeyFingerprints(subkeys));
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Exporting subkeys..."));
return exportJob;
#else
Q_UNUSED(subkeys)
return {};
#endif
}
void ExportSecretSubkeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData)
{
if (err) {
showError(err);
finished();
return;
}
if (keyData.isEmpty()) {
error(i18nc("@info", "The result of the export is empty."),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly)) {
error(xi18nc("@info", "Cannot open file %1 for writing.", filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18ncp("@info",
"Writing subkey to file %2 failed.",
"Writing subkeys to file %2 failed.",
subkeys.size(), filename),
i18nc("@title:window", "Export Failed"));
finished();
return;
}
information(i18ncp("@info",
"The subkey was exported successfully.",
"%1 subkeys were exported successfully.",
subkeys.size()),
i18nc("@title:window", "Secret Key Backup"));
finished();
}
void ExportSecretSubkeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"An error occurred during the export: "
"%1 ",
QString::fromLocal8Bit(err.asString())),
i18nc("@title:window", "Export Failed"));
}
ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector &subkeys)
: Command{new Private{this}}
{
d->subkeys = subkeys;
}
ExportSecretSubkeyCommand::~ExportSecretSubkeyCommand() = default;
void ExportSecretSubkeyCommand::doStart()
{
d->start();
}
void ExportSecretSubkeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_exportsecretsubkeycommand.cpp"
diff --git a/src/commands/importcertificatefromfilecommand.cpp b/src/commands/importcertificatefromfilecommand.cpp
index 53ebe9f9e..5e9f6e8c1 100644
--- a/src/commands/importcertificatefromfilecommand.cpp
+++ b/src/commands/importcertificatefromfilecommand.cpp
@@ -1,174 +1,173 @@
/* -*- mode: c++; c-basic-offset:4 -*-
importcertificatefromfilecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "importcertificatefromfilecommand.h"
#include "importcertificatescommand_p.h"
#include
-#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
class ImportCertificateFromFileCommand::Private : public ImportCertificatesCommand::Private
{
friend class ::ImportCertificateFromFileCommand;
ImportCertificateFromFileCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(ImportCertificateFromFileCommand *qq, KeyListController *c);
~Private() override;
bool ensureHaveFile();
private:
QStringList files;
};
ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func()
{
return static_cast(d.get());
}
const ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func() const
{
return static_cast(d.get());
}
ImportCertificateFromFileCommand::Private::Private(ImportCertificateFromFileCommand *qq, KeyListController *c)
: ImportCertificatesCommand::Private(qq, c),
files()
{
}
ImportCertificateFromFileCommand::Private::~Private() {}
#define d d_func()
#define q q_func()
ImportCertificateFromFileCommand::ImportCertificateFromFileCommand()
: ImportCertificatesCommand(new Private(this, nullptr))
{
}
ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(KeyListController *p)
: ImportCertificatesCommand(new Private(this, p))
{
}
ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(QAbstractItemView *v, KeyListController *p)
: ImportCertificatesCommand(v, new Private(this, p))
{
}
ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, KeyListController *p)
: ImportCertificatesCommand(new Private(this, p))
{
d->files = files;
}
ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, QAbstractItemView *v, KeyListController *p)
: ImportCertificatesCommand(v, new Private(this, p))
{
d->files = files;
}
ImportCertificateFromFileCommand::~ImportCertificateFromFileCommand() {}
void ImportCertificateFromFileCommand::setFiles(const QStringList &files)
{
d->files = files;
}
void ImportCertificateFromFileCommand::doStart()
{
if (!d->ensureHaveFile()) {
Q_EMIT canceled();
d->finished();
return;
}
d->setProgressWindowTitle(i18nc("@title:window", "Importing Certificates"));
d->setProgressLabelText(i18np("Importing certificates from 1 file...",
"Importing certificates from %1 files...",
d->files.size()));
//TODO: use KIO here
d->setWaitForMoreJobs(true);
for (const QString &fn : std::as_const(d->files)) {
QFile in(fn);
if (!in.open(QIODevice::ReadOnly)) {
d->error(i18n("Could not open file %1 for reading: %2", in.fileName(), in.errorString()), i18n("Certificate Import Failed"));
d->importResult({fn, GpgME::UnknownProtocol, ImportType::Local, ImportResult{}});
continue;
}
const auto data = in.readAll();
d->startImport(GpgME::OpenPGP, data, fn);
d->startImport(GpgME::CMS, data, fn);
d->importGroupsFromFile(fn);
}
d->setWaitForMoreJobs(false);
}
static QStringList get_file_name(QWidget *parent)
{
const QString certificateFilter = i18n("Certificates") + QLatin1String(" (*.asc *.cer *.cert *.crt *.der *.pem *.gpg *.p7c *.p12 *.pfx *.pgp *.kgrp)");
const QString anyFilesFilter = i18n("Any files") + QLatin1String(" (*)");
QString previousDir;
if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) {
const KConfigGroup group(config, "Import Certificate");
previousDir = group.readPathEntry("last-open-file-directory", QDir::homePath());
}
const QStringList files = QFileDialog::getOpenFileNames(parent, i18n("Select Certificate File"), previousDir, certificateFilter + QLatin1String(";;") + anyFilesFilter);
if (!files.empty())
if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) {
KConfigGroup group(config, "Import Certificate");
group.writePathEntry("last-open-file-directory", QFileInfo(files.front()).path());
}
return files;
}
bool ImportCertificateFromFileCommand::Private::ensureHaveFile()
{
if (files.empty()) {
files = get_file_name(parentWidgetOrView());
}
return !files.empty();
}
#undef d
#undef q
diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp
index 56dd26a97..4d0397630 100644
--- a/src/commands/importcertificatescommand.cpp
+++ b/src/commands/importcertificatescommand.cpp
@@ -1,1046 +1,1045 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/importcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "importcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "certifycertificatecommand.h"
#include
#include
#include "kleopatra_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
#include
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include // for Qt::escape
#include
#include
#include
#include
using namespace GpgME;
using namespace Kleo;
using namespace QGpgME;
bool operator==(const ImportJobData &lhs, const ImportJobData &rhs)
{
return lhs.job == rhs.job;
}
namespace
{
make_comparator_str(ByImportFingerprint, .fingerprint());
class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ImportResultProxyModel(const std::vector &results, QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
updateFindCache(results);
}
~ImportResultProxyModel() override {}
ImportResultProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ImportResultProxyModel(*this);
}
void setImportResults(const std::vector &results)
{
updateFindCache(results);
invalidateFilter();
}
protected:
QVariant data(const QModelIndex &index, int role) const override
{
if (!index.isValid() || role != Qt::ToolTipRole) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
}
const QString fpr = index.data(KeyList::FingerprintRole).toString();
// find information:
const std::vector::const_iterator it
= Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint());
if (it == m_importsByFingerprint.end()) {
return AbstractKeyListSortFilterProxyModel::data(index, role);
} else {
QStringList rv;
const auto ids = m_idsByFingerprint[it->fingerprint()];
rv.reserve(ids.size());
std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv));
return Formatting::importMetaData(*it, rv);
}
}
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
{
//
// 0. Keep parents of matching children:
//
const QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
Q_ASSERT(index.isValid());
for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i)
if (filterAcceptsRow(i, index)) {
return true;
}
//
// 1. Check that this is an imported key:
//
const QString fpr = index.data(KeyList::FingerprintRole).toString();
return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
fpr.toLatin1().constData(),
ByImportFingerprint());
}
private:
void updateFindCache(const std::vector &results)
{
m_importsByFingerprint.clear();
m_idsByFingerprint.clear();
m_results = results;
for (const auto &r : results) {
const std::vector imports = r.result.imports();
m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end());
for (std::vector::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) {
m_idsByFingerprint[it->fingerprint()].insert(r.id);
}
}
std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(),
ByImportFingerprint());
}
private:
mutable std::vector m_importsByFingerprint;
mutable std::map< const char *, std::set, ByImportFingerprint > m_idsByFingerprint;
std::vector m_results;
};
bool importFailed(const ImportResultData &r)
{
// ignore GPG_ERR_EOF error to handle the "failed" import of files
// without X.509 certificates by gpgsm gracefully
return r.result.error() && r.result.error().code() != GPG_ERR_EOF;
}
bool importWasCanceled(const ImportResultData &r)
{
return r.result.error().isCanceled();
}
}
ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, progressWindowTitle{i18nc("@title:window", "Importing Certificates")}
, progressLabelText{i18n("Importing certificates... (this can take a while)")}
{
}
ImportCertificatesCommand::Private::~Private()
{
if (progressDialog) {
delete progressDialog;
}
}
#define d d_func()
#define q q_func()
ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ImportCertificatesCommand::~ImportCertificatesCommand() = default;
static QString format_ids(const std::vector &ids)
{
QStringList escapedIds;
for (const QString &id : ids) {
if (!id.isEmpty()) {
escapedIds << id.toHtmlEscaped();
}
}
return escapedIds.join(QLatin1String(" "));
}
static QString make_tooltip(const std::vector &results)
{
if (results.empty()) {
return {};
}
std::vector ids;
ids.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(ids),
[](const auto &r) { return r.id; });
std::sort(std::begin(ids), std::end(ids));
ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids));
if (ids.size() == 1)
if (ids.front().isEmpty()) {
return {};
} else
return i18nc("@info:tooltip",
"Imported Certificates from %1",
ids.front().toHtmlEscaped());
else
return i18nc("@info:tooltip",
"Imported certificates from these sources: %1",
format_ids(ids));
}
void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector &results)
{
if (std::none_of(std::begin(results), std::end(results),
[](const auto &r) { return r.result.numConsidered() > 0; })) {
return;
}
q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"),
new ImportResultProxyModel(results),
make_tooltip(results));
if (QTreeView *const tv = qobject_cast(parentWidgetOrView())) {
tv->expandAll();
}
}
int sum(const std::vector &res, int (ImportResult::*fun)() const)
{
return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0);
}
static QString make_report(const std::vector &results,
const std::vector &groups)
{
const KLocalizedString normalLine = ki18n("%1 %2 ");
const KLocalizedString boldLine = ki18n("%1 %2 ");
const KLocalizedString headerLine = ki18n("%1 ");
std::vector res;
res.reserve(results.size());
std::transform(std::begin(results), std::end(results),
std::back_inserter(res),
[](const auto &r) { return r.result; });
const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered);
QStringList lines;
if (numProcessedCertificates > 0 || groups.size() == 0) {
lines.push_back(headerLine.subs(i18n("Certificates")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(numProcessedCertificates).toString());
lines.push_back(normalLine.subs(i18n("Imported:"))
.subs(sum(res, &ImportResult::numImported)).toString());
if (const int n = sum(res, &ImportResult::newSignatures))
lines.push_back(normalLine.subs(i18n("New signatures:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newUserIDs))
lines.push_back(normalLine.subs(i18n("New user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numKeysWithoutUserID))
lines.push_back(normalLine.subs(i18n("Certificates without user IDs:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newSubkeys))
lines.push_back(normalLine.subs(i18n("New subkeys:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::newRevocations))
lines.push_back(boldLine.subs(i18n("Newly revoked:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::notImported))
lines.push_back(boldLine.subs(i18n("Not imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numUnchanged))
lines.push_back(normalLine.subs(i18n("Unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered))
lines.push_back(normalLine.subs(i18n("Secret keys processed:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysImported))
lines.push_back(normalLine.subs(i18n("Secret keys imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported) - sum(res, &ImportResult::numSecretKeysUnchanged))
if (n > 0)
lines.push_back(boldLine.subs(i18n("Secret keys not imported:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged))
lines.push_back(normalLine.subs(i18n("Secret keys unchanged:"))
.subs(n).toString());
if (const int n = sum(res, &ImportResult::numV3KeysSkipped))
lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:"))
.subs(n).toString());
}
if (!lines.empty()) {
lines.push_back(headerLine.subs(QLatin1String{" "}).toString());
}
if (groups.size() > 0) {
const auto newGroups = std::count_if(std::begin(groups), std::end(groups),
[](const auto &g) {
return g.status == ImportedGroup::Status::New;
});
const auto updatedGroups = groups.size() - newGroups;
lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString());
lines.push_back(normalLine.subs(i18n("Total number processed:"))
.subs(groups.size()).toString());
lines.push_back(normalLine.subs(i18n("New groups:"))
.subs(newGroups).toString());
lines.push_back(normalLine.subs(i18n("Updated groups:"))
.subs(updatedGroups).toString());
}
return lines.join(QLatin1String{});
}
static bool isImportFromSingleSource(const std::vector &res)
{
return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id);
}
static QString make_message_report(const std::vector &res,
const std::vector &groups)
{
QString report{QLatin1String{""}};
if (res.empty()) {
report += i18n("No imports (should not happen, please report a bug).");
} else {
const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ?
i18n("Detailed results of importing %1:", res.front().id) :
i18n("Detailed results of import:");
report += QLatin1String{""} + title + QLatin1String{"
"};
report += QLatin1String{"
"};
report += make_report(res, groups);
report += QLatin1String{"
"};
}
report += QLatin1String{""};
return report;
}
// Returns false on error, true if please certify was shown.
bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp)
{
const char *fpr = imp.fingerprint();
if (!fpr) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Import without fingerprint";
return false;
}
// Exactly one public key imported. Let's see if it is openpgp. We are async here so
// we can just fetch it.
auto ctx = GpgME::Context::createForProtocol(GpgME::OpenPGP);
if (!ctx) {
// WTF
qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto";
return false;
}
GpgME::Error err;
auto key = ctx->key(fpr, err, false);
delete ctx;
if (key.isNull() || err) {
// No such key most likely not OpenPGP
return false;
}
if (!Kleo::canBeCertified(key)) {
// key is expired or revoked
return false;
}
for (const auto &uid: key.userIDs()) {
if (uid.validity() >= GpgME::UserID::Marginal) {
// Already marginal so don't bug the user
return false;
}
}
const QStringList suggestions = QStringList() << i18n("A phone call to the person.")
<< i18n("Using a business card.")
<< i18n("Confirming it on a trusted website.");
auto sel = KMessageBox::questionYesNo(parentWidgetOrView(),
i18n("In order to mark the certificate as valid (green) it needs to be certified.") + QStringLiteral(" ") +
i18n("Certifying means that you check the Fingerprint.") + QStringLiteral(" ") +
i18n("Some suggestions to do this are:") +
QStringLiteral("%1").arg(suggestions.join(QStringLiteral(" ") +
i18n("Do you wish to start this process now?"),
i18nc("@title", "You have imported a new certificate (public key)"),
KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("CertifyQuestion"));
if (sel == KMessageBox::Yes) {
QEventLoop loop;
auto cmd = new Commands::CertifyCertificateCommand(key);
cmd->setParentWidget(parentWidgetOrView());
connect(cmd, &Command::finished, &loop, &QEventLoop::quit);
QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection);
loop.exec();
}
return true;
}
namespace
{
/**
* Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key.
* Otherwise, returns a null Import.
*/
auto getSingleOpenPGPImport(const std::vector &res)
{
static const Import nullImport;
if (!isImportFromSingleSource(res)) {
return nullImport;
}
const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) {
return s + r.result.numImported();
});
if (numImported > 1) {
return nullImport;
}
if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) {
return res[0].result.imports()[0];
} else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) {
return res[1].result.imports()[0];
}
return nullImport;
}
}
void ImportCertificatesCommand::Private::showDetails(const std::vector &res,
const std::vector &groups)
{
const auto singleOpenPGPImport = getSingleOpenPGPImport(res);
if (!singleOpenPGPImport.isNull()) {
if (showPleaseCertify(singleOpenPGPImport)) {
return;
}
}
setImportResultProxyModel(res);
information(make_message_report(res, groups),
i18n("Certificate Import Result"));
}
static QString make_error_message(const Error &err, const QString &id)
{
Q_ASSERT(err);
Q_ASSERT(!err.isCanceled());
return id.isEmpty()
? i18n("An error occurred while trying "
"to import the certificate:
"
"%1
",
QString::fromLocal8Bit(err.asString()))
: i18n("An error occurred while trying "
"to import the certificate %1:
"
"%2
",
id, QString::fromLocal8Bit(err.asString()));
}
void ImportCertificatesCommand::Private::showError(QWidget *parent, const Error &err, const QString &id)
{
if (parent) {
KMessageBox::error(parent, make_error_message(err, id), i18n("Certificate Import Failed"));
} else {
showError(err, id);
}
}
void ImportCertificatesCommand::Private::showError(const Error &err, const QString &id)
{
error(make_error_message(err, id), i18n("Certificate Import Failed"));
}
void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait)
{
if (wait == waitForMoreJobs) {
return;
}
waitForMoreJobs = wait;
if (!waitForMoreJobs) {
tryToFinish();
}
}
void ImportCertificatesCommand::Private::importResult(const ImportResult &result, QGpgME::Job *finishedJob)
{
if (!finishedJob) {
finishedJob = qobject_cast(q->sender());
}
Q_ASSERT(finishedJob);
auto it = std::find_if(std::cbegin(jobs), std::cend(jobs),
[finishedJob](const auto &job) { return job.job == finishedJob; });
Q_ASSERT(it != std::cend(jobs));
if (it == std::cend(jobs)) {
qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found";
return;
}
const auto job = *it;
jobs.erase(std::remove(std::begin(jobs), std::end(jobs), job), std::end(jobs));
increaseProgressValue();
importResult({job.id, job.protocol, job.type, result});
}
void ImportCertificatesCommand::Private::importResult(const ImportResultData &result)
{
qCDebug(KLEOPATRA_LOG) << __func__ << result.id;
results.push_back(result);
tryToFinish();
}
static void handleOwnerTrust(const std::vector &results)
{
//iterate over all imported certificates
for (const auto &r: results) {
//when a new certificate got a secret key
if (r.result.numSecretKeysImported() >= 1) {
const char *fingerPr = r.result.imports()[0].fingerprint();
GpgME::Error err;
QScopedPointer
ctx(Context::createForProtocol(GpgME::Protocol::OpenPGP));
if (!ctx){
qCWarning(KLEOPATRA_LOG) << "Failed to get context";
continue;
}
const Key toTrustOwner = ctx->key(fingerPr, err , false);
if (toTrustOwner.isNull()) {
return;
}
const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()};
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(), toTrustOwnerUserIDs.cend(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &uid) {
return kxi18nc("@info", "%1- %2
").subs(temp).subs(Formatting::prettyNameAndEMail(uid));
});
const QString str = xi18nc("@info",
"You have imported a Secret Key. "
"The key has the fingerprint: "
"%1 "
" "
"And claims the user IDs:"
"%2
"
" "
"Is this your own key? (Set trust level to ultimate)",
QString::fromUtf8(fingerPr),
uids);
int k = KMessageBox::questionYesNo(nullptr, str, i18nc("@title:window",
"Secret key imported"));
if (k == KMessageBox::Yes) {
//To use the ChangeOwnerTrustJob over
//the CryptoBackendFactory
const QGpgME::Protocol *const backend = QGpgME::openpgp();
if (!backend){
qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend";
return;
}
ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob();
j->start(toTrustOwner, Key::Ultimate);
}
}
}
}
static void validateImportedCertificate(const GpgME::Import &import)
{
if (const auto fpr = import.fingerprint()) {
auto key = KeyCache::instance()->findByFingerprint(fpr);
if (!key.isNull()) {
// this triggers a keylisting with validation for this certificate
key.update();
} else {
qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found";
}
}
}
static void handleExternalCMSImports(const std::vector &results)
{
// For external CMS Imports we have to manually do a keylist
// with validation to get the intermediate and root ca imported
// automatically if trusted-certs and extra-certs are used.
for (const auto &r : results) {
if (r.protocol == GpgME::CMS && r.type == ImportType::External
&& !importFailed(r) && !importWasCanceled(r)) {
const auto imports = r.result.imports();
std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate);
}
}
}
void ImportCertificatesCommand::Private::processResults()
{
importGroups();
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) {
importingSignerKeys = true;
const auto missingSignerKeys = getMissingSignerKeyIds(results);
if (!missingSignerKeys.empty()) {
importSignerKeys(missingSignerKeys);
return;
}
}
#endif
handleExternalCMSImports(results);
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
handleOwnerTrust(results);
showDetails(results, importedGroups);
auto tv = dynamic_cast (view());
if (!tv) {
qCDebug(KLEOPATRA_LOG) << "Failed to find treeview";
} else {
tv->expandAll();
}
finished();
}
void ImportCertificatesCommand::Private::tryToFinish()
{
if (waitForMoreJobs || !jobs.empty()) {
return;
}
auto keyCache = KeyCache::mutableInstance();
keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone,
q, [this]() { keyCacheUpdated(); });
keyCache->startKeyListing();
}
void ImportCertificatesCommand::Private::keyCacheUpdated()
{
disconnect(keyListConnection);
keyCacheAutoRefreshSuspension.reset();
const auto allIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set{},
[](auto allIds, const auto &r) {
allIds.insert(r.id);
return allIds;
});
const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results),
std::set{},
[](auto canceledIds, const auto &r) {
if (importWasCanceled(r)) {
canceledIds.insert(r.id);
}
return canceledIds;
});
const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results),
0,
[](auto totalConsidered, const auto &r) {
return totalConsidered + r.result.numConsidered();
});
if (totalConsidered == 0 && canceledIds.size() == allIds.size()) {
// nothing was considered for import and at least one import per id was
// canceled => treat the command as canceled
canceled();
return;
}
if (std::any_of(std::cbegin(results), std::cend(results), &importFailed)) {
// ensure that the progress dialog is closed before we show any other dialogs
setProgressToMaximum();
setImportResultProxyModel(results);
for (const auto &r : results) {
if (importFailed(r)) {
showError(r.result.error(), r.id);
}
}
finished();
return;
}
processResults();
}
static ImportedGroup storeGroup(const KeyGroup &group, const QString &id)
{
const auto status = KeyCache::instance()->group(group.id()).isNull() ?
ImportedGroup::Status::New :
ImportedGroup::Status::Updated;
if (status == ImportedGroup::Status::New) {
KeyCache::mutableInstance()->insert(group);
} else {
KeyCache::mutableInstance()->update(group);
}
return {id, group, status};
}
void ImportCertificatesCommand::Private::importGroups()
{
for (const auto &path : filesToImportGroupsFrom) {
const bool certificateImportSucceeded =
std::any_of(std::cbegin(results), std::cend(results),
[path](const auto &r) {
return r.id == path && !importFailed(r) && !importWasCanceled(r);
});
if (certificateImportSucceeded) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path;
const auto groups = readKeyGroups(path);
std::transform(std::begin(groups), std::end(groups),
std::back_inserter(importedGroups),
[path](const auto &group) {
return storeGroup(group, path);
});
}
increaseProgressValue();
}
filesToImportGroupsFrom.clear();
}
static auto accumulateNewKeys(std::vector &fingerprints, const std::vector &imports)
{
return std::accumulate(std::begin(imports), std::end(imports),
fingerprints,
[](auto fingerprints, const auto &import) {
if (import.status() == Import::NewKey) {
fingerprints.push_back(import.fingerprint());
}
return fingerprints;
});
}
static auto accumulateNewOpenPGPKeys(const std::vector &results)
{
return std::accumulate(std::begin(results), std::end(results),
std::vector{},
[](auto fingerprints, const auto &r) {
if (r.protocol == GpgME::OpenPGP) {
fingerprints = accumulateNewKeys(fingerprints, r.result.imports());
}
return fingerprints;
});
}
std::set ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector &results)
{
auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results));
// update all new OpenPGP keys to get information about certifications
std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update));
auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys);
return missingSignerKeyIds;
}
void ImportCertificatesCommand::Private::importSignerKeys(const std::set &keyIds)
{
Q_ASSERT(!keyIds.empty());
setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)",
"Fetching %1 signer keys... (this can take a while)",
keyIds.size()));
setWaitForMoreJobs(true);
// start one import per key id to allow canceling the key retrieval without
// losing already retrieved keys
for (const auto &keyId : keyIds) {
startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys"));
}
setWaitForMoreJobs(false);
}
static std::unique_ptr get_import_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id,
[[maybe_unused]] const ImportOptions &options)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult({id, protocol, ImportType::Local, ImportResult{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { importResult(result); }),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
#ifdef QGPGME_SUPPORTS_IMPORT_WITH_FILTER
job->setImportFilter(options.importFilter);
#endif
#ifdef QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN
job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl);
#endif
const GpgME::Error err = job->start(data);
if (err.code()) {
importResult({id, protocol, ImportType::Local, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::Local, job.release(), connections});
}
}
static std::unique_ptr get_import_from_keyserver_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
return std::unique_ptr(backend->importFromKeyserverJob());
} else {
return std::unique_ptr();
}
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector &keys, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) {
return;
}
std::unique_ptr job = get_import_from_keyserver_job(protocol);
if (!job.get()) {
nonWorkingProtocols.push_back(protocol);
error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.",
Formatting::displayName(protocol)),
i18n("Certificate Import Failed"));
importResult({id, protocol, ImportType::External, ImportResult{}});
return;
}
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { importResult(result); }),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
const GpgME::Error err = job->start(keys);
if (err.code()) {
importResult({id, protocol, ImportType::External, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
}
static auto get_receive_keys_job(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != UnknownProtocol);
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
std::unique_ptr job{};
if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) {
job.reset(backend->receiveKeysJob());
}
return job;
#else
return std::unique_ptr{};
#endif
}
void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id)
{
Q_ASSERT(protocol != UnknownProtocol);
auto job = get_receive_keys_job(protocol);
if (!job.get()) {
qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol);
importResult({id, protocol, ImportType::External, ImportResult{}});
return;
}
#ifdef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh();
std::vector connections = {
connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) {
importResult(result);
}),
connect(job.get(), &Job::progress,
q, &Command::progress)
};
const GpgME::Error err = job->start(keyIds);
if (err.code()) {
importResult({id, protocol, ImportType::External, ImportResult{err}});
} else {
increaseProgressMaximum();
jobs.push_back({id, protocol, ImportType::External, job.release(), connections});
}
#endif
}
void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename)
{
increaseProgressMaximum();
filesToImportGroupsFrom.push_back(filename);
}
void ImportCertificatesCommand::Private::setUpProgressDialog()
{
if (progressDialog) {
return;
}
progressDialog = new QProgressDialog{parentWidgetOrView()};
progressDialog->setModal(true);
progressDialog->setWindowTitle(progressWindowTitle);
progressDialog->setLabelText(progressLabelText);
progressDialog->setMinimumDuration(1000);
progressDialog->setMaximum(1);
progressDialog->setValue(0);
connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel);
connect(q, &Command::finished, progressDialog, [this]() { progressDialog->accept(); });
}
void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title)
{
if (progressDialog) {
progressDialog->setWindowTitle(title);
} else {
progressWindowTitle = title;
}
}
void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text)
{
if (progressDialog) {
progressDialog->setLabelText(text);
} else {
progressLabelText = text;
}
}
void ImportCertificatesCommand::Private::increaseProgressMaximum()
{
setUpProgressDialog();
progressDialog->setMaximum(progressDialog->maximum() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::increaseProgressValue()
{
progressDialog->setValue(progressDialog->value() + 1);
qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum();
}
void ImportCertificatesCommand::Private::setProgressToMaximum()
{
qCDebug(KLEOPATRA_LOG) << __func__;
progressDialog->setValue(progressDialog->maximum());
}
static void disconnectConnection(const QMetaObject::Connection &connection)
{
// trivial function for disconnecting a signal-slot connection because
// using a lambda seems to confuse older GCC / MinGW and unnecessarily
// capturing 'this' generates warnings
QObject::disconnect(connection);
}
void ImportCertificatesCommand::doCancel()
{
const auto jobsToCancel = d->jobs;
std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel),
[this](const auto &job) {
qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job;
std::for_each(std::cbegin(job.connections), std::cend(job.connections),
&disconnectConnection);
job.job->slotCancel();
d->importResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job);
});
}
#undef d
#undef q
#include "moc_importcertificatescommand.cpp"
#include "importcertificatescommand.moc"
diff --git a/src/commands/lookupcertificatescommand.cpp b/src/commands/lookupcertificatescommand.cpp
index 34c2dfa9c..230e23303 100644
--- a/src/commands/lookupcertificatescommand.cpp
+++ b/src/commands/lookupcertificatescommand.cpp
@@ -1,612 +1,611 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/lookupcertificatescommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008, 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "lookupcertificatescommand.h"
#include "importcertificatescommand_p.h"
#include "detailscommand.h"
#include
#include "view/tabwidget.h"
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
#include
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
# include
# include
#endif
#include
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
#include
#include
#include
#include
#include
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace QGpgME;
class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private
{
friend class ::Kleo::Commands::LookupCertificatesCommand;
LookupCertificatesCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(LookupCertificatesCommand *qq, KeyListController *c);
~Private() override;
void init();
private:
void slotSearchTextChanged(const QString &str);
void slotNextKey(const Key &key);
void slotKeyListResult(const KeyListResult &result);
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
void slotWKDLookupResult(const WKDLookupResult &result);
#endif
void tryToFinishKeyLookup();
void slotImportRequested(const std::vector &keys);
void slotDetailsRequested(const Key &key);
void slotSaveAsRequested(const std::vector &keys);
void slotDialogRejected()
{
canceled();
}
private:
using ImportCertificatesCommand::Private::showError;
void showError(QWidget *parent, const KeyListResult &result);
void showResult(QWidget *parent, const KeyListResult &result);
void createDialog();
KeyListJob *createKeyListJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->keyListJob(true) : nullptr;
}
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
WKDLookupJob *createWKDLookupJob() const
{
const auto cbp = QGpgME::openpgp();
return cbp ? cbp->wkdLookupJob() : nullptr;
}
#endif
ImportFromKeyserverJob *createImportJob(GpgME::Protocol proto) const
{
const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
return cbp ? cbp->importFromKeyserverJob() : nullptr;
}
void startKeyListJob(GpgME::Protocol proto, const QString &str);
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
void startWKDLookupJob(const QString &str);
#endif
bool checkConfig() const;
QWidget *dialogOrParentWidgetOrView() const
{
if (dialog) {
return dialog;
} else {
return parentWidgetOrView();
}
}
private:
GpgME::Protocol protocol = GpgME::UnknownProtocol;
QString query;
bool autoStartLookup = false;
QPointer dialog;
struct KeyListingVariables {
QPointer cms, openpgp;
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
QPointer wkdJob;
#endif
QString pattern;
KeyListResult result;
std::vector keys;
int numKeysWithoutUserId = 0;
std::set wkdKeyFingerprints;
QByteArray wkdKeyData;
QString wkdSource;
bool cmsKeysHaveNoFingerprints = false;
bool openPgpKeysHaveNoFingerprints = false;
void reset()
{
*this = KeyListingVariables();
}
} keyListing;
};
LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func()
{
return static_cast(d.get());
}
const LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
LookupCertificatesCommand::Private::Private(LookupCertificatesCommand *qq, KeyListController *c)
: ImportCertificatesCommand::Private(qq, c),
dialog()
{
if (!Settings{}.cmsEnabled()) {
protocol = GpgME::OpenPGP;
}
}
LookupCertificatesCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
delete dialog;
}
LookupCertificatesCommand::LookupCertificatesCommand(KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
}
LookupCertificatesCommand::LookupCertificatesCommand(const QString &query, KeyListController *c)
: ImportCertificatesCommand(new Private(this, c))
{
d->init();
d->query = query;
d->autoStartLookup = true;
}
LookupCertificatesCommand::LookupCertificatesCommand(QAbstractItemView *v, KeyListController *c)
: ImportCertificatesCommand(v, new Private(this, c))
{
d->init();
if (c->tabWidget()) {
d->query = c->tabWidget()->stringFilter();
// do not start the lookup automatically to prevent unwanted leaking
// of information
}
}
void LookupCertificatesCommand::Private::init()
{
}
LookupCertificatesCommand::~LookupCertificatesCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void LookupCertificatesCommand::setProtocol(GpgME::Protocol protocol)
{
d->protocol = protocol;
}
GpgME::Protocol LookupCertificatesCommand::protocol() const
{
return d->protocol;
}
void LookupCertificatesCommand::doStart()
{
if (!d->checkConfig()) {
d->finished();
return;
}
d->createDialog();
Q_ASSERT(d->dialog);
// if we have a prespecified query, load it into find field
// and start the search, if auto-start is enabled
if (!d->query.isEmpty()) {
d->dialog->setSearchText(d->query);
if (d->autoStartLookup) {
d->slotSearchTextChanged(d->query);
}
} else {
d->dialog->setPassive(false);
}
d->dialog->show();
}
void LookupCertificatesCommand::Private::createDialog()
{
if (dialog) {
return;
}
dialog = new LookupCertificatesDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &LookupCertificatesDialog::searchTextChanged, q, [this](const QString &text) { slotSearchTextChanged(text); });
using CertsVec = std::vector;
connect(dialog, &LookupCertificatesDialog::saveAsRequested, q, [this](const CertsVec &certs) { slotSaveAsRequested(certs); });
connect(dialog, &LookupCertificatesDialog::importRequested, q, [this](const CertsVec &certs) { slotImportRequested(certs); });
connect(dialog, &LookupCertificatesDialog::detailsRequested, q, [this](const GpgME::Key &gpgKey) { slotDetailsRequested(gpgKey); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
static auto searchTextToEmailAddress(const QString &s)
{
return QString::fromStdString(UserID::addrSpecFromString(s.toStdString().c_str()));
}
void LookupCertificatesCommand::Private::slotSearchTextChanged(const QString &str)
{
// pressing return might trigger both search and dialog destruction (search focused and default key set)
// On Windows, the dialog is then destroyed before this slot is called
if (dialog) { //thus test
dialog->setPassive(true);
dialog->setCertificates(std::vector());
dialog->showInformation({});
}
query = str;
keyListing.reset();
keyListing.pattern = str;
if (protocol != GpgME::OpenPGP) {
startKeyListJob(CMS, str);
}
if (protocol != GpgME::CMS) {
static const QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1String("(?:0x|0X)?[0-9a-fA-F]{6,}")));
if (rx.match(query).hasMatch() && !str.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
qCDebug(KLEOPATRA_LOG) << "Adding 0x prefix to query";
startKeyListJob(OpenPGP, QStringLiteral("0x") + str);
} else {
startKeyListJob(OpenPGP, str);
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
if (str.contains(QLatin1Char{'@'}) && !searchTextToEmailAddress(str).isEmpty()) {
startWKDLookupJob(str);
}
#endif
}
}
}
void LookupCertificatesCommand::Private::startKeyListJob(GpgME::Protocol proto, const QString &str)
{
KeyListJob *const klj = createKeyListJob(proto);
if (!klj) {
return;
}
connect(klj, &QGpgME::KeyListJob::result, q, [this](const GpgME::KeyListResult &result) { slotKeyListResult(result); });
connect(klj, &QGpgME::KeyListJob::nextKey, q, [this](const GpgME::Key &key) { slotNextKey(key); });
if (const Error err = klj->start(QStringList(str))) {
keyListing.result.mergeWith(KeyListResult(err));
} else if (proto == CMS) {
keyListing.cms = klj;
} else {
keyListing.openpgp = klj;
}
}
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
void LookupCertificatesCommand::Private::startWKDLookupJob(const QString &str)
{
const auto job = createWKDLookupJob();
if (!job) {
qCDebug(KLEOPATRA_LOG) << "Failed to create WKDLookupJob";
return;
}
connect(job, &WKDLookupJob::result,
q, [this](const WKDLookupResult &result) { slotWKDLookupResult(result); });
if (const Error err = job->start(str)) {
keyListing.result.mergeWith(KeyListResult{err});
} else {
keyListing.wkdJob = job;
}
}
#endif
void LookupCertificatesCommand::Private::slotNextKey(const Key &key)
{
if (!key.primaryFingerprint()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without fingerprint" << key;
if (q->sender() == keyListing.cms) {
keyListing.cmsKeysHaveNoFingerprints = true;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openPgpKeysHaveNoFingerprints = true;
}
} else if (key.numUserIDs() == 0) {
qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without user IDs" << key;
keyListing.numKeysWithoutUserId++;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "got key" << key;
keyListing.keys.push_back(key);
}
}
void LookupCertificatesCommand::Private::slotKeyListResult(const KeyListResult &r)
{
if (q->sender() == keyListing.cms) {
keyListing.cms = nullptr;
} else if (q->sender() == keyListing.openpgp) {
keyListing.openpgp = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << "unknown sender()" << q->sender();
}
keyListing.result.mergeWith(r);
tryToFinishKeyLookup();
}
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
static auto removeKeysNotMatchingEmail(const std::vector &keys, const std::string &email)
{
std::vector filteredKeys;
const auto addrSpec = UserID::addrSpecFromString(email.c_str());
std::copy_if(std::begin(keys), std::end(keys),
std::back_inserter(filteredKeys),
[addrSpec](const auto &key) {
const auto uids = key.userIDs();
return std::any_of(std::begin(uids), std::end(uids),
[addrSpec](const auto &uid) {
return uid.addrSpec() == addrSpec;
});
});
return filteredKeys;
}
void LookupCertificatesCommand::Private::slotWKDLookupResult(const WKDLookupResult &result)
{
if (q->sender() == keyListing.wkdJob) {
keyListing.wkdJob = nullptr;
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "unknown sender()" << q->sender();
}
// we do not want to bother the user with errors during the WKD lookup;
// therefore, we log the result, but we do not merge it into keyListing.result
qCDebug(KLEOPATRA_LOG) << "Result of WKD lookup:" << result.error();
const auto keys = removeKeysNotMatchingEmail(result.keyData().toKeys(GpgME::OpenPGP), result.pattern());
if (!keys.empty()) {
keyListing.wkdKeyData = QByteArray::fromStdString(result.keyData().toString());
keyListing.wkdSource = QString::fromStdString(result.source());
std::copy(std::begin(keys), std::end(keys),
std::back_inserter(keyListing.keys));
// remember the keys retrieved via WKD for import
std::transform(std::begin(keys), std::end(keys),
std::inserter(keyListing.wkdKeyFingerprints, std::begin(keyListing.wkdKeyFingerprints)),
[](const auto &k) { return k.primaryFingerprint(); });
}
tryToFinishKeyLookup();
}
#endif
namespace
{
void showKeysWithoutFingerprintsNotification(QWidget *parent, GpgME::Protocol protocol)
{
if (protocol != GpgME::CMS && protocol != GpgME::OpenPGP) {
return;
}
QString message;
if (protocol == GpgME::CMS) {
message = xi18nc("@info",
"One of the X.509 directory services returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates. "
"You may want to configure a different X.509 directory service "
"in the configuration dialog. ");
} else {
message = xi18nc("@info",
"The OpenPGP keyserver returned certificates without "
"fingerprints. Those certificates are ignored because fingerprints "
"are required as unique identifiers for certificates. "
"You may want to configure a different OpenPGP keyserver "
"in the configuration dialog. ");
}
KMessageBox::information(parent, message, i18nc("@title", "Invalid Server Reply"),
QStringLiteral("certificates-lookup-missing-fingerprints"));
}
}
void LookupCertificatesCommand::Private::tryToFinishKeyLookup()
{
if (keyListing.cms || keyListing.openpgp
#ifdef QGPGME_SUPPORTS_WKDLOOKUP
|| keyListing.wkdJob
#endif
) {
// still waiting for jobs to complete
return;
}
if (keyListing.result.error() && !keyListing.result.error().isCanceled()) {
showError(dialog, keyListing.result);
}
if (keyListing.result.isTruncated()) {
showResult(dialog, keyListing.result);
}
if (keyListing.cmsKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::CMS);
}
if (keyListing.openPgpKeysHaveNoFingerprints) {
showKeysWithoutFingerprintsNotification(dialog, GpgME::OpenPGP);
}
if (dialog) {
dialog->setPassive(false);
dialog->setCertificates(keyListing.keys);
if (keyListing.numKeysWithoutUserId > 0) {
dialog->showInformation(i18ncp("@info",
"One certificate without name and email address was ignored.",
"%1 certificates without name and email address were ignored.",
keyListing.numKeysWithoutUserId));
}
} else {
finished();
}
}
void LookupCertificatesCommand::Private::slotImportRequested(const std::vector &keys)
{
dialog = nullptr;
Q_ASSERT(!keys.empty());
Q_ASSERT(std::none_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.isNull(); }));
std::vector wkdKeys, otherKeys;
otherKeys.reserve(keys.size());
kdtools::separate_if(std::begin(keys), std::end(keys),
std::back_inserter(wkdKeys),
std::back_inserter(otherKeys),
[this](const auto &key) {
return key.primaryFingerprint() &&
keyListing.wkdKeyFingerprints.find(key.primaryFingerprint()) != std::end(keyListing.wkdKeyFingerprints);
});
std::vector pgp, cms;
pgp.reserve(otherKeys.size());
cms.reserve(otherKeys.size());
kdtools::separate_if(otherKeys.begin(), otherKeys.end(),
std::back_inserter(pgp),
std::back_inserter(cms),
[](const Key &key) {
return key.protocol() == GpgME::OpenPGP;
});
setWaitForMoreJobs(true);
if (!wkdKeys.empty()) {
// set an import filter, so that only user IDs matching the email address used for the WKD lookup are imported
const QString importFilter = QLatin1String{"keep-uid=mbox = "} + searchTextToEmailAddress(keyListing.pattern);
startImport(OpenPGP, keyListing.wkdKeyData, keyListing.wkdSource,
{importFilter, Key::OriginWKD, keyListing.wkdSource});
}
if (!pgp.empty()) {
startImport(OpenPGP, pgp,
i18nc(R"(@title %1:"OpenPGP" or "S/MIME")",
"%1 Certificate Server",
Formatting::displayName(OpenPGP)));
}
if (!cms.empty()) {
startImport(CMS, cms,
i18nc(R"(@title %1:"OpenPGP" or "S/MIME")",
"%1 Certificate Server",
Formatting::displayName(CMS)));
}
setWaitForMoreJobs(false);
}
void LookupCertificatesCommand::Private::slotSaveAsRequested(const std::vector &keys)
{
Q_UNUSED(keys)
qCDebug(KLEOPATRA_LOG) << "not implemented";
}
void LookupCertificatesCommand::Private::slotDetailsRequested(const Key &key)
{
Command *const cmd = new DetailsCommand(key);
cmd->setParentWidget(dialogOrParentWidgetOrView());
cmd->start();
}
void LookupCertificatesCommand::doCancel()
{
ImportCertificatesCommand::doCancel();
if (QDialog *const dlg = d->dialog) {
d->dialog = nullptr;
dlg->close();
}
}
void LookupCertificatesCommand::Private::showError(QWidget *parent, const KeyListResult &result)
{
if (!result.error()) {
return;
}
KMessageBox::information(parent, i18nc("@info",
"Failed to search on certificate server. The error returned was:\n%1",
QString::fromLocal8Bit(result.error().asString())));
}
void LookupCertificatesCommand::Private::showResult(QWidget *parent, const KeyListResult &result)
{
if (result.isTruncated())
KMessageBox::information(parent,
xi18nc("@info",
"The query result has been truncated. "
"Either the local or a remote limit on "
"the maximum number of returned hits has "
"been exceeded. "
"You can try to increase the local limit "
"in the configuration dialog, but if one "
"of the configured servers is the limiting "
"factor, you have to refine your search. "),
i18nc("@title", "Result Truncated"),
QStringLiteral("lookup-certificates-truncated-result"));
}
bool LookupCertificatesCommand::Private::checkConfig() const
{
const bool haveOrDontNeedOpenPGPServer = haveKeyserverConfigured() || (protocol == GpgME::CMS);
const bool haveOrDontNeedCMSServer = haveX509DirectoryServerConfigured() || (protocol == GpgME::OpenPGP);
const bool ok = haveOrDontNeedOpenPGPServer || haveOrDontNeedCMSServer;
if (!ok)
information(xi18nc("@info",
"You do not have any directory servers configured. "
"You need to configure at least one directory server to "
"search on one. "
"You can configure directory servers here: "
"Settings->Configure Kleopatra . "),
i18nc("@title", "No Directory Servers Configured"));
return ok;
}
#undef d
#undef q
#include "moc_lookupcertificatescommand.cpp"
diff --git a/src/commands/revokekeycommand.cpp b/src/commands/revokekeycommand.cpp
index 96f6ca2ab..cad0b9463 100644
--- a/src/commands/revokekeycommand.cpp
+++ b/src/commands/revokekeycommand.cpp
@@ -1,258 +1,257 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/revokekeycommand.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 "revokekeycommand.h"
#include "command_p.h"
#include "dialogs/revokekeydialog.h"
#include
#include
-#include
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
#include
#endif
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace GpgME;
class RevokeKeyCommand::Private : public Command::Private
{
friend class ::RevokeKeyCommand;
RevokeKeyCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(RevokeKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
void ensureDialogCreated();
void onDialogAccepted();
void onDialogRejected();
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
std::unique_ptr startJob();
#endif
void onJobResult(const Error &err);
void showError(const Error &err);
private:
Key key;
QPointer dialog;
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
QPointer job;
#endif
};
RevokeKeyCommand::Private *RevokeKeyCommand::d_func()
{
return static_cast(d.get());
}
const RevokeKeyCommand::Private *RevokeKeyCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
RevokeKeyCommand::Private::Private(RevokeKeyCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
RevokeKeyCommand::Private::~Private() = default;
namespace
{
Key getKey(const std::vector &keys)
{
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size();
return {};
}
const Key key = keys.front();
if (key.protocol() != GpgME::OpenPGP) {
qCWarning(KLEOPATRA_LOG) << "Expected OpenPGP key, but got" << Formatting::displayName(key.protocol()) << "key";
return {};
}
return key;
}
}
void RevokeKeyCommand::Private::start()
{
key = getKey(keys());
if (key.isNull()) {
finished();
return;
}
if (key.isRevoked()) {
information(i18nc("@info", "This key has already been revoked."));
finished();
return;
}
ensureDialogCreated();
Q_ASSERT(dialog);
dialog->setKey(key);
dialog->show();
}
void RevokeKeyCommand::Private::cancel()
{
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
if (job) {
job->slotCancel();
}
job.clear();
#endif
}
void RevokeKeyCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new RevokeKeyDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { onDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { onDialogRejected(); });
}
void RevokeKeyCommand::Private::onDialogAccepted()
{
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
auto revokeJob = startJob();
if (!revokeJob) {
finished();
return;
}
job = revokeJob.release();
#endif
}
void RevokeKeyCommand::Private::onDialogRejected()
{
canceled();
}
namespace
{
std::vector toStdStrings(const QStringList &l)
{
std::vector v;
v.reserve(l.size());
std::transform(std::begin(l), std::end(l),
std::back_inserter(v),
std::mem_fn(&QString::toStdString));
return v;
}
auto descriptionToLines(const QString &description)
{
std::vector lines;
if (!description.isEmpty()) {
lines = toStdStrings(description.split(QLatin1Char('\n')));
}
return lines;
}
}
#ifdef QGPGME_SUPPORTS_KEY_REVOCATION
std::unique_ptr RevokeKeyCommand::Private::startJob()
{
std::unique_ptr revokeJob{QGpgME::openpgp()->revokeKeyJob()};
Q_ASSERT(revokeJob);
connect(revokeJob.get(), &QGpgME::RevokeKeyJob::result,
q, [this](const GpgME::Error &err) {
onJobResult(err);
});
connect(revokeJob.get(), &QGpgME::Job::progress,
q, &Command::progress);
const auto description = descriptionToLines(dialog->description());
const GpgME::Error err = revokeJob->start(key, dialog->reason(), description);
if (err) {
showError(err);
return {};
}
Q_EMIT q->info(i18nc("@info:status", "Revoking key..."));
return revokeJob;
}
#endif
void RevokeKeyCommand::Private::onJobResult(const Error &err)
{
if (err) {
showError(err);
finished();
return;
}
if (!err.isCanceled()) {
information(i18nc("@info", "The key was revoked successfully."),
i18nc("@title:window", "Key Revoked"));
}
finished();
}
void RevokeKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"An error occurred during the revocation: "
"%1 ",
QString::fromLocal8Bit(err.asString())),
i18nc("@title:window", "Revocation Failed"));
}
RevokeKeyCommand::RevokeKeyCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
RevokeKeyCommand::RevokeKeyCommand(const GpgME::Key &key)
: Command{key, new Private{this}}
{
}
RevokeKeyCommand::~RevokeKeyCommand() = default;
void RevokeKeyCommand::doStart()
{
d->start();
}
void RevokeKeyCommand::doCancel()
{
d->cancel();
}
#undef d
#undef q
#include "moc_revokekeycommand.cpp"
diff --git a/src/commands/setprimaryuseridcommand.cpp b/src/commands/setprimaryuseridcommand.cpp
index 7f866e5a9..68fafb89e 100644
--- a/src/commands/setprimaryuseridcommand.cpp
+++ b/src/commands/setprimaryuseridcommand.cpp
@@ -1,181 +1,180 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/setprimaryuseridcommand.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 "setprimaryuseridcommand.h"
#include "command_p.h"
#include
-#include
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
#include
#endif
#include
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class SetPrimaryUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::SetPrimaryUserIDCommand;
SetPrimaryUserIDCommand *q_func() const
{
return static_cast(q);
}
public:
explicit Private(SetPrimaryUserIDCommand *qq, const UserID &userId);
~Private() override;
void startJob();
private:
void createJob();
void slotResult(const Error &err);
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::UserID userId;
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
QPointer job;
#endif
};
SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func()
{
return static_cast(d.get());
}
const SetPrimaryUserIDCommand::Private *SetPrimaryUserIDCommand::d_func() const
{
return static_cast(d.get());
}
#define d d_func()
#define q q_func()
SetPrimaryUserIDCommand::Private::Private(SetPrimaryUserIDCommand *qq, const UserID &userId)
: Command::Private{qq}
, userId{userId}
{
}
SetPrimaryUserIDCommand::Private::~Private() = default;
void Commands::SetPrimaryUserIDCommand::Private::startJob()
{
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
createJob();
if (!job) {
finished();
return;
}
job->start(userId);
#else
error(i18nc("@info", "The backend does not support this operation."));
#endif
}
void SetPrimaryUserIDCommand::Private::createJob()
{
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->setPrimaryUserIDJob();
if (!j) {
return;
}
connect(j, &QGpgME::Job::progress, q, &Command::progress);
connect(j, &QGpgME::SetPrimaryUserIDJob::result, q, [this](const GpgME::Error &err) {
slotResult(err);
});
job = j;
#endif
}
void SetPrimaryUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void SetPrimaryUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"An error occurred while trying to flag the user ID%1 as the primary user ID. "
"%2 ",
QString::fromUtf8(userId.id()),
QString::fromLocal8Bit(err.asString())));
}
void SetPrimaryUserIDCommand::Private::showSuccessDialog()
{
success(xi18nc("@info",
"The user ID%1 has been flagged successfully as the primary user ID. ",
QString::fromUtf8(userId.id())));
}
SetPrimaryUserIDCommand::SetPrimaryUserIDCommand(const GpgME::UserID &userId)
: Command{new Private{this, userId}}
{
}
SetPrimaryUserIDCommand::~SetPrimaryUserIDCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void SetPrimaryUserIDCommand::doStart()
{
if (d->userId.isNull()) {
d->finished();
return;
}
const auto key = d->userId.parent();
if (key.protocol() != GpgME::OpenPGP || !key.hasSecret()) {
d->finished();
return;
}
d->startJob();
}
void SetPrimaryUserIDCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
#ifdef QGPGME_SUPPORTS_SET_PRIMARY_UID
if (d->job) {
d->job->slotCancel();
}
#endif
}
#undef d
#undef q
diff --git a/src/conf/appearanceconfigwidget.cpp b/src/conf/appearanceconfigwidget.cpp
index bf159e969..f87858853 100644
--- a/src/conf/appearanceconfigwidget.cpp
+++ b/src/conf/appearanceconfigwidget.cpp
@@ -1,687 +1,686 @@
/*
appearanceconfigwidget.cpp
This file is part of kleopatra, the KDE key manager
SPDX-FileCopyrightText: 2002, 2004, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2002, 2003 Marc Mutz
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "appearanceconfigwidget.h"
#include "ui_appearanceconfigwidget.h"
#include "tagspreferences.h"
#include "tooltippreferences.h"
#include
-#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace Kleo;
using namespace Kleo::Config;
enum {
HasNameRole = Qt::UserRole + 0x1234, /*!< Records that the user has assigned a name (to avoid comparing with i18n-strings) */
HasFontRole, /*!< Records that the user has chosen completely different font (as opposed to italic/bold/strikeout) */
IconNameRole, /*!< Records the name of the icon (since QIcon won't give it out again, once set) */
MayChangeNameRole,
MayChangeForegroundRole,
MayChangeBackgroundRole,
MayChangeFontRole,
MayChangeItalicRole,
MayChangeBoldRole,
MayChangeStrikeOutRole,
MayChangeIconRole,
StoredForegroundRole, /*!< Stores the actual configured foreground color */
StoredBackgroundRole, /*!< Stores the actual configured background color */
EndDummy,
};
static QFont tryToFindFontFor(const QListWidgetItem *item)
{
if (item)
if (const QListWidget *const lw = item->listWidget()) {
return lw->font();
}
return QApplication::font("QListWidget");
}
static bool is(const QListWidgetItem *item, bool (QFont::*func)() const)
{
if (!item) {
return false;
}
const QVariant v = item->data(Qt::FontRole);
if (!v.isValid() || v.type() != QVariant::Font) {
return false;
}
return (v.value().*func)();
}
static bool is_italic(const QListWidgetItem *item)
{
return is(item, &QFont::italic);
}
static bool is_bold(const QListWidgetItem *item)
{
return is(item, &QFont::bold);
}
static bool is_strikeout(const QListWidgetItem *item)
{
return is(item, &QFont::strikeOut);
}
static void set(QListWidgetItem *item, bool on, void (QFont::*func)(bool))
{
if (!item) {
return;
}
const QVariant v = item->data(Qt::FontRole);
QFont font = v.isValid() && v.type() == QVariant::Font ? v.value() : tryToFindFontFor(item);
(font.*func)(on);
item->setData(Qt::FontRole, font);
}
static void set_italic(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setItalic);
}
static void set_bold(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setBold);
}
static void set_strikeout(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setStrikeOut);
}
static void apply_config(const KConfigGroup &group, QListWidgetItem *item)
{
if (!item) {
return;
}
const QString name = group.readEntry("Name");
item->setText(name.isEmpty() ? i18nc("Key filter without user-assigned name", "") : name);
item->setData(HasNameRole, !name.isEmpty());
item->setData(MayChangeNameRole, !group.isEntryImmutable("Name"));
const QColor fg = group.readEntry("foreground-color", QColor());
item->setData(StoredForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::ForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
}
item->setData(MayChangeForegroundRole, !group.isEntryImmutable("foreground-color"));
const QColor bg = group.readEntry("background-color", QColor());
item->setData(StoredBackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::BackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
}
item->setData(MayChangeBackgroundRole, !group.isEntryImmutable("background-color"));
const QFont defaultFont = tryToFindFontFor(item);
if (group.hasKey("font")) {
const QFont font = group.readEntry("font", defaultFont);
item->setData(Qt::FontRole, font != defaultFont ? font : QVariant());
item->setData(HasFontRole, font != defaultFont);
} else {
QFont font = defaultFont;
font.setStrikeOut(group.readEntry("font-strikeout", false));
font.setItalic(group.readEntry("font-italic", false));
font.setBold(group.readEntry("font-bold", false));
item->setData(Qt::FontRole, font);
item->setData(HasFontRole, false);
}
item->setData(MayChangeFontRole, !group.isEntryImmutable("font"));
item->setData(MayChangeItalicRole, !group.isEntryImmutable("font-italic"));
item->setData(MayChangeBoldRole, !group.isEntryImmutable("font-bold"));
item->setData(MayChangeStrikeOutRole, !group.isEntryImmutable("font-strikeout"));
const QString iconName = group.readEntry("icon");
item->setData(Qt::DecorationRole, iconName.isEmpty() ? QVariant() : QIcon::fromTheme(iconName));
item->setData(IconNameRole, iconName.isEmpty() ? QVariant() : iconName);
item->setData(MayChangeIconRole, !group.isEntryImmutable("icon"));
}
static void erase_if_allowed(QListWidgetItem *item, int role, int allowRole)
{
if (item && item->data(allowRole).toBool()) {
item->setData(role, QVariant());
}
}
#if 0
static void erase_if_allowed(QListWidgetItem *item, const int role[], size_t numRoles, int allowRole)
{
if (item && item->data(allowRole).toBool())
for (unsigned int i = 0; i < numRoles; ++i) {
item->setData(role[i], QVariant());
}
}
static void erase_if_allowed(QListWidgetItem *item, int role, const int allowRole[], size_t numAllowRoles)
{
if (!item) {
return;
}
for (unsigned int i = 0; i < numAllowRoles; ++i)
if (!item->data(allowRole[i]).toBool()) {
return;
}
item->setData(role, QVariant());
}
#endif
static void erase_if_allowed(QListWidgetItem *item, const int role[], size_t numRoles, const int allowRole[], size_t numAllowRoles)
{
if (!item) {
return;
}
for (unsigned int i = 0; i < numAllowRoles; ++i)
if (!item->data(allowRole[i]).toBool()) {
return;
}
for (unsigned int i = 0; i < numRoles; ++i) {
item->setData(role[i], QVariant());
}
}
static void set_default_appearance(QListWidgetItem *item)
{
if (!item) {
return;
}
erase_if_allowed(item, StoredForegroundRole, MayChangeForegroundRole);
erase_if_allowed(item, Qt::ForegroundRole, MayChangeForegroundRole);
erase_if_allowed(item, StoredBackgroundRole, MayChangeBackgroundRole);
erase_if_allowed(item, Qt::BackgroundRole, MayChangeBackgroundRole);
erase_if_allowed(item, Qt::DecorationRole, MayChangeIconRole);
static const int fontRoles[] = { Qt::FontRole, HasFontRole };
static const int fontAllowRoles[] = {
MayChangeFontRole,
MayChangeItalicRole,
MayChangeBoldRole,
MayChangeStrikeOutRole,
};
erase_if_allowed(item, fontRoles, sizeof(fontRoles) / sizeof(int), fontAllowRoles, sizeof(fontAllowRoles) / sizeof(int));
}
static void writeOrDelete(KConfigGroup &group, const char *key, const QVariant &value)
{
if (value.isValid()) {
group.writeEntry(key, value);
} else {
group.deleteEntry(key);
}
}
static QVariant brush2color(const QVariant &v)
{
if (v.isValid()) {
if (v.type() == QVariant::Color) {
return v;
} else if (v.type() == QVariant::Brush) {
return v.value().color();
}
}
return QVariant();
}
static void save_to_config(const QListWidgetItem *item, KConfigGroup &group)
{
if (!item) {
return;
}
writeOrDelete(group, "Name", item->data(HasNameRole).toBool() ? item->text() : QVariant());
writeOrDelete(group, "foreground-color", brush2color(item->data(StoredForegroundRole)));
writeOrDelete(group, "background-color", brush2color(item->data(StoredBackgroundRole)));
writeOrDelete(group, "icon", item->data(IconNameRole));
group.deleteEntry("font");
group.deleteEntry("font-strikeout");
group.deleteEntry("font-italic");
group.deleteEntry("font-bold");
if (item->data(HasFontRole).toBool()) {
writeOrDelete(group, "font", item->data(Qt::FontRole));
return;
}
if (is_strikeout(item)) {
group.writeEntry("font-strikeout", true);
}
if (is_italic(item)) {
group.writeEntry("font-italic", true);
}
if (is_bold(item)) {
group.writeEntry("font-bold", true);
}
}
static void kiosk_enable(QWidget *w, const QListWidgetItem *item, int allowRole)
{
if (!w) {
return;
}
if (item && !item->data(allowRole).toBool()) {
w->setEnabled(false);
w->setToolTip(i18n("This parameter has been locked down by the system administrator."));
} else {
w->setEnabled(item);
w->setToolTip(QString());
}
}
class AppearanceConfigWidget::Private : public Ui_AppearanceConfigWidget
{
friend class ::Kleo::Config::AppearanceConfigWidget;
AppearanceConfigWidget *const q;
public:
explicit Private(AppearanceConfigWidget *qq)
: Ui_AppearanceConfigWidget()
, q(qq)
{
setupUi(q);
if (QLayout *const l = q->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
highContrastMsg->setVisible(SystemInfo::isHighContrastModeActive());
highContrastMsg->setMessageType(KMessageWidget::Warning);
highContrastMsg->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q));
highContrastMsg->setText(i18n("The preview of colors is disabled because high-contrast mode is active."));
highContrastMsg->setCloseButtonVisible(false);
if (Kleo::Settings{}.cmsEnabled()) {
auto w = new QWidget;
dnOrderWidget = new DNAttributeOrderConfigWidget{w};
dnOrderWidget->setObjectName(QStringLiteral("dnOrderWidget"));
(new QVBoxLayout(w))->addWidget(dnOrderWidget);
tabWidget->addTab(w, i18n("DN-Attribute Order"));
connect(dnOrderWidget, &DNAttributeOrderConfigWidget::changed, q, &AppearanceConfigWidget::changed);
}
connect(iconButton, SIGNAL(clicked()), q, SLOT(slotIconClicked()));
#ifndef QT_NO_COLORDIALOG
connect(foregroundButton, SIGNAL(clicked()), q, SLOT(slotForegroundClicked()));
connect(backgroundButton, SIGNAL(clicked()), q, SLOT(slotBackgroundClicked()));
#else
foregroundButton->hide();
backgroundButton->hide();
#endif
#ifndef QT_NO_FONTDIALOG
connect(fontButton, SIGNAL(clicked()), q, SLOT(slotFontClicked()));
#else
fontButton->hide();
#endif
connect(categoriesLV, SIGNAL(itemSelectionChanged()), q, SLOT(slotSelectionChanged()));
connect(defaultLookPB, SIGNAL(clicked()), q, SLOT(slotDefaultClicked()));
connect(italicCB, SIGNAL(toggled(bool)), q, SLOT(slotItalicToggled(bool)));
connect(boldCB, SIGNAL(toggled(bool)), q, SLOT(slotBoldToggled(bool)));
connect(strikeoutCB, SIGNAL(toggled(bool)), q, SLOT(slotStrikeOutToggled(bool)));
connect(tooltipValidityCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipValidityChanged(bool)));
connect(tooltipOwnerCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipOwnerChanged(bool)));
connect(tooltipDetailsCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipDetailsChanged(bool)));
connect(useTagsCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotUseTagsChanged(bool)));
}
private:
void enableDisableActions(QListWidgetItem *item);
QListWidgetItem *selectedItem() const;
private:
void slotIconClicked();
#ifndef QT_NO_COLORDIALOG
void slotForegroundClicked();
void slotBackgroundClicked();
#endif
#ifndef QT_NO_FONTDIALOG
void slotFontClicked();
#endif
void slotSelectionChanged();
void slotDefaultClicked();
void slotItalicToggled(bool);
void slotBoldToggled(bool);
void slotStrikeOutToggled(bool);
void slotTooltipValidityChanged(bool);
void slotTooltipOwnerChanged(bool);
void slotTooltipDetailsChanged(bool);
void slotUseTagsChanged(bool);
private:
Kleo::DNAttributeOrderConfigWidget *dnOrderWidget = nullptr;
};
AppearanceConfigWidget::AppearanceConfigWidget(QWidget *p, Qt::WindowFlags f)
: QWidget(p, f), d(new Private(this))
{
// load();
}
AppearanceConfigWidget::~AppearanceConfigWidget() {}
void AppearanceConfigWidget::Private::slotSelectionChanged()
{
enableDisableActions(selectedItem());
}
QListWidgetItem *AppearanceConfigWidget::Private::selectedItem() const
{
const QList items = categoriesLV->selectedItems();
return items.empty() ? nullptr : items.front();
}
void AppearanceConfigWidget::Private::enableDisableActions(QListWidgetItem *item)
{
kiosk_enable(iconButton, item, MayChangeIconRole);
#ifndef QT_NO_COLORDIALOG
kiosk_enable(foregroundButton, item, MayChangeForegroundRole);
kiosk_enable(backgroundButton, item, MayChangeBackgroundRole);
#endif
#ifndef QT_NO_FONTDIALOG
kiosk_enable(fontButton, item, MayChangeFontRole);
#endif
kiosk_enable(italicCB, item, MayChangeItalicRole);
kiosk_enable(boldCB, item, MayChangeBoldRole);
kiosk_enable(strikeoutCB, item, MayChangeStrikeOutRole);
defaultLookPB->setEnabled(item);
italicCB->setChecked(is_italic(item));
boldCB->setChecked(is_bold(item));
strikeoutCB->setChecked(is_strikeout(item));
}
void AppearanceConfigWidget::Private::slotDefaultClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
set_default_appearance(item);
enableDisableActions(item);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::defaults()
{
// This simply means "default look for every category"
for (int i = 0, end = d->categoriesLV->count(); i != end; ++i) {
set_default_appearance(d->categoriesLV->item(i));
}
// use a temporary TooltipPreferences instance for resetting the values to the defaults;
// the setters respect the immutability of the individual settings, so that we don't have
// to check this explicitly
TooltipPreferences tooltipPrefs;
tooltipPrefs.setShowValidity(tooltipPrefs.findItem(QStringLiteral("ShowValidity"))->getDefault().toBool());
d->tooltipValidityCheckBox->setChecked(tooltipPrefs.showValidity());
tooltipPrefs.setShowOwnerInformation(tooltipPrefs.findItem(QStringLiteral("ShowOwnerInformation"))->getDefault().toBool());
d->tooltipOwnerCheckBox->setChecked(tooltipPrefs.showOwnerInformation());
tooltipPrefs.setShowCertificateDetails(tooltipPrefs.findItem(QStringLiteral("ShowCertificateDetails"))->getDefault().toBool());
d->tooltipDetailsCheckBox->setChecked(tooltipPrefs.showCertificateDetails());
if (d->dnOrderWidget) {
const Settings settings;
if (!settings.isImmutable(QStringLiteral("AttributeOrder"))) {
d->dnOrderWidget->setAttributeOrder(DN::defaultAttributeOrder());
}
}
Q_EMIT changed();
}
void AppearanceConfigWidget::load()
{
if (d->dnOrderWidget) {
const Settings settings;
d->dnOrderWidget->setAttributeOrder(DN::attributeOrder());
d->dnOrderWidget->setEnabled(!settings.isImmutable(QStringLiteral("AttributeOrder")));
}
d->categoriesLV->clear();
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
if (!config) {
return;
}
const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
for (const QString &group : groups) {
const KConfigGroup configGroup{config, group};
const bool isCmsSpecificKeyFilter = !configGroup.readEntry("is-openpgp-key", true);
auto item = new QListWidgetItem{d->categoriesLV};
// hide CMS-specific filters if CMS is disabled; we hide those filters
// instead of skipping them, so that they are not removed on save
item->setHidden(isCmsSpecificKeyFilter && !Kleo::Settings{}.cmsEnabled());
apply_config(configGroup, item);
}
const TooltipPreferences prefs;
d->tooltipValidityCheckBox->setChecked(prefs.showValidity());
d->tooltipValidityCheckBox->setEnabled(!prefs.isImmutable(QStringLiteral("ShowValidity")));
d->tooltipOwnerCheckBox->setChecked(prefs.showOwnerInformation());
d->tooltipOwnerCheckBox->setEnabled(!prefs.isImmutable(QStringLiteral("ShowOwnerInformation")));
d->tooltipDetailsCheckBox->setChecked(prefs.showCertificateDetails());
d->tooltipDetailsCheckBox->setEnabled(!prefs.isImmutable(QStringLiteral("ShowCertificateDetails")));
const TagsPreferences tagsPrefs;
d->useTagsCheckBox->setChecked(tagsPrefs.useTags());
d->useTagsCheckBox->setEnabled(!tagsPrefs.isImmutable(QStringLiteral("UseTags")));
}
void AppearanceConfigWidget::save()
{
if (d->dnOrderWidget) {
Settings settings;
settings.setAttributeOrder(d->dnOrderWidget->attributeOrder());
settings.save();
DN::setAttributeOrder(settings.attributeOrder());
}
TooltipPreferences prefs;
prefs.setShowValidity(d->tooltipValidityCheckBox->isChecked());
prefs.setShowOwnerInformation(d->tooltipOwnerCheckBox->isChecked());
prefs.setShowCertificateDetails(d->tooltipDetailsCheckBox->isChecked());
prefs.save();
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
if (!config) {
return;
}
// We know (assume) that the groups in the config object haven't changed,
// so we just iterate over them and over the listviewitems, and map one-to-one.
const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
#if 0
if (groups.isEmpty()) {
// If we created the default categories ourselves just now, then we need to make up their list
Q3ListViewItemIterator lvit(categoriesLV);
for (; lvit.current(); ++lvit) {
groups << lvit.current()->text(0);
}
}
#endif
for (int i = 0, end = std::min(groups.size(), d->categoriesLV->count()); i != end; ++i) {
const QListWidgetItem *const item = d->categoriesLV->item(i);
Q_ASSERT(item);
KConfigGroup group(config, groups[i]);
save_to_config(item, group);
}
TagsPreferences tagsPrefs;
tagsPrefs.setUseTags(d->useTagsCheckBox->isChecked());
tagsPrefs.save();
config->sync();
KeyFilterManager::instance()->reload();
}
void AppearanceConfigWidget::Private::slotIconClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QString iconName = KIconDialog::getIcon( /* repeating default arguments begin */
KIconLoader::Desktop, KIconLoader::Application, false, 0, false,
/* repeating default arguments end */
q);
if (iconName.isEmpty()) {
return;
}
item->setIcon(QIcon::fromTheme(iconName));
item->setData(IconNameRole, iconName);
Q_EMIT q->changed();
}
#ifndef QT_NO_COLORDIALOG
void AppearanceConfigWidget::Private::slotForegroundClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = brush2color(item->data(StoredForegroundRole));
const QColor initial = v.isValid() ? v.value() : categoriesLV->palette().color(QPalette::Normal, QPalette::Text);
const QColor c = QColorDialog::getColor(initial, q);
if (c.isValid()) {
item->setData(StoredForegroundRole, QBrush(c));
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::ForegroundRole, QBrush(c));
}
Q_EMIT q->changed();
}
}
void AppearanceConfigWidget::Private::slotBackgroundClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = brush2color(item->data(StoredBackgroundRole));
const QColor initial = v.isValid() ? v.value() : categoriesLV->palette().color(QPalette::Normal, QPalette::Base);
const QColor c = QColorDialog::getColor(initial, q);
if (c.isValid()) {
item->setData(StoredBackgroundRole, QBrush(c));
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::BackgroundRole, QBrush(c));
}
Q_EMIT q->changed();
}
}
#endif // QT_NO_COLORDIALOG
#ifndef QT_NO_FONTDIALOG
void AppearanceConfigWidget::Private::slotFontClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = item->data(Qt::FontRole);
bool ok = false;
const QFont defaultFont = tryToFindFontFor(item);
const QFont initial = v.isValid() && v.type() == QVariant::Font ? v.value() : defaultFont;
QFont f = QFontDialog::getFont(&ok, initial, q);
if (!ok) {
return;
}
// disallow circumventing KIOSK:
if (!item->data(MayChangeItalicRole).toBool()) {
f.setItalic(initial.italic());
}
if (!item->data(MayChangeBoldRole).toBool()) {
f.setBold(initial.bold());
}
if (!item->data(MayChangeStrikeOutRole).toBool()) {
f.setStrikeOut(initial.strikeOut());
}
item->setData(Qt::FontRole, f != defaultFont ? f : QVariant());
item->setData(HasFontRole, true);
Q_EMIT q->changed();
}
#endif // QT_NO_FONTDIALOG
void AppearanceConfigWidget::Private::slotItalicToggled(bool on)
{
set_italic(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotBoldToggled(bool on)
{
set_bold(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotStrikeOutToggled(bool on)
{
set_strikeout(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipValidityChanged(bool)
{
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipOwnerChanged(bool)
{
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipDetailsChanged(bool)
{
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotUseTagsChanged(bool)
{
Q_EMIT q->changed();
}
#include "moc_appearanceconfigwidget.cpp"
diff --git a/src/conf/groupsconfigwidget.cpp b/src/conf/groupsconfigwidget.cpp
index 765397a18..35eb5ee09 100644
--- a/src/conf/groupsconfigwidget.cpp
+++ b/src/conf/groupsconfigwidget.cpp
@@ -1,421 +1,420 @@
/*
conf/groupsconfigwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "groupsconfigwidget.h"
#include "commands/exportgroupscommand.h"
#include "dialogs/editgroupdialog.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Dialogs;
Q_DECLARE_METATYPE(KeyGroup)
namespace
{
class ListView : public QListView
{
Q_OBJECT
public:
using QListView::QListView;
protected:
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override
{
// workaround bug in QListView::currentChanged which sends an accessible focus event
// even if the list view doesn't have focus
if (hasFocus()) {
QListView::currentChanged(current, previous);
} else {
// skip the reimplementation of currentChanged in QListView
QAbstractItemView::currentChanged(current, previous);
}
}
void focusInEvent(QFocusEvent *event) override
{
QListView::focusInEvent(event);
// select current item if it isn't selected
if (currentIndex().isValid() && !selectionModel()->isSelected(currentIndex())) {
selectionModel()->select(currentIndex(), QItemSelectionModel::ClearAndSelect);
}
}
};
class ProxyModel : public AbstractKeyListSortFilterProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent = nullptr)
: AbstractKeyListSortFilterProxyModel(parent)
{
}
~ProxyModel() override = default;
ProxyModel *clone() const override
{
// compiler-generated copy ctor is fine!
return new ProxyModel(*this);
}
int columnCount(const QModelIndex &parent = {}) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// list view
return 1;
}
QVariant data(const QModelIndex &idx, int role) const override
{
if (!idx.isValid()) {
return {};
}
return AbstractKeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role);
}
};
struct Selection
{
KeyGroup current;
std::vector selected;
};
}
class GroupsConfigWidget::Private
{
friend class ::Kleo::GroupsConfigWidget;
GroupsConfigWidget *const q;
struct {
QLineEdit *groupsFilter = nullptr;
QListView *groupsList = nullptr;
QPushButton *newButton = nullptr;
QPushButton *editButton = nullptr;
QPushButton *deleteButton = nullptr;
QPushButton *exportButton = nullptr;
} ui;
AbstractKeyListModel *groupsModel = nullptr;
ProxyModel *groupsFilterModel = nullptr;
public:
Private(GroupsConfigWidget *qq)
: q(qq)
{
auto mainLayout = new QVBoxLayout(q);
auto groupsLayout = new QGridLayout;
groupsLayout->setColumnStretch(0, 1);
groupsLayout->setRowStretch(1, 1);
int row = -1;
row++;
{
auto hbox = new QHBoxLayout;
auto label = new QLabel{i18nc("@label", "Search:")};
label->setAccessibleName(i18nc("@label", "Search groups"));
label->setToolTip(i18nc("@info:tooltip", "Search the list for groups matching the search term."));
hbox->addWidget(label);
ui.groupsFilter = new QLineEdit(q);
ui.groupsFilter->setClearButtonEnabled(true);
ui.groupsFilter->setAccessibleName(i18nc("@label", "Search groups"));
ui.groupsFilter->setToolTip(i18nc("@info:tooltip", "Search the list for groups matching the search term."));
ui.groupsFilter->setPlaceholderText(i18nc("@info::placeholder", "Enter search term"));
ui.groupsFilter->setCursorPosition(0); // prevent emission of accessible text cursor event before accessible focus event
label->setBuddy(ui.groupsFilter);
hbox->addWidget(ui.groupsFilter, 1);
groupsLayout->addLayout(hbox, row, 0);
}
row++;
groupsModel = AbstractKeyListModel::createFlatKeyListModel(q);
groupsFilterModel = new ProxyModel(q);
groupsFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
groupsFilterModel->setFilterKeyColumn(KeyList::Summary);
groupsFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
groupsFilterModel->setSourceModel(groupsModel);
groupsFilterModel->sort(KeyList::Summary, Qt::AscendingOrder);
ui.groupsList = new ListView(q);
ui.groupsList->setAccessibleName(i18nc("groups of keys", "groups"));
ui.groupsList->setModel(groupsFilterModel);
ui.groupsList->setSelectionBehavior(QAbstractItemView::SelectRows);
ui.groupsList->setSelectionMode(QAbstractItemView::ExtendedSelection);
groupsLayout->addWidget(ui.groupsList, row, 0);
auto groupsButtonLayout = new QVBoxLayout;
ui.newButton = new QPushButton(i18nc("@action::button", "New"), q);
groupsButtonLayout->addWidget(ui.newButton);
ui.editButton = new QPushButton(i18nc("@action::button", "Edit"), q);
ui.editButton->setEnabled(false);
groupsButtonLayout->addWidget(ui.editButton);
ui.deleteButton = new QPushButton(i18nc("@action::button", "Delete"), q);
ui.deleteButton->setEnabled(false);
groupsButtonLayout->addWidget(ui.deleteButton);
ui.exportButton = new QPushButton{i18nc("@action::button", "Export"), q};
ui.exportButton->setEnabled(false);
groupsButtonLayout->addWidget(ui.exportButton);
groupsButtonLayout->addStretch(1);
groupsLayout->addLayout(groupsButtonLayout, row, 1);
mainLayout->addLayout(groupsLayout, /*stretch=*/ 1);
connect(ui.groupsFilter, &QLineEdit::textChanged, q, [this](const auto &s) {
groupsFilterModel->setFilterRegularExpression(QRegularExpression::escape(s));
});
connect(ui.groupsList->selectionModel(), &QItemSelectionModel::selectionChanged,
q, [this] () { selectionChanged(); });
connect(ui.groupsList, &QListView::doubleClicked,
q, [this] (const QModelIndex &index) { editGroup(index); });
connect(ui.newButton, &QPushButton::clicked, q, [this] () { addGroup(); });
connect(ui.editButton, &QPushButton::clicked, q, [this] () { editGroup(); });
connect(ui.deleteButton, &QPushButton::clicked, q, [this] () { deleteGroup(); });
connect(ui.exportButton, &QPushButton::clicked, q, [this] () { exportGroup(); });
}
~Private()
{
}
private:
auto getGroupIndex(const KeyGroup &group)
{
QModelIndex index;
if (const KeyListModelInterface *const klmi = dynamic_cast(ui.groupsList->model())) {
index = klmi->index(group);
}
return index;
}
auto selectedRows()
{
return ui.groupsList->selectionModel()->selectedRows();
}
auto getGroup(const QModelIndex &index)
{
return index.isValid() ? ui.groupsList->model()->data(index, KeyList::GroupRole).value() : KeyGroup{};
}
auto getGroups(const QModelIndexList &indexes)
{
std::vector groups;
std::transform(std::begin(indexes), std::end(indexes),
std::back_inserter(groups),
[this](const auto &index) { return getGroup(index); });
return groups;
}
Selection saveSelection()
{
return {getGroup(ui.groupsList->selectionModel()->currentIndex()), getGroups(selectedRows())};
}
void restoreSelection(const Selection &selection)
{
auto selectionModel = ui.groupsList->selectionModel();
selectionModel->clearSelection();
for (const auto &group : selection.selected) {
selectionModel->select(getGroupIndex(group), QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
auto currentIndex = getGroupIndex(selection.current);
if (currentIndex.isValid()) {
// keep current item if old current group is gone
selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
}
}
void selectionChanged()
{
const auto selectedGroups = getGroups(selectedRows());
const bool allSelectedGroupsAreEditable =
std::all_of(std::begin(selectedGroups), std::end(selectedGroups),
[](const auto &g) { return !g.isNull() && !g.isImmutable(); });
ui.editButton->setEnabled(selectedGroups.size() == 1 && allSelectedGroupsAreEditable);
ui.deleteButton->setEnabled(!selectedGroups.empty() && allSelectedGroupsAreEditable);
ui.exportButton->setEnabled(selectedGroups.size() == 1);
}
KeyGroup showEditGroupDialog(KeyGroup group, const QString &windowTitle, EditGroupDialog::FocusWidget focusWidget)
{
auto dialog = std::make_unique(q);
dialog->setWindowTitle(windowTitle);
dialog->setGroupName(group.name());
const KeyGroup::Keys &keys = group.keys();
dialog->setGroupKeys(std::vector(keys.cbegin(), keys.cend()));
dialog->setInitialFocus(focusWidget);
const int result = dialog->exec();
if (result == QDialog::Rejected) {
return KeyGroup();
}
group.setName(dialog->groupName());
group.setKeys(dialog->groupKeys());
return group;
}
void addGroup()
{
const KeyGroup::Id newId = KRandom::randomString(8);
KeyGroup group = KeyGroup(newId, i18nc("default name for new group of keys", "New Group"), {}, KeyGroup::ApplicationConfig);
group.setIsImmutable(false);
const KeyGroup newGroup = showEditGroupDialog(
group, i18nc("@title:window a group of keys", "New Group"), EditGroupDialog::GroupName);
if (newGroup.isNull()) {
return;
}
const QModelIndex newIndex = groupsModel->addGroup(newGroup);
if (!newIndex.isValid()) {
qCDebug(KLEOPATRA_LOG) << "Adding group to model failed";
return;
}
Q_EMIT q->changed();
}
void editGroup(const QModelIndex &index = {})
{
QModelIndex groupIndex;
if (index.isValid()) {
groupIndex = index;
} else {
const auto selection = selectedRows();
if (selection.size() != 1) {
qCDebug(KLEOPATRA_LOG) << (selection.empty() ? "selection is empty" : "more than one group is selected");
return;
}
groupIndex = selection.front();
}
const KeyGroup group = getGroup(groupIndex);
if (group.isNull()) {
qCDebug(KLEOPATRA_LOG) << "selected group is null";
return;
}
if (group.isImmutable()) {
qCDebug(KLEOPATRA_LOG) << "selected group is immutable";
return;
}
const KeyGroup updatedGroup = showEditGroupDialog(
group, i18nc("@title:window a group of keys", "Edit Group"), EditGroupDialog::KeysFilter);
if (updatedGroup.isNull()) {
return;
}
// look up index of updated group; the groupIndex used above may have become invalid
const auto updatedGroupIndex = getGroupIndex(updatedGroup);
if (!updatedGroupIndex.isValid()) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Failed to find index of group" << updatedGroup;
return;
}
const bool success = ui.groupsList->model()->setData(updatedGroupIndex, QVariant::fromValue(updatedGroup));
if (!success) {
qCDebug(KLEOPATRA_LOG) << "Updating group in model failed";
return;
}
Q_EMIT q->changed();
}
void deleteGroup()
{
const auto selectedGroups = getGroups(selectedRows());
if (selectedGroups.empty()) {
qCDebug(KLEOPATRA_LOG) << "selection is empty";
return;
}
for (const auto &group : selectedGroups) {
const bool success = groupsModel->removeGroup(group);
if (!success) {
qCDebug(KLEOPATRA_LOG) << "Removing group from model failed:" << group;
}
}
Q_EMIT q->changed();
}
void exportGroup()
{
const auto selectedGroups = getGroups(selectedRows());
if (selectedGroups.empty()) {
qCDebug(KLEOPATRA_LOG) << "selection is empty";
return;
}
// execute export group command
auto cmd = new ExportGroupsCommand(selectedGroups);
cmd->start();
}
};
GroupsConfigWidget::GroupsConfigWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
GroupsConfigWidget::~GroupsConfigWidget() = default;
void GroupsConfigWidget::setGroups(const std::vector &groups)
{
const auto selection = d->saveSelection();
d->groupsModel->setGroups(groups);
d->restoreSelection(selection);
}
std::vector GroupsConfigWidget::groups() const
{
std::vector result;
result.reserve(d->groupsModel->rowCount());
for (int row = 0; row < d->groupsModel->rowCount(); ++row) {
const QModelIndex index = d->groupsModel->index(row, 0);
result.push_back(d->groupsModel->group(index));
}
return result;
}
#include "groupsconfigwidget.moc"
diff --git a/src/conf/smimevalidationconfigurationwidget.cpp b/src/conf/smimevalidationconfigurationwidget.cpp
index 232daa2ad..75cf00d7c 100644
--- a/src/conf/smimevalidationconfigurationwidget.cpp
+++ b/src/conf/smimevalidationconfigurationwidget.cpp
@@ -1,406 +1,405 @@
/* -*- mode: c++; c-basic-offset:4 -*-
conf/smimevalidationconfigurationwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "smimevalidationconfigurationwidget.h"
#include "ui_smimevalidationconfigurationwidget.h"
#include "labelledwidget.h"
#include "smimevalidationpreferences.h"
#include
#include
-#include
#include
#include "kleopatra_debug.h"
#if HAVE_QDBUS
# include
#endif
using namespace Kleo;
using namespace Kleo::Config;
using namespace QGpgME;
class SMimeValidationConfigurationWidget::Private
{
friend class ::Kleo::Config::SMimeValidationConfigurationWidget;
SMimeValidationConfigurationWidget *const q;
public:
explicit Private(SMimeValidationConfigurationWidget *qq)
: q(qq),
ui(qq)
{
#if HAVE_QDBUS
QDBusConnection::sessionBus().connect(QString(), QString(), QStringLiteral("org.kde.kleo.CryptoConfig"), QStringLiteral("changed"), q, SLOT(load()));
#endif
auto changedSignal = &SMimeValidationConfigurationWidget::changed;
connect(ui.intervalRefreshCB, &QCheckBox::toggled, q, changedSignal);
#if QT_DEPRECATED_SINCE(5, 14)
connect(ui.intervalRefreshSB, qOverload(&QSpinBox::valueChanged), q, changedSignal);
#else
connect(ui.intervalRefreshSB, &QSpinBox::valueChanged, q, changedSignal);
#endif
connect(ui.OCSPCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.OCSPResponderURL, &QLineEdit::textChanged, q, changedSignal);
auto certRequesterSignal = &KleopatraClientCopy::Gui::CertificateRequester::selectedCertificatesChanged;
connect(ui.OCSPResponderSignature, certRequesterSignal, q, changedSignal);
connect(ui.doNotCheckCertPolicyCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.neverConsultCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.allowMarkTrustedCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.fetchMissingCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.ignoreServiceURLCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.ignoreHTTPDPCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.disableHTTPCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.honorHTTPProxyRB, &QRadioButton::toggled, q, changedSignal);
connect(ui.useCustomHTTPProxyRB, &QRadioButton::toggled, q, changedSignal);
connect(ui.customHTTPProxy, &QLineEdit::textChanged, q, changedSignal);
connect(ui.ignoreLDAPDPCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.disableLDAPCB, &QCheckBox::toggled, q, changedSignal);
connect(ui.customLDAPProxy, &QLineEdit::textChanged, q, changedSignal);
auto enableDisableSlot = [this]() {
enableDisableActions();
};
connect(ui.useCustomHTTPProxyRB, &QRadioButton::toggled, q, enableDisableSlot);
connect(ui.disableHTTPCB, &QCheckBox::toggled, q, enableDisableSlot);
}
bool customHTTPProxyWritable = false;
private:
void enableDisableActions()
{
ui.customHTTPProxy->setEnabled(ui.useCustomHTTPProxyRB->isChecked() &&
!ui.disableHTTPCB->isChecked() &&
customHTTPProxyWritable);
}
private:
struct UI : Ui_SMimeValidationConfigurationWidget {
LabelledWidget labelledOCSPResponderSignature;
LabelledWidget labelledOCSPResponderURL;
explicit UI(SMimeValidationConfigurationWidget *q)
: Ui_SMimeValidationConfigurationWidget()
{
setupUi(q);
labelledOCSPResponderURL.setWidgets(OCSPResponderURL, OCSPResponderURLLabel);
labelledOCSPResponderSignature.setWidgets(OCSPResponderSignature, OCSPResponderSignatureLabel);
if (QLayout *l = q->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
OCSPResponderSignature->setOnlyX509CertificatesAllowed(true);
OCSPResponderSignature->setOnlySigningCertificatesAllowed(true);
OCSPResponderSignature->setMultipleCertificatesAllowed(false);
//OCSPResponderSignature->setAllowedKeys( KeySelectionDialog::TrustedKeys|KeySelectionDialog::ValidKeys );
}
} ui;
};
SMimeValidationConfigurationWidget::SMimeValidationConfigurationWidget(QWidget *p, Qt::WindowFlags f)
: QWidget(p, f), d(new Private(this))
{
}
SMimeValidationConfigurationWidget::~SMimeValidationConfigurationWidget() {}
static void disableDirmngrWidget(QWidget *w)
{
w->setEnabled(false);
w->setWhatsThis(i18n("This option requires dirmngr >= 0.9.0"));
}
static void initializeDirmngrCheckbox(QCheckBox *cb, CryptoConfigEntry *entry)
{
if (entry) {
cb->setChecked(entry->boolValue());
}
if (!entry || entry->isReadOnly()) {
disableDirmngrWidget(cb);
}
}
struct SMIMECryptoConfigEntries {
enum ShowError {
DoNotShowError,
DoShowError
};
SMIMECryptoConfigEntries(CryptoConfig *config)
: mConfig(config),
// Checkboxes
mCheckUsingOCSPConfigEntry(configEntry("gpgsm", "enable-ocsp", CryptoConfigEntry::ArgType_None)),
mEnableOCSPsendingConfigEntry(configEntry("dirmngr", "allow-ocsp", CryptoConfigEntry::ArgType_None)),
mDoNotCheckCertPolicyConfigEntry(configEntry("gpgsm", "disable-policy-checks", CryptoConfigEntry::ArgType_None)),
mNeverConsultConfigEntry(configEntry("gpgsm", "disable-crl-checks", CryptoConfigEntry::ArgType_None)),
mAllowMarkTrustedConfigEntry(configEntry("gpg-agent", "allow-mark-trusted", CryptoConfigEntry::ArgType_None, DoNotShowError)), // legacy entry -> ignore error
mFetchMissingConfigEntry(configEntry("gpgsm", "auto-issuer-key-retrieve", CryptoConfigEntry::ArgType_None)),
mNoAllowMarkTrustedConfigEntry(configEntry("gpg-agent", "no-allow-mark-trusted", CryptoConfigEntry::ArgType_None)),
// dirmngr-0.9.0 options
mIgnoreServiceURLEntry(configEntry("dirmngr", "ignore-ocsp-service-url", CryptoConfigEntry::ArgType_None)),
mIgnoreHTTPDPEntry(configEntry("dirmngr", "ignore-http-dp", CryptoConfigEntry::ArgType_None)),
mDisableHTTPEntry(configEntry("dirmngr", "disable-http", CryptoConfigEntry::ArgType_None)),
mHonorHTTPProxy(configEntry("dirmngr", "honor-http-proxy", CryptoConfigEntry::ArgType_None)),
mIgnoreLDAPDPEntry(configEntry("dirmngr", "ignore-ldap-dp", CryptoConfigEntry::ArgType_None)),
mDisableLDAPEntry(configEntry("dirmngr", "disable-ldap", CryptoConfigEntry::ArgType_None)),
// Other widgets
mOCSPResponderURLConfigEntry(configEntry("dirmngr", "ocsp-responder", CryptoConfigEntry::ArgType_String)),
mOCSPResponderSignature(configEntry("dirmngr", "ocsp-signer", CryptoConfigEntry::ArgType_String)),
mCustomHTTPProxy(configEntry("dirmngr", "http-proxy", CryptoConfigEntry::ArgType_String)),
mCustomLDAPProxy(configEntry("dirmngr", "ldap-proxy", CryptoConfigEntry::ArgType_String))
{
}
CryptoConfigEntry *configEntry(const char *componentName,
const char *entryName,
int argType,
ShowError showError=DoShowError);
CryptoConfig *const mConfig;
// Checkboxes
CryptoConfigEntry *const mCheckUsingOCSPConfigEntry;
CryptoConfigEntry *const mEnableOCSPsendingConfigEntry;
CryptoConfigEntry *const mDoNotCheckCertPolicyConfigEntry;
CryptoConfigEntry *const mNeverConsultConfigEntry;
CryptoConfigEntry *const mAllowMarkTrustedConfigEntry;
CryptoConfigEntry *const mFetchMissingConfigEntry;
// gnupg 2.0.17+ option that should inhibit allow-mark-trusted display
CryptoConfigEntry *const mNoAllowMarkTrustedConfigEntry;
// dirmngr-0.9.0 options
CryptoConfigEntry *const mIgnoreServiceURLEntry;
CryptoConfigEntry *const mIgnoreHTTPDPEntry;
CryptoConfigEntry *const mDisableHTTPEntry;
CryptoConfigEntry *const mHonorHTTPProxy;
CryptoConfigEntry *const mIgnoreLDAPDPEntry;
CryptoConfigEntry *const mDisableLDAPEntry;
// Other widgets
CryptoConfigEntry *const mOCSPResponderURLConfigEntry;
CryptoConfigEntry *const mOCSPResponderSignature;
CryptoConfigEntry *const mCustomHTTPProxy;
CryptoConfigEntry *const mCustomLDAPProxy;
};
void SMimeValidationConfigurationWidget::defaults()
{
qCDebug(KLEOPATRA_LOG) << "not implemented";
}
void SMimeValidationConfigurationWidget::load()
{
const SMimeValidationPreferences preferences;
const unsigned int refreshInterval = preferences.refreshInterval();
d->ui.intervalRefreshCB->setChecked(refreshInterval > 0);
d->ui.intervalRefreshSB->setValue(refreshInterval);
const bool isRefreshIntervalImmutable = preferences.isImmutable(QStringLiteral("RefreshInterval"));
d->ui.intervalRefreshCB->setEnabled(!isRefreshIntervalImmutable);
d->ui.intervalRefreshSB->setEnabled(!isRefreshIntervalImmutable);
CryptoConfig *const config = QGpgME::cryptoConfig();
if (!config) {
setEnabled(false);
return;
}
#if 0
// crashes other pages' save() by nuking the CryptoConfigEntries under their feet.
// This was probably not a problem in KMail, where this code comes
// from. But here, it's fatal.
// Force re-parsing gpgconf data, in case e.g. kleopatra or "configure backend" was used
// (which ends up calling us via D-Bus)
config->clear();
#endif
// Create config entries
// Don't keep them around, they'll get deleted by clear(), which could be
// done by the "configure backend" button even before we save().
const SMIMECryptoConfigEntries e(config);
// Initialize GUI items from the config entries
if (e.mCheckUsingOCSPConfigEntry) {
d->ui.OCSPCB->setChecked(e.mCheckUsingOCSPConfigEntry->boolValue());
}
d->ui.OCSPCB->setEnabled(e.mCheckUsingOCSPConfigEntry && !e.mCheckUsingOCSPConfigEntry->isReadOnly());
d->ui.OCSPGroupBox->setEnabled(d->ui.OCSPCB->isChecked());
if (e.mDoNotCheckCertPolicyConfigEntry) {
d->ui.doNotCheckCertPolicyCB->setChecked(e.mDoNotCheckCertPolicyConfigEntry->boolValue());
}
d->ui.doNotCheckCertPolicyCB->setEnabled(e.mDoNotCheckCertPolicyConfigEntry && !e.mDoNotCheckCertPolicyConfigEntry->isReadOnly());
if (e.mNeverConsultConfigEntry) {
d->ui.neverConsultCB->setChecked(e.mNeverConsultConfigEntry->boolValue());
}
d->ui.neverConsultCB->setEnabled(e.mNeverConsultConfigEntry && !e.mNeverConsultConfigEntry->isReadOnly());
if (e.mNoAllowMarkTrustedConfigEntry) {
d->ui.allowMarkTrustedCB->hide(); // this option was only here to _enable_ allow-mark-trusted, and makes no sense if it's already default on
}
if (e.mAllowMarkTrustedConfigEntry) {
d->ui.allowMarkTrustedCB->setChecked(e.mAllowMarkTrustedConfigEntry->boolValue());
}
d->ui.allowMarkTrustedCB->setEnabled(e.mAllowMarkTrustedConfigEntry && !e.mAllowMarkTrustedConfigEntry->isReadOnly());
if (e.mFetchMissingConfigEntry) {
d->ui.fetchMissingCB->setChecked(e.mFetchMissingConfigEntry->boolValue());
}
d->ui.fetchMissingCB->setEnabled(e.mFetchMissingConfigEntry && !e.mFetchMissingConfigEntry->isReadOnly());
if (e.mOCSPResponderURLConfigEntry) {
d->ui.OCSPResponderURL->setText(e.mOCSPResponderURLConfigEntry->stringValue());
}
d->ui.labelledOCSPResponderURL.setEnabled(e.mOCSPResponderURLConfigEntry && !e.mOCSPResponderURLConfigEntry->isReadOnly());
if (e.mOCSPResponderSignature) {
d->ui.OCSPResponderSignature->setSelectedCertificate(e.mOCSPResponderSignature->stringValue());
}
d->ui.labelledOCSPResponderSignature.setEnabled(e.mOCSPResponderSignature && !e.mOCSPResponderSignature->isReadOnly());
// dirmngr-0.9.0 options
initializeDirmngrCheckbox(d->ui.ignoreServiceURLCB, e.mIgnoreServiceURLEntry);
initializeDirmngrCheckbox(d->ui.ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry);
initializeDirmngrCheckbox(d->ui.disableHTTPCB, e.mDisableHTTPEntry);
initializeDirmngrCheckbox(d->ui.ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry);
initializeDirmngrCheckbox(d->ui.disableLDAPCB, e.mDisableLDAPEntry);
if (e.mCustomHTTPProxy) {
QString systemProxy = QString::fromLocal8Bit(qgetenv("http_proxy"));
if (systemProxy.isEmpty()) {
systemProxy = i18n("no proxy");
}
d->ui.systemHTTPProxy->setText(i18n("(Current system setting: %1)", systemProxy));
const bool honor = e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue();
d->ui.honorHTTPProxyRB->setChecked(honor);
d->ui.useCustomHTTPProxyRB->setChecked(!honor);
d->ui.customHTTPProxy->setText(e.mCustomHTTPProxy->stringValue());
}
d->customHTTPProxyWritable = e.mCustomHTTPProxy && !e.mCustomHTTPProxy->isReadOnly();
if (!d->customHTTPProxyWritable) {
disableDirmngrWidget(d->ui.honorHTTPProxyRB);
disableDirmngrWidget(d->ui.useCustomHTTPProxyRB);
disableDirmngrWidget(d->ui.systemHTTPProxy);
disableDirmngrWidget(d->ui.customHTTPProxy);
}
if (e.mCustomLDAPProxy) {
d->ui.customLDAPProxy->setText(e.mCustomLDAPProxy->stringValue());
}
if (!e.mCustomLDAPProxy || e.mCustomLDAPProxy->isReadOnly()) {
disableDirmngrWidget(d->ui.customLDAPProxy);
disableDirmngrWidget(d->ui.customLDAPLabel);
}
d->enableDisableActions();
}
static void saveCheckBoxToKleoEntry(QCheckBox *cb, CryptoConfigEntry *entry)
{
const bool b = cb->isChecked();
if (entry && entry->boolValue() != b) {
entry->setBoolValue(b);
}
}
void SMimeValidationConfigurationWidget::save() const
{
CryptoConfig *const config = QGpgME::cryptoConfig();
if (!config) {
return;
}
{
SMimeValidationPreferences preferences;
preferences.setRefreshInterval(d->ui.intervalRefreshCB->isChecked() ? d->ui.intervalRefreshSB->value() : 0);
preferences.save();
}
// Create config entries
// Don't keep them around, they'll get deleted by clear(), which could be done by the
// "configure backend" button.
const SMIMECryptoConfigEntries e(config);
const bool b = d->ui.OCSPCB->isChecked();
if (e.mCheckUsingOCSPConfigEntry && e.mCheckUsingOCSPConfigEntry->boolValue() != b) {
e.mCheckUsingOCSPConfigEntry->setBoolValue(b);
}
// Set allow-ocsp together with enable-ocsp
if (e.mEnableOCSPsendingConfigEntry && e.mEnableOCSPsendingConfigEntry->boolValue() != b) {
e.mEnableOCSPsendingConfigEntry->setBoolValue(b);
}
saveCheckBoxToKleoEntry(d->ui.doNotCheckCertPolicyCB, e.mDoNotCheckCertPolicyConfigEntry);
saveCheckBoxToKleoEntry(d->ui.neverConsultCB, e.mNeverConsultConfigEntry);
saveCheckBoxToKleoEntry(d->ui.allowMarkTrustedCB, e.mAllowMarkTrustedConfigEntry);
saveCheckBoxToKleoEntry(d->ui.fetchMissingCB, e.mFetchMissingConfigEntry);
QString txt = d->ui.OCSPResponderURL->text();
if (e.mOCSPResponderURLConfigEntry && e.mOCSPResponderURLConfigEntry->stringValue() != txt) {
e.mOCSPResponderURLConfigEntry->setStringValue(txt);
}
txt = d->ui.OCSPResponderSignature->selectedCertificate();
if (e.mOCSPResponderSignature && e.mOCSPResponderSignature->stringValue() != txt) {
e.mOCSPResponderSignature->setStringValue(txt);
}
//dirmngr-0.9.0 options
saveCheckBoxToKleoEntry(d->ui.ignoreServiceURLCB, e.mIgnoreServiceURLEntry);
saveCheckBoxToKleoEntry(d->ui.ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry);
saveCheckBoxToKleoEntry(d->ui.disableHTTPCB, e.mDisableHTTPEntry);
saveCheckBoxToKleoEntry(d->ui.ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry);
saveCheckBoxToKleoEntry(d->ui.disableLDAPCB, e.mDisableLDAPEntry);
if (e.mCustomHTTPProxy) {
const bool honor = d->ui.honorHTTPProxyRB->isChecked();
if (e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue() != honor) {
e.mHonorHTTPProxy->setBoolValue(honor);
}
const QString chosenProxy = d->ui.customHTTPProxy->text();
if (chosenProxy != e.mCustomHTTPProxy->stringValue()) {
e.mCustomHTTPProxy->setStringValue(chosenProxy);
}
}
txt = d->ui.customLDAPProxy->text();
if (e.mCustomLDAPProxy && e.mCustomLDAPProxy->stringValue() != txt) {
e.mCustomLDAPProxy->setStringValue(d->ui.customLDAPProxy->text());
}
config->sync(true);
}
CryptoConfigEntry *SMIMECryptoConfigEntries::configEntry(const char *componentName,
const char *entryName,
int /*CryptoConfigEntry::ArgType*/ argType,
ShowError showError)
{
CryptoConfigEntry *const entry = getCryptoConfigEntry(mConfig, componentName, entryName);
if (!entry) {
if (showError == DoShowError) {
qCWarning(KLEOPATRA_LOG) << QStringLiteral("Backend error: gpgconf doesn't seem to know the entry for %1/%2").arg(QLatin1String(componentName), QLatin1String(entryName));
}
return nullptr;
}
if (entry->argType() != argType || entry->isList()) {
if (showError == DoShowError) {
qCWarning(KLEOPATRA_LOG) << QStringLiteral("Backend error: gpgconf has wrong type for %1/%2: %3 %4").arg(QLatin1String(componentName), QLatin1String(entryName)).arg(entry->argType()).arg(entry->isList());
}
return nullptr;
}
return entry;
}
#include "moc_smimevalidationconfigurationwidget.cpp"
diff --git a/src/crypto/createchecksumscontroller.h b/src/crypto/createchecksumscontroller.h
index 761a303df..1558c389b 100644
--- a/src/crypto/createchecksumscontroller.h
+++ b/src/crypto/createchecksumscontroller.h
@@ -1,55 +1,54 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/createchecksumscontroller.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include
#include
-#include
#include
#include
#include
namespace Kleo
{
namespace Crypto
{
class CreateChecksumsController : public Controller
{
Q_OBJECT
public:
explicit CreateChecksumsController(QObject *parent = nullptr);
explicit CreateChecksumsController(const std::shared_ptr &ctx, QObject *parent = nullptr);
~CreateChecksumsController() override;
void setAllowAddition(bool allow);
bool allowAddition() const;
void setFiles(const QStringList &files);
void start();
public Q_SLOTS:
void cancel();
private:
class Private;
kdtools::pimpl_ptr d;
Q_PRIVATE_SLOT(d, void slotOperationFinished())
Q_PRIVATE_SLOT(d, void slotProgress(int, int, QString))
};
}
}
diff --git a/src/crypto/encryptemailcontroller.cpp b/src/crypto/encryptemailcontroller.cpp
index f59ddf1de..695a0f4a3 100644
--- a/src/crypto/encryptemailcontroller.cpp
+++ b/src/crypto/encryptemailcontroller.cpp
@@ -1,307 +1,305 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/encryptemailcontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include
#include "encryptemailcontroller.h"
#include "kleopatra_debug.h"
#include "encryptemailtask.h"
#include "taskcollection.h"
#include
#include
#include
#include
#include
#include
#include "emailoperationspreferences.h"
#include
-#include
#include
#include
-#include
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
using namespace GpgME;
using namespace KMime::Types;
class EncryptEMailController::Private
{
friend class ::Kleo::Crypto::EncryptEMailController;
EncryptEMailController *const q;
public:
explicit Private(Mode mode, EncryptEMailController *qq);
private:
void slotWizardCanceled();
private:
void ensureWizardCreated();
void ensureWizardVisible();
void cancelAllTasks();
void schedule();
std::shared_ptr takeRunnable(GpgME::Protocol proto);
private:
const Mode mode;
std::vector< std::shared_ptr > runnable, completed;
std::shared_ptr cms, openpgp;
QPointer wizard;
};
EncryptEMailController::Private::Private(Mode m, EncryptEMailController *qq)
: q(qq),
mode(m),
runnable(),
cms(),
openpgp(),
wizard()
{
}
EncryptEMailController::EncryptEMailController(const std::shared_ptr &xc, Mode mode, QObject *p)
: Controller(xc, p), d(new Private(mode, this))
{
}
EncryptEMailController::EncryptEMailController(Mode mode, QObject *p)
: Controller(p), d(new Private(mode, this))
{
}
EncryptEMailController::~EncryptEMailController()
{
if (d->wizard && !d->wizard->isVisible()) {
delete d->wizard;
}
//d->wizard->close(); ### ?
}
EncryptEMailController::Mode EncryptEMailController::mode() const
{
return d->mode;
}
void EncryptEMailController::setProtocol(Protocol proto)
{
d->ensureWizardCreated();
const Protocol protocol = d->wizard->presetProtocol();
kleo_assert(protocol == UnknownProtocol ||
protocol == proto);
d->wizard->setPresetProtocol(proto);
}
Protocol EncryptEMailController::protocol()
{
d->ensureWizardCreated();
return d->wizard->selectedProtocol();
}
const char *EncryptEMailController::protocolAsString()
{
switch (protocol()) {
case OpenPGP: return "OpenPGP";
case CMS: return "CMS";
default:
throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL),
i18n("Call to EncryptEMailController::protocolAsString() is ambiguous."));
}
}
void EncryptEMailController::startResolveRecipients()
{
startResolveRecipients(std::vector(), std::vector());
}
void EncryptEMailController::startResolveRecipients(const std::vector &recipients, const std::vector &senders)
{
d->ensureWizardCreated();
d->wizard->setRecipients(recipients, senders);
d->ensureWizardVisible();
}
void EncryptEMailController::Private::slotWizardCanceled()
{
q->setLastError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel"));
q->emitDoneOrError();
}
void EncryptEMailController::setInputAndOutput(const std::shared_ptr &input, const std::shared_ptr &output)
{
setInputsAndOutputs(std::vector< std::shared_ptr >(1, input), std::vector< std::shared_ptr >(1, output));
}
void EncryptEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs)
{
kleo_assert(!inputs.empty());
kleo_assert(outputs.size() == inputs.size());
std::vector< std::shared_ptr > tasks;
tasks.reserve(inputs.size());
d->ensureWizardCreated();
const std::vector keys = d->wizard->resolvedCertificates();
kleo_assert(!keys.empty());
for (unsigned int i = 0, end = inputs.size(); i < end; ++i) {
const std::shared_ptr task(new EncryptEMailTask);
task->setInput(inputs[i]);
task->setOutput(outputs[i]);
if (d->mode == ClipboardMode) {
task->setAsciiArmor(true);
}
task->setRecipients(keys);
tasks.push_back(task);
}
d->runnable.swap(tasks);
}
void EncryptEMailController::start()
{
std::shared_ptr coll(new TaskCollection);
std::vector > tmp;
std::copy(d->runnable.begin(), d->runnable.end(), std::back_inserter(tmp));
coll->setTasks(tmp);
d->ensureWizardCreated();
d->wizard->setTaskCollection(coll);
for (const std::shared_ptr &t : std::as_const(tmp)) {
connectTask(t);
}
d->schedule();
}
void EncryptEMailController::Private::schedule()
{
if (!cms)
if (const std::shared_ptr t = takeRunnable(CMS)) {
t->start();
cms = t;
}
if (!openpgp)
if (const std::shared_ptr t = takeRunnable(OpenPGP)) {
t->start();
openpgp = t;
}
if (cms || openpgp) {
return;
}
kleo_assert(runnable.empty());
q->emitDoneOrError();
}
std::shared_ptr EncryptEMailController::Private::takeRunnable(GpgME::Protocol proto)
{
const auto it = std::find_if(runnable.begin(), runnable.end(),
[proto](const std::shared_ptr &task) {
return task->protocol() == proto;
});
if (it == runnable.end()) {
return std::shared_ptr();
}
const std::shared_ptr result = *it;
runnable.erase(it);
return result;
}
void EncryptEMailController::doTaskDone(const Task *task, const std::shared_ptr &result)
{
Q_UNUSED(result)
Q_ASSERT(task);
// We could just delete the tasks here, but we can't use
// Qt::QueuedConnection here (we need sender()) and other slots
// might not yet have executed. Therefore, we push completed tasks
// into a burial container
if (task == d->cms.get()) {
d->completed.push_back(d->cms);
d->cms.reset();
} else if (task == d->openpgp.get()) {
d->completed.push_back(d->openpgp);
d->openpgp.reset();
}
QMetaObject::invokeMethod(this, [this]() { d->schedule(); }, Qt::QueuedConnection);
}
void EncryptEMailController::cancel()
{
try {
if (d->wizard) {
d->wizard->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
void EncryptEMailController::Private::cancelAllTasks()
{
// we just kill all runnable tasks - this will not result in
// signal emissions.
runnable.clear();
// a cancel() will result in a call to
if (cms) {
cms->cancel();
}
if (openpgp) {
openpgp->cancel();
}
}
void EncryptEMailController::Private::ensureWizardCreated()
{
if (wizard) {
return;
}
std::unique_ptr w(new EncryptEMailWizard);
w->setAttribute(Qt::WA_DeleteOnClose);
Kleo::EMailOperationsPreferences prefs;
w->setQuickMode(prefs.quickEncryptEMail());
connect(w.get(), &EncryptEMailWizard::recipientsResolved, q, &EncryptEMailController::recipientsResolved, Qt::QueuedConnection);
connect(w.get(), &EncryptEMailWizard::canceled, q, [this]() { slotWizardCanceled(); }, Qt::QueuedConnection);
wizard = w.release();
}
void EncryptEMailController::Private::ensureWizardVisible()
{
ensureWizardCreated();
q->bringToForeground(wizard);
}
#include "moc_encryptemailcontroller.cpp"
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index c5a61ad21..41f28ada2 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,717 +1,716 @@
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certificatelineedit.h"
#include "commands/detailscommand.h"
#include "dialogs/groupdetailsdialog.h"
#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include
#include
#include
#include
#include
#include "kleopatra_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(KeyGroup)
static QStringList s_lookedUpKeys;
namespace
{
class CompletionProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
CompletionProxyModel(QObject *parent = nullptr)
: KeyListSortFilterProxyModel(parent)
{
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// completion pop-up
return 1;
}
QVariant data(const QModelIndex &idx, int role) const override
{
if (!idx.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DecorationRole: {
const auto key = KeyListSortFilterProxyModel::data(idx, KeyList::KeyRole).value();
if (!key.isNull()) {
return Kleo::Formatting::iconForUid(key.userID(0));
}
const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value();
if (!group.isNull()) {
return QIcon::fromTheme(QStringLiteral("group"));
}
Q_ASSERT(!key.isNull() || !group.isNull());
return QVariant();
}
default:
return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role);
}
}
};
auto createSeparatorAction(QObject *parent)
{
auto action = new QAction{parent};
action->setSeparator(true);
return action;
}
} // namespace
class CertificateLineEdit::Private
{
CertificateLineEdit *q;
public:
enum class Status
{
Empty, //< text is empty
Success, //< a certificate or group is set
None, //< entered text does not match any certificates or groups
Ambiguous, //< entered text matches multiple certificates or groups
};
enum class CursorPositioning
{
MoveToEnd,
KeepPosition,
MoveToStart,
Default = MoveToEnd,
};
explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter);
QString text() const;
void setKey(const GpgME::Key &key);
void setGroup(const KeyGroup &group);
void setKeyFilter(const std::shared_ptr &filter);
void setAccessibleName(const QString &s);
private:
void updateKey(CursorPositioning positioning);
void editChanged();
void editFinished();
void checkLocate();
void onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector &keys);
void openDetailsDialog();
void setTextWithBlockedSignals(const QString &s, CursorPositioning positioning);
void showContextMenu(const QPoint &pos);
QString errorMessage() const;
QIcon statusIcon() const;
QString statusToolTip() const;
void updateStatusAction();
void updateErrorLabel();
void updateAccessibleNameAndDescription();
public:
Status mStatus = Status::Empty;
GpgME::Key mKey;
KeyGroup mGroup;
struct Ui {
explicit Ui(QWidget *parent)
: lineEdit{parent}
, button{parent}
, errorLabel{parent}
{}
QLineEdit lineEdit;
QToolButton button;
ErrorLabel errorLabel;
} ui;
private:
QString mAccessibleName;
KeyListSortFilterProxyModel *const mFilterModel;
KeyListSortFilterProxyModel *const mCompleterFilterModel;
QCompleter *mCompleter = nullptr;
std::shared_ptr mFilter;
bool mEditingInProgress = false;
QAction *const mStatusAction;
QAction *const mShowDetailsAction;
QPointer mLocateJob;
};
CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter)
: q{qq}
, ui{qq}
, mFilterModel{new KeyListSortFilterProxyModel{qq}}
, mCompleterFilterModel{new CompletionProxyModel{qq}}
, mCompleter{new QCompleter{qq}}
, mFilter{std::shared_ptr{filter}}
, mStatusAction{new QAction{qq}}
, mShowDetailsAction{new QAction{qq}}
{
ui.lineEdit.setPlaceholderText(i18n("Please enter a name or email address..."));
ui.lineEdit.setClearButtonEnabled(true);
ui.lineEdit.setContextMenuPolicy(Qt::CustomContextMenu);
ui.lineEdit.addAction(mStatusAction, QLineEdit::LeadingPosition);
mCompleterFilterModel->setKeyFilter(mFilter);
mCompleterFilterModel->setSourceModel(model);
mCompleter->setModel(mCompleterFilterModel);
mCompleter->setFilterMode(Qt::MatchContains);
mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
ui.lineEdit.setCompleter(mCompleter);
ui.button.setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
ui.button.setToolTip(i18n("Show certificate list"));
ui.button.setAccessibleName(i18n("Show certificate list"));
ui.errorLabel.setVisible(false);
auto vbox = new QVBoxLayout{q};
vbox->setContentsMargins(0, 0, 0, 0);
auto l = new QHBoxLayout;
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(&ui.lineEdit);
l->addWidget(&ui.button);
vbox->addLayout(l);
vbox->addWidget(&ui.errorLabel);
q->setFocusPolicy(ui.lineEdit.focusPolicy());
q->setFocusProxy(&ui.lineEdit);
mShowDetailsAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
mShowDetailsAction->setText(i18nc("@action:inmenu", "Show Details"));
mShowDetailsAction->setEnabled(false);
mFilterModel->setSourceModel(model);
mFilterModel->setFilterKeyColumn(KeyList::Summary);
if (filter) {
mFilterModel->setKeyFilter(mFilter);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
q, [this]() { updateKey(CursorPositioning::KeepPosition); });
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
setTextWithBlockedSignals(Formatting::summaryLine(group), CursorPositioning::KeepPosition);
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection);
}
});
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved,
q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
mGroup = KeyGroup();
QSignalBlocker blocky{&ui.lineEdit};
ui.lineEdit.clear();
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection);
}
});
connect(&ui.lineEdit, &QLineEdit::editingFinished,
q, [this]() {
// queue the call of editFinished() to ensure that QCompleter::activated is handled first
QMetaObject::invokeMethod(q, [this]() { editFinished(); }, Qt::QueuedConnection);
});
connect(&ui.lineEdit, &QLineEdit::textChanged,
q, [this]() { editChanged(); });
connect(&ui.lineEdit, &QLineEdit::customContextMenuRequested,
q, [this](const QPoint &pos) { showContextMenu(pos); });
connect(mStatusAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(mShowDetailsAction, &QAction::triggered,
q, [this]() { openDetailsDialog(); });
connect(&ui.button, &QToolButton::clicked,
q, &CertificateLineEdit::certificateSelectionRequested);
connect(mCompleter, qOverload(&QCompleter::activated),
q, [this] (const QModelIndex &index) {
Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value();
auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value();
if (!key.isNull()) {
q->setKey(key);
} else if (!group.isNull()) {
q->setGroup(group);
} else {
qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group";
}
});
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::Private::openDetailsDialog()
{
if (!q->key().isNull()) {
auto cmd = new Commands::DetailsCommand{q->key()};
cmd->setParentWidget(q);
cmd->start();
} else if (!q->group().isNull()) {
auto dlg = new Dialogs::GroupDetailsDialog{q};
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setGroup(q->group());
dlg->show();
}
}
void CertificateLineEdit::Private::setTextWithBlockedSignals(const QString &s, CursorPositioning positioning)
{
QSignalBlocker blocky{&ui.lineEdit};
const auto cursorPos = ui.lineEdit.cursorPosition();
ui.lineEdit.setText(s);
switch(positioning)
{
case CursorPositioning::KeepPosition:
ui.lineEdit.setCursorPosition(cursorPos);
break;
case CursorPositioning::MoveToStart:
ui.lineEdit.setCursorPosition(0);
break;
case CursorPositioning::MoveToEnd:
default:
; // setText() already moved the cursor to the end of the line
};
}
void CertificateLineEdit::Private::showContextMenu(const QPoint &pos)
{
if (QMenu *menu = ui.lineEdit.createStandardContextMenu()) {
auto *const firstStandardAction = menu->actions().value(0);
menu->insertActions(firstStandardAction,
{mShowDetailsAction, createSeparatorAction(menu)});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(ui.lineEdit.mapToGlobal(pos));
}
}
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model,
KeyFilter *filter,
QWidget *parent)
: QWidget{parent}
, d{new Private{this, model, filter}}
{
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model->setParent(parent ? parent : this);
}
CertificateLineEdit::~CertificateLineEdit() = default;
void CertificateLineEdit::Private::editChanged()
{
const bool editingStarted = !mEditingInProgress;
mEditingInProgress = true;
updateKey(CursorPositioning::Default);
if (editingStarted) {
Q_EMIT q->editingStarted();
}
}
void CertificateLineEdit::Private::editFinished()
{
// perform a first update with the "editing in progress" flag still set
updateKey(CursorPositioning::MoveToStart);
mEditingInProgress = false;
checkLocate();
// perform another update with the "editing in progress" flag cleared
// after a key locate may have been started; this makes sure that displaying
// an error is delayed until the key locate job has finished
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::Private::checkLocate()
{
if (mStatus != Status::None) {
// try to locate key only if text matches no local certificates or groups
return;
}
// Only check once per mailbox
const auto mailText = ui.lineEdit.text().trimmed();
if (mailText.isEmpty() || s_lookedUpKeys.contains(mailText)) {
return;
}
s_lookedUpKeys << mailText;
if (mLocateJob) {
mLocateJob->slotCancel();
mLocateJob.clear();
}
auto job = QGpgME::openpgp()->locateKeysJob();
connect(job, &QGpgME::KeyListJob::result, q, [this, job, mailText](const KeyListResult &result, const std::vector &keys) {
onLocateJobResult(job, mailText, result, keys);
});
if (auto err = job->start({mailText}, /*secretOnly=*/false)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Starting" << job << "for" << mailText << "failed with" << err.asString();
} else {
mLocateJob = job;
qCDebug(KLEOPATRA_LOG) << __func__ << "Started" << job << "for" << mailText;
}
}
void CertificateLineEdit::Private::onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector &keys)
{
if (mLocateJob != job) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Ignoring outdated finished" << job << "for" << email;
return;
}
qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << result.error().asString() << "and keys" << keys;
mLocateJob.clear();
if (!keys.empty() && !keys.front().isNull()) {
KeyCache::mutableInstance()->insert(keys.front());
// inserting the key implicitly triggers an update
} else {
// explicitly trigger an update to display "no key" error
updateKey(CursorPositioning::MoveToStart);
}
}
void CertificateLineEdit::Private::updateKey(CursorPositioning positioning)
{
static const _detail::ByFingerprint keysHaveSameFingerprint;
const auto mailText = ui.lineEdit.text().trimmed();
auto newKey = Key();
auto newGroup = KeyGroup();
if (mailText.isEmpty()) {
mStatus = Status::Empty;
} else {
mFilterModel->setFilterRegularExpression(QRegularExpression::escape(mailText));
if (mFilterModel->rowCount() > 1) {
// keep current key or group if they still match
if (!mKey.isNull()) {
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
Key key = mFilterModel->key(index);
if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) {
newKey = mKey;
break;
}
}
} else if (!mGroup.isNull()) {
newGroup = mGroup;
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
KeyGroup group = mFilterModel->group(index);
if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) {
newGroup = mGroup;
break;
}
}
}
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::Ambiguous;
}
} else if (mFilterModel->rowCount() == 1) {
const auto index = mFilterModel->index(0, 0);
newKey = mFilterModel->data(index, KeyList::KeyRole).value();
newGroup = mFilterModel->data(index, KeyList::GroupRole).value();
Q_ASSERT(!newKey.isNull() || !newGroup.isNull());
if (newKey.isNull() && newGroup.isNull()) {
mStatus = Status::None;
}
} else {
mStatus = Status::None;
}
}
mKey = newKey;
mGroup = newGroup;
if (!mKey.isNull()) {
/* FIXME: This needs to be solved by a multiple UID supporting model */
mStatus = Status::Success;
ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mKey), positioning);
}
} else if (!mGroup.isNull()) {
mStatus = Status::Success;
ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mGroup), positioning);
}
} else {
ui.lineEdit.setToolTip({});
}
mShowDetailsAction->setEnabled(mStatus == Status::Success);
updateStatusAction();
updateErrorLabel();
Q_EMIT q->keyChanged();
}
QString CertificateLineEdit::Private::errorMessage() const
{
switch (mStatus) {
case Status::Empty:
case Status::Success:
return {};
case Status::None:
return i18n("No matching certificates or groups found");
case Status::Ambiguous:
return i18n("Multiple matching certificates or groups found");
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
QIcon CertificateLineEdit::Private::statusIcon() const
{
switch (mStatus) {
case Status::Empty:
return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
case Status::Success:
if (!mKey.isNull()) {
return Formatting::iconForUid(mKey.userID(0));
} else if (!mGroup.isNull()) {
return Formatting::validityIcon(mGroup);
} else {
qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group.";
return {};
}
case Status::None:
case Status::Ambiguous:
if (mEditingInProgress || mLocateJob) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
} else {
return QIcon::fromTheme(QStringLiteral("emblem-error"));
}
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast