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("
      "))) + 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%1as the primary user ID." "%2", QString::fromUtf8(userId.id()), QString::fromLocal8Bit(err.asString()))); } void SetPrimaryUserIDCommand::Private::showSuccessDialog() { success(xi18nc("@info", "The user ID%1has 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(mStatus); Q_ASSERT(!"Invalid status"); }; return {}; } QString CertificateLineEdit::Private::statusToolTip() const { switch (mStatus) { case Status::Empty: return {}; case Status::Success: if (!mKey.isNull()) { return Formatting::validity(mKey.userID(0)); } else if (!mGroup.isNull()) { return Formatting::validity(mGroup); } else { qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group."; return {}; } case Status::None: case Status::Ambiguous: return errorMessage(); default: qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast(mStatus); Q_ASSERT(!"Invalid status"); }; return {}; } void CertificateLineEdit::Private::updateStatusAction() { mStatusAction->setIcon(statusIcon()); mStatusAction->setToolTip(statusToolTip()); } namespace { QString decoratedError(const QString &text) { return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text); } } void CertificateLineEdit::Private::updateErrorLabel() { const auto currentErrorMessage = ui.errorLabel.text(); const auto newErrorMessage = decoratedError(errorMessage()); if (newErrorMessage == currentErrorMessage) { return; } if (currentErrorMessage.isEmpty() && (mEditingInProgress || mLocateJob)) { // delay showing the error message until editing is finished, so that we // do not annoy the user with an error message while they are still // entering the recipient; // on the other hand, we clear the error message immediately if it does // not apply anymore and we update the error message immediately if it // changed return; } ui.errorLabel.setVisible(!newErrorMessage.isEmpty()); ui.errorLabel.setText(newErrorMessage); updateAccessibleNameAndDescription(); } void CertificateLineEdit::Private::setAccessibleName(const QString &s) { mAccessibleName = s; updateAccessibleNameAndDescription(); } void CertificateLineEdit::Private::updateAccessibleNameAndDescription() { // fall back to default accessible name if accessible name wasn't set explicitly if (mAccessibleName.isEmpty()) { mAccessibleName = getAccessibleName(&ui.lineEdit); } const bool errorShown = ui.errorLabel.isVisible(); // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); // emulate this by setting the error message as accessible description of the input field const auto description = errorShown ? ui.errorLabel.text() : QString{}; if (ui.lineEdit.accessibleDescription() != description) { ui.lineEdit.setAccessibleDescription(description); } // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); // screen readers say something like "invalid data" if this state is set; // emulate this by adding "invalid data" to the accessible name of the input field const auto name = errorShown ? mAccessibleName + QLatin1String{", "} + invalidEntryText() : mAccessibleName; if (ui.lineEdit.accessibleName() != name) { ui.lineEdit.setAccessibleName(name); } } Key CertificateLineEdit::key() const { if (isEnabled()) { return d->mKey; } else { return Key(); } } KeyGroup CertificateLineEdit::group() const { if (isEnabled()) { return d->mGroup; } else { return KeyGroup(); } } QString CertificateLineEdit::Private::text() const { return ui.lineEdit.text().trimmed(); } QString CertificateLineEdit::text() const { return d->text(); } void CertificateLineEdit::Private::setKey(const Key &key) { mKey = key; mGroup = KeyGroup(); qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key); // position cursor, so that that the start of the summary is visible setTextWithBlockedSignals(Formatting::summaryLine(key), CursorPositioning::MoveToStart); updateKey(CursorPositioning::MoveToStart); } void CertificateLineEdit::setKey(const Key &key) { d->setKey(key); } void CertificateLineEdit::Private::setGroup(const KeyGroup &group) { mGroup = group; mKey = Key(); const QString summary = Formatting::summaryLine(group); qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary; // position cursor, so that that the start of the summary is visible setTextWithBlockedSignals(summary, CursorPositioning::MoveToStart); updateKey(CursorPositioning::MoveToStart); } void CertificateLineEdit::setGroup(const KeyGroup &group) { d->setGroup(group); } bool CertificateLineEdit::isEmpty() const { return d->mStatus == Private::Status::Empty; } bool CertificateLineEdit::hasAcceptableInput() const { return d->mStatus == Private::Status::Empty || d->mStatus == Private::Status::Success; } void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr &filter) { mFilter = filter; mFilterModel->setKeyFilter(filter); mCompleterFilterModel->setKeyFilter(mFilter); updateKey(CursorPositioning::Default); } void CertificateLineEdit::setKeyFilter(const std::shared_ptr &filter) { d->setKeyFilter(filter); } void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name) { d->setAccessibleName(name); } #include "certificatelineedit.moc" diff --git a/src/crypto/gui/resolverecipientspage.cpp b/src/crypto/gui/resolverecipientspage.cpp index c22b6d4b1..c2968f062 100644 --- a/src/crypto/gui/resolverecipientspage.cpp +++ b/src/crypto/gui/resolverecipientspage.cpp @@ -1,698 +1,697 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resolverecipientspage.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 "resolverecipientspage.h" #include "resolverecipientspage_p.h" #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Dialogs; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace KMime::Types; ResolveRecipientsPage::ListWidget::ListWidget(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), m_protocol(UnknownProtocol) { m_listWidget = new QListWidget; m_listWidget->setSelectionMode(QAbstractItemView::MultiSelection); auto const layout = new QVBoxLayout(this); layout->addWidget(m_listWidget); connect(m_listWidget, &QListWidget::itemSelectionChanged, this, &ListWidget::onSelectionChange); } ResolveRecipientsPage::ListWidget::~ListWidget() { } void ResolveRecipientsPage::ListWidget::onSelectionChange() { const auto widgetskeys = widgets.keys(); for (const QString &i : widgetskeys) { Q_ASSERT(items.contains(i)); widgets[i]->setSelected(items[i]->isSelected()); } Q_EMIT selectionChanged(); } void ResolveRecipientsPage::ListWidget::addEntry(const Mailbox &mbox) { addEntry(mbox.prettyAddress(), mbox.prettyAddress(), mbox); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name) { addEntry(id, name, Mailbox()); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name, const Mailbox &mbox) { Q_ASSERT(!widgets.contains(id) && !items.contains(id)); auto item = new QListWidgetItem; item->setData(IdRole, id); auto wid = new ItemWidget(id, name, mbox, this); connect(wid, &ItemWidget::changed, this, &ListWidget::completeChanged); wid->setProtocol(m_protocol); item->setSizeHint(wid->sizeHint()); m_listWidget->addItem(item); m_listWidget->setItemWidget(item, wid); widgets[id] = wid; items[id] = item; } Mailbox ResolveRecipientsPage::ListWidget::mailbox(const QString &id) const { return widgets.contains(id) ? widgets[id]->mailbox() : Mailbox(); } void ResolveRecipientsPage::ListWidget::setCertificates(const QString &id, const std::vector &pgp, const std::vector &cms) { Q_ASSERT(widgets.contains(id)); widgets[id]->setCertificates(pgp, cms); } Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id) const { return widgets.contains(id) ? widgets[id]->selectedCertificate() : Key(); } GpgME::Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id, GpgME::Protocol prot) const { return widgets.contains(id) ? widgets[id]->selectedCertificate(prot) : Key(); } QStringList ResolveRecipientsPage::ListWidget::identifiers() const { return widgets.keys(); } void ResolveRecipientsPage::ListWidget::setProtocol(GpgME::Protocol prot) { if (m_protocol == prot) { return; } m_protocol = prot; for (ItemWidget *i : std::as_const(widgets)) { i->setProtocol(prot); } } void ResolveRecipientsPage::ListWidget::removeEntry(const QString &id) { if (!widgets.contains(id)) { return; } delete items[id]; items.remove(id); delete widgets[id]; widgets.remove(id); } void ResolveRecipientsPage::ListWidget::showSelectionDialog(const QString &id) { if (!widgets.contains(id)) { return; } widgets[id]->showSelectionDialog(); } QStringList ResolveRecipientsPage::ListWidget::selectedEntries() const { QStringList entries; const QList items = m_listWidget->selectedItems(); entries.reserve(items.count()); for (const QListWidgetItem *i : items) { entries.append(i->data(IdRole).toString()); } return entries; } ResolveRecipientsPage::ItemWidget::ItemWidget(const QString &id, const QString &name, const Mailbox &mbox, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), m_id(id), m_mailbox(mbox), m_protocol(UnknownProtocol), m_selected(false) { Q_ASSERT(!m_id.isEmpty()); setAutoFillBackground(true); auto layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(15); m_nameLabel = new QLabel; m_nameLabel->setText(name); layout->addWidget(m_nameLabel); layout->addStretch(); m_certLabel = new QLabel; m_certLabel->setText(i18n("No certificate selected")); layout->addWidget(m_certLabel); m_certCombo = new QComboBox; connect(m_certCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); layout->addWidget(m_certCombo); m_selectButton = new QToolButton; m_selectButton->setText(i18n("...")); connect(m_selectButton, &QAbstractButton::clicked, this, &ItemWidget::showSelectionDialog); layout->addWidget(m_selectButton); layout->addSpacing(15); setCertificates(std::vector(), std::vector()); } void ResolveRecipientsPage::ItemWidget::updateVisibility() { m_certLabel->setVisible(m_certCombo->count() == 0); m_certCombo->setVisible(m_certCombo->count() > 0); } ResolveRecipientsPage::ItemWidget::~ItemWidget() { } QString ResolveRecipientsPage::ItemWidget::id() const { return m_id; } void ResolveRecipientsPage::ItemWidget::setSelected(bool selected) { if (m_selected == selected) { return; } m_selected = selected; setBackgroundRole(selected ? QPalette::Highlight : QPalette::Base); const QPalette::ColorRole foreground = selected ? QPalette::HighlightedText : QPalette::Text; setForegroundRole(foreground); m_nameLabel->setForegroundRole(foreground); m_certLabel->setForegroundRole(foreground); } bool ResolveRecipientsPage::ItemWidget::isSelected() const { return m_selected; } static CertificateSelectionDialog *createCertificateSelectionDialog(QWidget *parent, GpgME::Protocol prot) { auto const dlg = new CertificateSelectionDialog(parent); const CertificateSelectionDialog::Options options = CertificateSelectionDialog::SingleSelection | CertificateSelectionDialog::EncryptOnly | CertificateSelectionDialog::MultiSelection | CertificateSelectionDialog::optionsFromProtocol(prot); dlg->setOptions(options); return dlg; } void ResolveRecipientsPage::ItemWidget::showSelectionDialog() { QPointer dlg = createCertificateSelectionDialog(this, m_protocol); if (dlg->exec() == QDialog::Accepted && dlg /* still with us? */) { const GpgME::Key cert = dlg->selectedCertificate(); if (!cert.isNull()) { addCertificateToComboBox(cert); selectCertificateInComboBox(cert); } } delete dlg; } Mailbox ResolveRecipientsPage::ItemWidget::mailbox() const { return m_mailbox; } void ResolveRecipientsPage::ItemWidget::selectCertificateInComboBox(const Key &key) { m_certCombo->setCurrentIndex(m_certCombo->findData(QLatin1String(key.keyID()))); } void ResolveRecipientsPage::ItemWidget::addCertificateToComboBox(const GpgME::Key &key) { m_certCombo->addItem(Formatting::formatForComboBox(key), QByteArray(key.keyID())); if (m_certCombo->count() == 1) { m_certCombo->setCurrentIndex(0); } updateVisibility(); } void ResolveRecipientsPage::ItemWidget::resetCertificates() { std::vector certs; Key selected; switch (m_protocol) { case OpenPGP: certs = m_pgp; break; case CMS: certs = m_cms; break; case UnknownProtocol: certs = m_cms; certs.insert(certs.end(), m_pgp.begin(), m_pgp.end()); } m_certCombo->clear(); for (const Key &i : std::as_const(certs)) { addCertificateToComboBox(i); } if (!m_selectedCertificates[m_protocol].isNull()) { selectCertificateInComboBox(m_selectedCertificates[m_protocol]); } else if (m_certCombo->count() > 0) { m_certCombo->setCurrentIndex(0); } updateVisibility(); Q_EMIT changed(); } void ResolveRecipientsPage::ItemWidget::setProtocol(Protocol prot) { if (m_protocol == prot) { return; } m_selectedCertificates[m_protocol] = selectedCertificate(); if (m_protocol != UnknownProtocol) { (m_protocol == OpenPGP ? m_pgp : m_cms) = certificates(); } m_protocol = prot; resetCertificates(); } void ResolveRecipientsPage::ItemWidget::setCertificates(const std::vector &pgp, const std::vector &cms) { m_pgp = pgp; m_cms = cms; resetCertificates(); } Key ResolveRecipientsPage::ItemWidget::selectedCertificate() const { return KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(m_certCombo->currentIndex(), ListWidget::IdRole).toString().toStdString()); } GpgME::Key ResolveRecipientsPage::ItemWidget::selectedCertificate(GpgME::Protocol prot) const { return prot == m_protocol ? selectedCertificate() : m_selectedCertificates.value(prot); } std::vector ResolveRecipientsPage::ItemWidget::certificates() const { std::vector certs; for (int i = 0; i < m_certCombo->count(); ++i) { certs.push_back(KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(i, ListWidget::IdRole).toString().toStdString())); } return certs; } class ResolveRecipientsPage::Private { friend class ::Kleo::Crypto::Gui::ResolveRecipientsPage; ResolveRecipientsPage *const q; public: explicit Private(ResolveRecipientsPage *qq); ~Private(); void setSelectedProtocol(Protocol protocol); void selectionChanged(); void removeSelectedEntries(); void addRecipient(); void addRecipient(const Mailbox &mbox); void addRecipient(const QString &id, const QString &name); void updateProtocolRBVisibility(); void protocolSelected(int prot); void writeSelectedCertificatesToPreferences(); void completeChangedInternal(); private: ListWidget *m_listWidget; QPushButton *m_addButton; QPushButton *m_removeButton; QRadioButton *m_pgpRB; QRadioButton *m_cmsRB; QLabel *m_additionalRecipientsLabel; Protocol m_presetProtocol; Protocol m_selectedProtocol; bool m_multipleProtocolsAllowed; std::shared_ptr m_recipientPreferences; }; ResolveRecipientsPage::Private::Private(ResolveRecipientsPage *qq) : q(qq), m_presetProtocol(UnknownProtocol), m_selectedProtocol(m_presetProtocol), m_multipleProtocolsAllowed(false), m_recipientPreferences() { connect(q, SIGNAL(completeChanged()), q, SLOT(completeChangedInternal())); q->setTitle(i18n("Recipients")); auto const layout = new QVBoxLayout(q); m_listWidget = new ListWidget; connect(m_listWidget, SIGNAL(selectionChanged()), q, SLOT(selectionChanged())); connect(m_listWidget, &ListWidget::completeChanged, q, &WizardPage::completeChanged); layout->addWidget(m_listWidget); m_additionalRecipientsLabel = new QLabel; m_additionalRecipientsLabel->setWordWrap(true); layout->addWidget(m_additionalRecipientsLabel); m_additionalRecipientsLabel->setVisible(false); auto buttonWidget = new QWidget; auto buttonLayout = new QHBoxLayout(buttonWidget); buttonLayout->setContentsMargins(0, 0, 0, 0); m_addButton = new QPushButton; connect(m_addButton, SIGNAL(clicked()), q, SLOT(addRecipient())); m_addButton->setText(i18n("Add Recipient...")); buttonLayout->addWidget(m_addButton); m_removeButton = new QPushButton; m_removeButton->setEnabled(false); m_removeButton->setText(i18n("Remove Selected")); connect(m_removeButton, SIGNAL(clicked()), q, SLOT(removeSelectedEntries())); buttonLayout->addWidget(m_removeButton); buttonLayout->addStretch(); layout->addWidget(buttonWidget); auto protocolWidget = new QWidget; auto protocolLayout = new QHBoxLayout(protocolWidget); auto protocolGroup = new QButtonGroup(q); connect(protocolGroup, SIGNAL(buttonClicked(int)), q, SLOT(protocolSelected(int))); m_pgpRB = new QRadioButton; m_pgpRB->setText(i18n("OpenPGP")); protocolGroup->addButton(m_pgpRB, OpenPGP); protocolLayout->addWidget(m_pgpRB); m_cmsRB = new QRadioButton; m_cmsRB->setText(i18n("S/MIME")); protocolGroup->addButton(m_cmsRB, CMS); protocolLayout->addWidget(m_cmsRB); protocolLayout->addStretch(); layout->addWidget(protocolWidget); } ResolveRecipientsPage::Private::~Private() {} void ResolveRecipientsPage::Private::completeChangedInternal() { const bool isComplete = q->isComplete(); const std::vector keys = q->resolvedCertificates(); const bool haveSecret = std::find_if(keys.begin(), keys.end(), [](const Key &key) { return key.hasSecret(); }) != keys.end(); if (isComplete && !haveSecret) { q->setExplanation(i18n("Warning: None of the selected certificates seem to be your own. You will not be able to decrypt the encrypted data again.")); } else { q->setExplanation(QString()); } } void ResolveRecipientsPage::Private::updateProtocolRBVisibility() { const bool visible = !m_multipleProtocolsAllowed && m_presetProtocol == UnknownProtocol; m_cmsRB->setVisible(visible); m_pgpRB->setVisible(visible); if (visible) { if (m_selectedProtocol == CMS) { m_cmsRB->click(); } else { m_pgpRB->click(); } } } bool ResolveRecipientsPage::isComplete() const { const QStringList ids = d->m_listWidget->identifiers(); if (ids.isEmpty()) { return false; } for (const QString &i : ids) { if (d->m_listWidget->selectedCertificate(i).isNull()) { return false; } } return true; } ResolveRecipientsPage::ResolveRecipientsPage(QWidget *parent) : WizardPage(parent), d(new Private(this)) { } ResolveRecipientsPage::~ResolveRecipientsPage() {} Protocol ResolveRecipientsPage::selectedProtocol() const { return d->m_selectedProtocol; } void ResolveRecipientsPage::Private::setSelectedProtocol(Protocol protocol) { if (m_selectedProtocol == protocol) { return; } m_selectedProtocol = protocol; m_listWidget->setProtocol(m_selectedProtocol); Q_EMIT q->selectedProtocolChanged(); } void ResolveRecipientsPage::Private::protocolSelected(int p) { const auto protocol = static_cast(p); Q_ASSERT(protocol != UnknownProtocol); setSelectedProtocol(protocol); } void ResolveRecipientsPage::setPresetProtocol(Protocol prot) { if (d->m_presetProtocol == prot) { return; } d->m_presetProtocol = prot; d->setSelectedProtocol(prot); if (prot != UnknownProtocol) { d->m_multipleProtocolsAllowed = false; } d->updateProtocolRBVisibility(); } Protocol ResolveRecipientsPage::presetProtocol() const { return d->m_presetProtocol; } bool ResolveRecipientsPage::multipleProtocolsAllowed() const { return d->m_multipleProtocolsAllowed; } void ResolveRecipientsPage::setMultipleProtocolsAllowed(bool allowed) { if (d->m_multipleProtocolsAllowed == allowed) { return; } d->m_multipleProtocolsAllowed = allowed; if (d->m_multipleProtocolsAllowed) { setPresetProtocol(UnknownProtocol); d->setSelectedProtocol(UnknownProtocol); } d->updateProtocolRBVisibility(); } void ResolveRecipientsPage::Private::addRecipient(const QString &id, const QString &name) { m_listWidget->addEntry(id, name); } void ResolveRecipientsPage::Private::addRecipient(const Mailbox &mbox) { m_listWidget->addEntry(mbox); } void ResolveRecipientsPage::Private::addRecipient() { QPointer dlg = createCertificateSelectionDialog(q, q->selectedProtocol()); if (dlg->exec() != QDialog::Accepted || !dlg /*q already deleted*/) { return; } const std::vector keys = dlg->selectedCertificates(); int i = 0; for (const Key &key : keys) { const QStringList existing = m_listWidget->identifiers(); QString rec = i18n("Recipient"); while (existing.contains(rec)) { rec = i18nc("%1 == number", "Recipient (%1)", ++i); } addRecipient(rec, rec); const std::vector pgp = key.protocol() == OpenPGP ? std::vector(1, key) : std::vector(); const std::vector cms = key.protocol() == CMS ? std::vector(1, key) : std::vector(); m_listWidget->setCertificates(rec, pgp, cms); } Q_EMIT q->completeChanged(); } namespace { std::vector makeSuggestions(const std::shared_ptr &prefs, const Mailbox &mb, GpgME::Protocol prot) { std::vector suggestions; const Key remembered = prefs ? prefs->preferredCertificate(mb, prot) : Key(); if (!remembered.isNull()) { suggestions.push_back(remembered); } else { suggestions = CertificateResolver::resolveRecipient(mb, prot); } return suggestions; } } static QString listKeysForInfo(const std::vector &keys) { QStringList list; std::transform(keys.begin(), keys.end(), list.begin(), &Formatting::formatKeyLink); return list.join(QLatin1String("
    ")); } void ResolveRecipientsPage::setAdditionalRecipientsInfo(const std::vector &recipients) { d->m_additionalRecipientsLabel->setVisible(!recipients.empty()); if (recipients.empty()) { return; } d->m_additionalRecipientsLabel->setText( i18n("

    Recipients predefined via GnuPG settings:

    %1
    ", listKeysForInfo(recipients))); } void ResolveRecipientsPage::setRecipients(const std::vector &recipients, const std::vector &encryptToSelfRecipients) { uint cmsCount = 0; uint pgpCount = 0; uint senders = 0; for (const Mailbox &mb : encryptToSelfRecipients) { const QString id = QLatin1String("sender-") + QString::number(++senders); d->m_listWidget->addEntry(id, i18n("Sender"), mb); const std::vector pgp = makeSuggestions(d->m_recipientPreferences, mb, OpenPGP); const std::vector cms = makeSuggestions(d->m_recipientPreferences, mb, CMS); pgpCount += !pgp.empty(); cmsCount += !cms.empty(); d->m_listWidget->setCertificates(id, pgp, cms); } for (const Mailbox &i : recipients) { //TODO: const QString address = i.prettyAddress(); d->addRecipient(i); const std::vector pgp = makeSuggestions(d->m_recipientPreferences, i, OpenPGP); const std::vector cms = makeSuggestions(d->m_recipientPreferences, i, CMS); pgpCount += pgp.empty() ? 0 : 1; cmsCount += cms.empty() ? 0 : 1; d->m_listWidget->setCertificates(address, pgp, cms); } if (d->m_presetProtocol == UnknownProtocol && !d->m_multipleProtocolsAllowed) { (cmsCount > pgpCount ? d->m_cmsRB : d->m_pgpRB)->click(); } } std::vector ResolveRecipientsPage::resolvedCertificates() const { std::vector certs; const QStringList identifiers = d->m_listWidget->identifiers(); for (const QString &i : identifiers) { const GpgME::Key cert = d->m_listWidget->selectedCertificate(i); if (!cert.isNull()) { certs.push_back(cert); } } return certs; } void ResolveRecipientsPage::Private::selectionChanged() { m_removeButton->setEnabled(!m_listWidget->selectedEntries().isEmpty()); } void ResolveRecipientsPage::Private::removeSelectedEntries() { const auto selectedEntries{m_listWidget->selectedEntries()}; for (const QString &i : selectedEntries) { m_listWidget->removeEntry(i); } Q_EMIT q->completeChanged(); } void ResolveRecipientsPage::setRecipientsUserMutable(bool isMutable) { d->m_addButton->setVisible(isMutable); d->m_removeButton->setVisible(isMutable); } bool ResolveRecipientsPage::recipientsUserMutable() const { return d->m_addButton->isVisible(); } std::shared_ptr ResolveRecipientsPage::recipientPreferences() const { return d->m_recipientPreferences; } void ResolveRecipientsPage::setRecipientPreferences(const std::shared_ptr &prefs) { d->m_recipientPreferences = prefs; } void ResolveRecipientsPage::Private::writeSelectedCertificatesToPreferences() { if (!m_recipientPreferences) { return; } const auto identifiers{m_listWidget->identifiers()}; for (const QString &i : identifiers) { const Mailbox mbox = m_listWidget->mailbox(i); if (!mbox.hasAddress()) { continue; } const Key pgp = m_listWidget->selectedCertificate(i, OpenPGP); if (!pgp.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, OpenPGP, pgp); } const Key cms = m_listWidget->selectedCertificate(i, CMS); if (!cms.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, CMS, cms); } } } void ResolveRecipientsPage::onNext() { d->writeSelectedCertificatesToPreferences(); } #include "moc_resolverecipientspage_p.cpp" #include "moc_resolverecipientspage.cpp" diff --git a/src/crypto/gui/signencryptfileswizard.h b/src/crypto/gui/signencryptfileswizard.h index e91b2b331..80bdacdbb 100644 --- a/src/crypto/gui/signencryptfileswizard.h +++ b/src/crypto/gui/signencryptfileswizard.h @@ -1,104 +1,103 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signencryptfileswizard.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include -#include #include #include namespace GpgME { class Key; } namespace Kleo { namespace Crypto { class TaskCollection; } } class ResultPage; class SigEncPage; namespace Kleo { class SignEncryptFilesWizard : public QWizard { Q_OBJECT public: enum KindNames { SignatureCMS, SignaturePGP, CombinedPGP, EncryptedPGP, EncryptedCMS, Directory }; explicit SignEncryptFilesWizard(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~SignEncryptFilesWizard() override; // Inputs void setSigningPreset(bool preset); void setSigningUserMutable(bool mut); void setEncryptionPreset(bool preset); void setEncryptionUserMutable(bool mut); void setArchiveForced(bool archive); void setArchiveMutable(bool archive); void setSingleFile(bool singleFile); void setOutputNames(const QMap &nameMap) const; QMap outputNames() const; void setTaskCollection(const std::shared_ptr &coll); // Outputs std::vector resolvedRecipients() const; std::vector resolvedSigners() const; bool encryptSymmetric() const; void setLabelText(const QString &label); protected: void readConfig(); void writeConfig(); Q_SIGNALS: void operationPrepared(); private Q_SLOTS: void slotCurrentIdChanged(int); private: SigEncPage *mSigEncPage = nullptr; ResultPage *mResultPage = nullptr; bool mSigningUserMutable = true; bool mEncryptionUserMutable = true; }; } diff --git a/src/crypto/gui/signerresolvepage.cpp b/src/crypto/gui/signerresolvepage.cpp index 1982d3751..a625603ff 100644 --- a/src/crypto/gui/signerresolvepage.cpp +++ b/src/crypto/gui/signerresolvepage.cpp @@ -1,668 +1,667 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signerresolvepage.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 "signerresolvepage.h" #include "signerresolvepage_p.h" #include "signingcertificateselectiondialog.h" #include "crypto/certificateresolver.h" #include "utils/keys.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { static SignerResolvePage::Operation operationFromFlags(bool sign, bool encrypt) { if (!encrypt && sign) { return SignerResolvePage::SignOnly; } if (!sign && encrypt) { return SignerResolvePage::EncryptOnly; } return SignerResolvePage::SignAndEncrypt; } static QString formatLabel(Protocol p, const Key &key) { return i18nc("%1=protocol (S/Mime, OpenPGP), %2=certificate", "Sign using %1: %2", Formatting::displayName(p), !key.isNull() ? Formatting::formatForComboBox(key) : i18n("No certificate selected")); } static std::vector supportedProtocols() { std::vector protocols; protocols.push_back(OpenPGP); protocols.push_back(CMS); return protocols; } } AbstractSigningProtocolSelectionWidget::AbstractSigningProtocolSelectionWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f) { } ReadOnlyProtocolSelectionWidget::ReadOnlyProtocolSelectionWidget(QWidget *p, Qt::WindowFlags f) : AbstractSigningProtocolSelectionWidget(p, f) { auto const layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); const auto supportedProtocolsLst = supportedProtocols(); for (const Protocol i: supportedProtocolsLst) { auto const l = new QLabel; l->setText(formatLabel(i, Key())); layout->addWidget(l); m_labels[i] = l; } } void ReadOnlyProtocolSelectionWidget::setProtocolChecked(Protocol protocol, bool checked) { QLabel *const l = label(protocol); Q_ASSERT(l); l->setVisible(checked); } bool ReadOnlyProtocolSelectionWidget::isProtocolChecked(Protocol protocol) const { QLabel *const l = label(protocol); Q_ASSERT(l); return l->isVisible(); } std::set ReadOnlyProtocolSelectionWidget::checkedProtocols() const { std::set res; for (const Protocol i : supportedProtocols()) { if (isProtocolChecked(i)) { res.insert(i); } } return res; } SigningProtocolSelectionWidget::SigningProtocolSelectionWidget(QWidget *parent, Qt::WindowFlags f) : AbstractSigningProtocolSelectionWidget(parent, f) { m_buttonGroup = new QButtonGroup(this); connect(m_buttonGroup, &QButtonGroup::idClicked, this, &SigningProtocolSelectionWidget::userSelectionChanged); auto const layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); for (const Protocol i : supportedProtocols()) { auto const b = new QCheckBox; b->setText(formatLabel(i, Key())); m_buttons[i] = b; layout->addWidget(b); m_buttonGroup->addButton(b); } setExclusive(true); } void SigningProtocolSelectionWidget::setProtocolChecked(Protocol p, bool checked) { Q_ASSERT(p != UnknownProtocol); QCheckBox *const b = button(p); Q_ASSERT(b); b->setChecked(checked); } bool SigningProtocolSelectionWidget::isProtocolChecked(Protocol p) const { Q_ASSERT(p != UnknownProtocol); const QAbstractButton *const b = button(p); Q_ASSERT(b); return b->isChecked(); } std::set SigningProtocolSelectionWidget::checkedProtocols() const { std::set res; for (auto it = m_buttons.begin(), end = m_buttons.end(); it != end; ++it) if (it->second->isChecked()) { res.insert(it->first); } return res; } void SigningProtocolSelectionWidget::setExclusive(bool exclusive) { if (exclusive == isExclusive()) { return; } m_buttonGroup->setExclusive(exclusive); Q_EMIT userSelectionChanged(); } QCheckBox *SigningProtocolSelectionWidget::button(Protocol p) const { const auto it = m_buttons.find(p); return it == m_buttons.end() ? nullptr : it->second; } QLabel *ReadOnlyProtocolSelectionWidget::label(Protocol p) const { const auto it = m_labels.find(p); return it == m_labels.end() ? nullptr : it->second; } bool SigningProtocolSelectionWidget::isExclusive() const { return m_buttonGroup->exclusive(); } void SigningProtocolSelectionWidget::setCertificate(Protocol prot, const Key &key) { QAbstractButton *const b = button(prot); Q_ASSERT(b); b->setText(formatLabel(prot, key)); } void ReadOnlyProtocolSelectionWidget::setCertificate(Protocol prot, const Key &key) { QLabel *const l = label(prot); l->setText(formatLabel(prot, key)); } namespace { class ValidatorImpl : public SignerResolvePage::Validator { public: QString explanation() const override { return QString(); } bool isComplete() const override { return true; } QString customWindowTitle() const override { return QString(); } }; } class SignerResolvePage::Private { friend class ::Kleo::Crypto::Gui::SignerResolvePage; SignerResolvePage *const q; public: explicit Private(SignerResolvePage *qq); ~Private(); void setOperation(Operation operation); void operationButtonClicked(int operation); void selectCertificates(); void setCertificates(const CertificatePair &certs); void updateModeSelectionWidgets(); void updateUi(); bool protocolSelected(Protocol p) const; bool protocolSelectionActuallyUserMutable() const; private: QButtonGroup *signEncryptGroup; QRadioButton *signAndEncryptRB; QRadioButton *encryptOnlyRB; QRadioButton *signOnlyRB; QGroupBox *signingCertificateBox; QLabel *signerLabelLabel; QLabel *signerLabel; QGroupBox *encryptBox; QCheckBox *textArmorCO; QPushButton *selectCertificatesButton; SigningProtocolSelectionWidget *signingProtocolSelectionWidget; ReadOnlyProtocolSelectionWidget *readOnlyProtocolSelectionWidget; std::vector presetProtocols; bool signingMutable; bool encryptionMutable; bool signingSelected; bool encryptionSelected; bool multipleProtocolsAllowed; bool protocolSelectionUserMutable; CertificatePair certificates; std::shared_ptr validator; std::shared_ptr signingPreferences; }; bool SignerResolvePage::Private::protocolSelectionActuallyUserMutable() const { return (q->protocolSelectionUserMutable() || presetProtocols.empty()) && q->operation() == SignOnly; } SignerResolvePage::Private::Private(SignerResolvePage *qq) : q(qq) , presetProtocols() , signingMutable(true) , encryptionMutable(true) , signingSelected(false) , encryptionSelected(false) , multipleProtocolsAllowed(false) , protocolSelectionUserMutable(true) , validator(new ValidatorImpl) { auto layout = new QVBoxLayout(q); signEncryptGroup = new QButtonGroup(q); q->connect(signEncryptGroup, SIGNAL(buttonClicked(int)), q, SLOT(operationButtonClicked(int))); signAndEncryptRB = new QRadioButton; signAndEncryptRB->setText(i18n("Sign and encrypt (OpenPGP only)")); signAndEncryptRB->setChecked(true); signEncryptGroup->addButton(signAndEncryptRB, SignAndEncrypt); layout->addWidget(signAndEncryptRB); encryptOnlyRB = new QRadioButton; encryptOnlyRB->setText(i18n("Encrypt only")); signEncryptGroup->addButton(encryptOnlyRB, EncryptOnly); layout->addWidget(encryptOnlyRB); signOnlyRB = new QRadioButton; signOnlyRB->setText(i18n("Sign only")); signEncryptGroup->addButton(signOnlyRB, SignOnly); layout->addWidget(signOnlyRB); encryptBox = new QGroupBox; encryptBox->setTitle(i18n("Encryption Options")); QBoxLayout *const encryptLayout = new QVBoxLayout(encryptBox); textArmorCO = new QCheckBox; textArmorCO->setText(i18n("Text output (ASCII armor)")); encryptLayout->addWidget(textArmorCO); layout->addWidget(encryptBox); signingCertificateBox = new QGroupBox; signingCertificateBox->setTitle(i18n("Signing Options")); auto signerLayout = new QGridLayout(signingCertificateBox); signerLayout->setColumnStretch(1, 1); signerLabelLabel = new QLabel; signerLabelLabel->setText(i18n("Signer:")); signerLayout->addWidget(signerLabelLabel, 1, 0); signerLabel = new QLabel; signerLayout->addWidget(signerLabel, 1, 1); signerLabelLabel->setVisible(false); signerLabel->setVisible(false); signingProtocolSelectionWidget = new SigningProtocolSelectionWidget; connect(signingProtocolSelectionWidget, SIGNAL(userSelectionChanged()), q, SLOT(updateUi())); signerLayout->addWidget(signingProtocolSelectionWidget, 2, 0, 1, -1); readOnlyProtocolSelectionWidget = new ReadOnlyProtocolSelectionWidget; signerLayout->addWidget(readOnlyProtocolSelectionWidget, 3, 0, 1, -1); selectCertificatesButton = new QPushButton; selectCertificatesButton->setText(i18n("Change Signing Certificates...")); selectCertificatesButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); signerLayout->addWidget(selectCertificatesButton, 4, 0, 1, -1, Qt::AlignLeft); q->connect(selectCertificatesButton, SIGNAL(clicked()), q, SLOT(selectCertificates())); layout->addWidget(signingCertificateBox); layout->addStretch(); } void SignerResolvePage::setValidator(const std::shared_ptr &validator) { Q_ASSERT(validator); d->validator = validator; d->updateUi(); } std::shared_ptr SignerResolvePage::validator() const { return d->validator; } SignerResolvePage::Private::~Private() {} bool SignerResolvePage::Private::protocolSelected(Protocol p) const { Q_ASSERT(p != UnknownProtocol); return signingProtocolSelectionWidget->isProtocolChecked(p); } void SignerResolvePage::Private::setCertificates(const CertificatePair &certs) { certificates = certs; readOnlyProtocolSelectionWidget->setCertificate(GpgME::OpenPGP, certs.openpgp); signingProtocolSelectionWidget->setCertificate(GpgME::OpenPGP, certs.openpgp); readOnlyProtocolSelectionWidget->setCertificate(GpgME::CMS, certs.cms); signingProtocolSelectionWidget->setCertificate(GpgME::CMS, certs.cms); updateUi(); } void SignerResolvePage::Private::updateUi() { const bool ismutable = protocolSelectionActuallyUserMutable(); readOnlyProtocolSelectionWidget->setVisible(!ismutable); signingProtocolSelectionWidget->setVisible(ismutable); q->setExplanation(validator->explanation()); Q_EMIT q->completeChanged(); const QString customTitle = validator->customWindowTitle(); if (!customTitle.isEmpty()) { Q_EMIT q->windowTitleChanged(customTitle); } selectCertificatesButton->setEnabled(signingProtocolSelectionWidget->checkedProtocols().size() > 0); } void SignerResolvePage::setProtocolSelectionUserMutable(bool ismutable) { if (d->protocolSelectionUserMutable == ismutable) { return; } d->protocolSelectionUserMutable = ismutable; d->updateModeSelectionWidgets(); } bool SignerResolvePage::protocolSelectionUserMutable() const { return d->protocolSelectionUserMutable; } void SignerResolvePage::setMultipleProtocolsAllowed(bool allowed) { if (d->multipleProtocolsAllowed == allowed) { return; } d->multipleProtocolsAllowed = allowed; d->updateModeSelectionWidgets(); } bool SignerResolvePage::multipleProtocolsAllowed() const { return d->multipleProtocolsAllowed; } void SignerResolvePage::Private::updateModeSelectionWidgets() { const bool bothMutable = signingMutable && encryptionMutable; const bool noSigningPossible = !signingSelected && !signingMutable; const bool noEncryptionPossible = !encryptionSelected && !encryptionMutable; signAndEncryptRB->setChecked(signingSelected && encryptionSelected); signOnlyRB->setChecked(signingSelected && !encryptionSelected); encryptOnlyRB->setChecked(encryptionSelected && !signingSelected); const bool canSignAndEncrypt = !noSigningPossible && !noEncryptionPossible && bothMutable && presetProtocols != std::vector(1, CMS); const bool canSignOnly = !encryptionSelected || encryptionMutable; const bool canEncryptOnly = !signingSelected || signingMutable; signAndEncryptRB->setEnabled(canSignAndEncrypt); signOnlyRB->setEnabled(canSignOnly); encryptOnlyRB->setEnabled(canEncryptOnly); const bool buttonsVisible = signingMutable || encryptionMutable; signOnlyRB->setVisible(buttonsVisible); encryptOnlyRB->setVisible(buttonsVisible); signAndEncryptRB->setVisible(buttonsVisible); signingProtocolSelectionWidget->setExclusive(!multipleProtocolsAllowed); signingCertificateBox->setVisible(!noSigningPossible); encryptBox->setVisible(!noEncryptionPossible); updateUi(); } void SignerResolvePage::Private::selectCertificates() { QPointer dlg = new SigningCertificateSelectionDialog(q); dlg->setAllowedProtocols(signingProtocolSelectionWidget->checkedProtocols()); if (dlg->exec() == QDialog::Accepted && dlg) { const auto certs = dlg->selectedCertificates(); setCertificates(certs); if (signingPreferences && dlg->rememberAsDefault()) { signingPreferences->setPreferredCertificate(OpenPGP, certs.openpgp); signingPreferences->setPreferredCertificate(CMS, certs.cms); } } delete dlg; updateUi(); } void SignerResolvePage::Private::operationButtonClicked(int mode_) { const auto op = static_cast(mode_); signingCertificateBox->setEnabled(op != EncryptOnly); encryptBox->setEnabled(op != SignOnly); if (op == SignAndEncrypt) { signingProtocolSelectionWidget->setProtocolChecked(CMS, false); readOnlyProtocolSelectionWidget->setProtocolChecked(CMS, false); signingProtocolSelectionWidget->setProtocolChecked(OpenPGP, true); readOnlyProtocolSelectionWidget->setProtocolChecked(OpenPGP, true); } updateUi(); } void SignerResolvePage::Private::setOperation(Operation op) { switch (op) { case SignOnly: signOnlyRB->click(); break; case EncryptOnly: encryptOnlyRB->click(); break; case SignAndEncrypt: signAndEncryptRB->click(); break; } } SignerResolvePage::Operation SignerResolvePage::operation() const { return operationFromFlags(signingSelected(), encryptionSelected()); } SignerResolvePage::SignerResolvePage(QWidget *parent, Qt::WindowFlags f) : WizardPage(parent, f), d(new Private(this)) { setTitle(i18n("Choose Operation to be Performed")); // setSubTitle( i18n( "TODO" ) ); setPresetProtocol(UnknownProtocol); d->setCertificates({}); d->updateModeSelectionWidgets(); d->operationButtonClicked(EncryptOnly); } SignerResolvePage::~SignerResolvePage() {} void SignerResolvePage::setSignersAndCandidates(const std::vector &signers, const std::vector< std::vector > &keys) { kleo_assert(signers.empty() || signers.size() == keys.size()); switch (signers.size()) { case 0: d->signerLabelLabel->setVisible(false); d->signerLabel->setVisible(false); // TODO: use default identity? break; case 1: d->signerLabelLabel->setVisible(true); d->signerLabel->setVisible(true); // TODO: use default identity? d->signerLabel->setText(signers.front().prettyAddress()); break; default: // > 1 kleo_assert(!"Resolving multiple signers not implemented"); } d->updateUi(); } void SignerResolvePage::setPresetProtocol(Protocol protocol) { std::vector protocols; if (protocol != CMS) { protocols.push_back(OpenPGP); } if (protocol != OpenPGP) { protocols.push_back(CMS); } setPresetProtocols(protocols); d->updateUi(); } void SignerResolvePage::setPresetProtocols(const std::vector &protocols) { d->presetProtocols = protocols; for (const Protocol i : supportedProtocols()) { const bool checked = std::find(protocols.begin(), protocols.end(), i) != protocols.end(); d->signingProtocolSelectionWidget->setProtocolChecked(i, checked); d->readOnlyProtocolSelectionWidget->setProtocolChecked(i, checked); } d->updateModeSelectionWidgets(); } std::set SignerResolvePage::selectedProtocols() const { return d->signingProtocolSelectionWidget->checkedProtocols(); } std::vector SignerResolvePage::signingCertificates(Protocol protocol) const { std::vector result; if (protocol != CMS && d->signingProtocolSelectionWidget->isProtocolChecked(OpenPGP) && !d->certificates.openpgp.isNull()) { result.push_back(d->certificates.openpgp); } if (protocol != OpenPGP && d->signingProtocolSelectionWidget->isProtocolChecked(CMS) && !d->certificates.cms.isNull()) { result.push_back(d->certificates.cms); } return result; } std::vector SignerResolvePage::resolvedSigners() const { std::vector result = signingCertificates(CMS); const std::vector pgp = signingCertificates(OpenPGP); result.insert(result.end(), pgp.begin(), pgp.end()); return result; } bool SignerResolvePage::isComplete() const { Q_ASSERT(d->validator); return d->validator->isComplete(); } bool SignerResolvePage::encryptionSelected() const { return !d->signOnlyRB->isChecked(); } void SignerResolvePage::setEncryptionSelected(bool selected) { d->encryptionSelected = selected; d->updateModeSelectionWidgets(); d->setOperation(operationFromFlags(d->signingSelected, d->encryptionSelected)); } bool SignerResolvePage::signingSelected() const { return !d->encryptOnlyRB->isChecked(); } void SignerResolvePage::setSigningSelected(bool selected) { d->signingSelected = selected; d->updateModeSelectionWidgets(); d->setOperation(operationFromFlags(d->signingSelected, d->encryptionSelected)); } bool SignerResolvePage::isEncryptionUserMutable() const { return d->encryptionMutable; } bool SignerResolvePage::isSigningUserMutable() const { return d->signingMutable; } void SignerResolvePage::setEncryptionUserMutable(bool ismutable) { d->encryptionMutable = ismutable; d->updateModeSelectionWidgets(); } void SignerResolvePage::setSigningUserMutable(bool ismutable) { d->signingMutable = ismutable; d->updateModeSelectionWidgets(); } std::set SignerResolvePage::selectedProtocolsWithoutSigningCertificate() const { std::set res; for (const Protocol i : selectedProtocols()) { if (signingCertificates(i).empty()) { res.insert(i); } } return res; } bool SignerResolvePage::isAsciiArmorEnabled() const { return d->textArmorCO->isChecked(); } void SignerResolvePage::setAsciiArmorEnabled(bool enabled) { d->textArmorCO->setChecked(enabled); } void SignerResolvePage::setSigningPreferences(const std::shared_ptr &prefs) { d->signingPreferences = prefs; const CertificatePair certs = { prefs ? prefs->preferredCertificate(OpenPGP) : Key(), prefs ? prefs->preferredCertificate(CMS) : Key() }; d->setCertificates(certs); } std::shared_ptr SignerResolvePage::signingPreferences() const { return d->signingPreferences; } void SignerResolvePage::onNext() { } #include "moc_signerresolvepage.cpp" #include "moc_signerresolvepage_p.cpp" diff --git a/src/crypto/gui/signingcertificateselectiondialog.cpp b/src/crypto/gui/signingcertificateselectiondialog.cpp index 65f6471e3..137704f7e 100644 --- a/src/crypto/gui/signingcertificateselectiondialog.cpp +++ b/src/crypto/gui/signingcertificateselectiondialog.cpp @@ -1,66 +1,65 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signingcertificateselectiondialog.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 "signingcertificateselectiondialog.h" #include "signingcertificateselectionwidget.h" #include "utils/keys.h" #include "utils/qt-cxx20-compat.h" #include -#include #include #include #include using namespace Kleo; using namespace Kleo::Crypto::Gui; SigningCertificateSelectionDialog::SigningCertificateSelectionDialog(QWidget *parent) : QDialog(parent), widget(new SigningCertificateSelectionWidget(this)) { setWindowTitle(i18nc("@title:window", "Select Signing Certificates")); auto mainLayout = new QVBoxLayout(this); mainLayout->addWidget(widget); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &SigningCertificateSelectionDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &SigningCertificateSelectionDialog::reject); mainLayout->addWidget(buttonBox); } SigningCertificateSelectionDialog::~SigningCertificateSelectionDialog() {} void SigningCertificateSelectionDialog::setSelectedCertificates(const CertificatePair &certificates) { widget->setSelectedCertificates(certificates); } CertificatePair SigningCertificateSelectionDialog::selectedCertificates() const { return widget->selectedCertificates(); } bool SigningCertificateSelectionDialog::rememberAsDefault() const { return widget->rememberAsDefault(); } void SigningCertificateSelectionDialog::setAllowedProtocols(const std::set &allowedProtocols) { widget->setAllowedProtocols(allowedProtocols); } diff --git a/src/crypto/gui/signingcertificateselectionwidget.cpp b/src/crypto/gui/signingcertificateselectionwidget.cpp index 9e3b442cd..130004ec1 100644 --- a/src/crypto/gui/signingcertificateselectionwidget.cpp +++ b/src/crypto/gui/signingcertificateselectionwidget.cpp @@ -1,145 +1,144 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signingcertificateselectionwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007, 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signingcertificateselectionwidget.h" #include "ui_signingcertificateselectionwidget.h" #include "utils/keys.h" #include #include #include #include -#include using namespace Kleo; using namespace Kleo::Crypto::Gui; class SigningCertificateSelectionWidget::Private { friend class ::SigningCertificateSelectionWidget; SigningCertificateSelectionWidget *const q; public: explicit Private(SigningCertificateSelectionWidget *qq); ~Private(); static std::vector candidates(GpgME::Protocol prot); static void addCandidates(GpgME::Protocol prot, QComboBox *combo); private: Ui::SigningCertificateSelectionWidget ui; }; static GpgME::Key current_cert(const QComboBox &cb) { const QByteArray fpr = cb.itemData(cb.currentIndex()).toByteArray(); return KeyCache::instance()->findByFingerprint(fpr.constData()); } static void select_cert(QComboBox &cb, const GpgME::Key &key) { const QByteArray fpr = key.primaryFingerprint(); if (!fpr.isEmpty()) { cb.setCurrentIndex(cb.findData(fpr)); } } static void add_cert(QComboBox &cb, const GpgME::Key &key) { cb.addItem(Formatting::formatForComboBox(key), QVariant(QByteArray(key.primaryFingerprint()))); } SigningCertificateSelectionWidget::Private::Private(SigningCertificateSelectionWidget *qq) : q(qq), ui() { ui.setupUi(q); addCandidates(GpgME::CMS, ui.cmsCombo); addCandidates(GpgME::OpenPGP, ui.pgpCombo); ui.rememberCO->setChecked(true); } SigningCertificateSelectionWidget::Private::~Private() {} SigningCertificateSelectionWidget::SigningCertificateSelectionWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new Private(this)) { } SigningCertificateSelectionWidget::~SigningCertificateSelectionWidget() {} void SigningCertificateSelectionWidget::setSelectedCertificates(const CertificatePair &certificates) { setSelectedCertificates(certificates.openpgp, certificates.cms); } void SigningCertificateSelectionWidget::setSelectedCertificates(const GpgME::Key &pgp, const GpgME::Key &cms) { select_cert(*d->ui.pgpCombo, pgp); select_cert(*d->ui.cmsCombo, cms); } std::vector SigningCertificateSelectionWidget::Private::candidates(GpgME::Protocol prot) { Q_ASSERT(prot != GpgME::UnknownProtocol); std::vector keys = KeyCache::instance()->keys(); auto end = keys.end(); end = std::remove_if(keys.begin(), end, [prot](const GpgME::Key &key) { return key.protocol() != prot; }); end = std::remove_if(keys.begin(), end, [](const GpgME::Key &key) { return !key.hasSecret(); }); Q_ASSERT(std::all_of(keys.begin(), end, [](const GpgME::Key &key) { return key.hasSecret(); })); end = std::remove_if(keys.begin(), end, [](const GpgME::Key &key) { return !key.canReallySign(); }); end = std::remove_if(keys.begin(), end, [](const GpgME::Key &key) { return key.isExpired(); }); end = std::remove_if(keys.begin(), end, [](const GpgME::Key &key) { return key.isRevoked(); }); keys.erase(end, keys.end()); return keys; } void SigningCertificateSelectionWidget::Private::addCandidates(GpgME::Protocol prot, QComboBox *combo) { const std::vector keys = candidates(prot); for (const GpgME::Key &i : keys) { add_cert(*combo, i); } } CertificatePair SigningCertificateSelectionWidget::selectedCertificates() const { return { current_cert(*d->ui.pgpCombo), current_cert(*d->ui.cmsCombo), }; } bool SigningCertificateSelectionWidget::rememberAsDefault() const { return d->ui.rememberCO->isChecked(); } void SigningCertificateSelectionWidget::setAllowedProtocols(const std::set &allowedProtocols) { setAllowedProtocols(allowedProtocols.find(GpgME::OpenPGP) != allowedProtocols.end(), allowedProtocols.find(GpgME::CMS) != allowedProtocols.end()); } void SigningCertificateSelectionWidget::setAllowedProtocols(bool pgp, bool cms) { d->ui.pgpLabel->setVisible(pgp); d->ui.pgpCombo->setVisible(pgp); d->ui.cmsLabel->setVisible(cms); d->ui.cmsCombo->setVisible(cms); } diff --git a/src/crypto/signemailcontroller.cpp b/src/crypto/signemailcontroller.cpp index 89f1dcbef..f39976d56 100644 --- a/src/crypto/signemailcontroller.cpp +++ b/src/crypto/signemailcontroller.cpp @@ -1,343 +1,342 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signemailcontroller.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 "signemailcontroller.h" #include "kleopatra_debug.h" #include "signemailtask.h" #include "certificateresolver.h" #include "taskcollection.h" #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 SignEMailController::Private { friend class ::Kleo::Crypto::SignEMailController; SignEMailController *const q; public: explicit Private(Mode m, SignEMailController *qq); ~Private(); private: void slotWizardSignersResolved(); void slotWizardCanceled(); // ### extract to base private: void ensureWizardCreated(); // ### extract to base void ensureWizardVisible(); // ### extract to base void cancelAllJobs(); // ### extract to base void schedule(); // ### extract to base std::shared_ptr takeRunnable(GpgME::Protocol proto); // ### extract to base private: const Mode mode; std::vector< std::shared_ptr > runnable, completed; // ### extract to base std::shared_ptr cms, openpgp; // ### extract to base QPointer wizard; // ### extract to base Protocol protocol; // ### extract to base bool detached : 1; }; SignEMailController::Private::Private(Mode m, SignEMailController *qq) : q(qq), mode(m), runnable(), cms(), openpgp(), wizard(), protocol(UnknownProtocol), detached(false) { } SignEMailController::Private::~Private() {} SignEMailController::SignEMailController(Mode mode, QObject *p) : Controller(p), d(new Private(mode, this)) { } SignEMailController::SignEMailController(const std::shared_ptr &xc, Mode mode, QObject *p) : Controller(xc, p), d(new Private(mode, this)) { } SignEMailController::~SignEMailController() { /// ### extract to base if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } SignEMailController::Mode SignEMailController::mode() const { return d->mode; } // ### extract to base void SignEMailController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); d->wizard->setPresetProtocol(proto); } Protocol SignEMailController::protocol() const { return d->protocol; } void SignEMailController::startResolveSigners() { startResolveSigners(std::vector()); } void SignEMailController::startResolveSigners(const std::vector &signers) { const std::vector< std::vector > keys = CertificateResolver::resolveSigners(signers, d->protocol); if (!signers.empty()) { kleo_assert(keys.size() == static_cast(signers.size())); } d->ensureWizardCreated(); d->wizard->setSignersAndCandidates(signers, keys); d->ensureWizardVisible(); } void SignEMailController::setDetachedSignature(bool detached) { kleo_assert(!d->openpgp); kleo_assert(!d->cms); kleo_assert(d->completed.empty()); kleo_assert(d->runnable.empty()); d->detached = detached; } void SignEMailController::Private::slotWizardSignersResolved() { Q_EMIT q->signersResolved(); } // ### extract to base void SignEMailController::Private::slotWizardCanceled() { q->setLastError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); q->emitDoneOrError(); } void SignEMailController::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)); } // ### extract to base void SignEMailController::setInputsAndOutputs(const std::vector< std::shared_ptr > &inputs, const std::vector< std::shared_ptr > &outputs) { kleo_assert(!inputs.empty()); kleo_assert(!outputs.empty()); std::vector< std::shared_ptr > tasks; tasks.reserve(inputs.size()); d->ensureWizardCreated(); const std::vector keys = d->wizard->resolvedSigners(); kleo_assert(!keys.empty()); for (unsigned int i = 0, end = inputs.size(); i < end; ++i) { const std::shared_ptr task(new SignEMailTask); task->setInput(inputs[i]); task->setOutput(outputs[i]); task->setSigners(keys); task->setDetachedSignature(d->detached); if (d->mode == ClipboardMode) { if (d->protocol == OpenPGP) { task->setClearsign(true); } else { task->setAsciiArmor(true); } } tasks.push_back(task); } d->runnable.swap(tasks); } // ### extract to base void SignEMailController::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(); } // ### extract to base void SignEMailController::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) { kleo_assert(runnable.empty()); QPointer Q = q; for (const std::shared_ptr &t : completed) { Q_EMIT q->reportMicAlg(t->micAlg()); if (!Q) { return; } } q->emitDoneOrError(); } } // ### extract to base std::shared_ptr SignEMailController::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; } // ### extract to base void SignEMailController::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(); } QTimer::singleShot(0, this, SLOT(schedule())); } // ### extract to base void SignEMailController::cancel() { try { if (d->wizard) { d->wizard->close(); } d->cancelAllJobs(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } // ### extract to base void SignEMailController::Private::cancelAllJobs() { // 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(); } } // ### extract to base void SignEMailController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr w(new SignEMailWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(signersResolved()), q, SLOT(slotWizardSignersResolved()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); w->setPresetProtocol(protocol); EMailOperationsPreferences prefs; w->setQuickMode(prefs.quickSignEMail()); wizard = w.release(); } // ### extract to base void SignEMailController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signemailcontroller.cpp" diff --git a/src/crypto/signencryptfilescontroller.h b/src/crypto/signencryptfilescontroller.h index 3675f2b89..3ca3b5866 100644 --- a/src/crypto/signencryptfilescontroller.h +++ b/src/crypto/signencryptfilescontroller.h @@ -1,81 +1,80 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencryptfilescontroller.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 #include -#include #include #include #include namespace Kleo { namespace Crypto { class SignEncryptFilesController : public Controller { Q_OBJECT public: explicit SignEncryptFilesController(QObject *parent = nullptr); explicit SignEncryptFilesController(const std::shared_ptr &ctx, QObject *parent = nullptr); ~SignEncryptFilesController() override; void setProtocol(GpgME::Protocol proto); GpgME::Protocol protocol() const; //const char * protocolAsString() const; enum Operation { SignDisallowed = 0, SignAllowed = 1, SignSelected = 2, SignMask = SignAllowed | SignSelected, EncryptDisallowed = 0, EncryptAllowed = 4, EncryptSelected = 8, EncryptMask = EncryptAllowed | EncryptSelected, ArchiveDisallowed = 0, ArchiveAllowed = 16, ArchiveForced = 32, ArchiveMask = ArchiveAllowed | ArchiveForced }; void setOperationMode(unsigned int mode); unsigned int operationMode() const; void setFiles(const QStringList &files); void start(); public Q_SLOTS: void cancel(); private: void doTaskDone(const Task *task, const std::shared_ptr &) override; class Private; kdtools::pimpl_ptr d; Q_PRIVATE_SLOT(d, void slotWizardOperationPrepared()) Q_PRIVATE_SLOT(d, void slotWizardCanceled()) Q_PRIVATE_SLOT(d, void schedule()) }; } } diff --git a/src/crypto/verifychecksumscontroller.h b/src/crypto/verifychecksumscontroller.h index 1b1480b9a..d343932de 100644 --- a/src/crypto/verifychecksumscontroller.h +++ b/src/crypto/verifychecksumscontroller.h @@ -1,55 +1,54 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/verifychecksumscontroller.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 #ifndef QT_NO_DIRMODEL #include -#include #include #include #include namespace Kleo { namespace Crypto { class VerifyChecksumsController : public Controller { Q_OBJECT public: explicit VerifyChecksumsController(QObject *parent = nullptr); explicit VerifyChecksumsController(const std::shared_ptr &ctx, QObject *parent = nullptr); ~VerifyChecksumsController() override; 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()) }; } } #endif // QT_NO_DIRMODEL diff --git a/src/dialogs/certificateselectiondialog.cpp b/src/dialogs/certificateselectiondialog.cpp index ca35713b4..74017b9f8 100644 --- a/src/dialogs/certificateselectiondialog.cpp +++ b/src/dialogs/certificateselectiondialog.cpp @@ -1,486 +1,485 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/certificateselectiondialog.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 "certificateselectiondialog.h" #include "settings.h" #include "conf/groupsconfigdialog.h" #include #include #include #include #include "utils/tags.h" #include #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::Dialogs; using namespace Kleo::Commands; using namespace GpgME; CertificateSelectionDialog::Option CertificateSelectionDialog::optionsFromProtocol(Protocol proto) { switch (proto) { case OpenPGP: return CertificateSelectionDialog::OpenPGPFormat; case CMS: return CertificateSelectionDialog::CMSFormat; default: return CertificateSelectionDialog::AnyFormat; } } namespace { auto protocolFromOptions(CertificateSelectionDialog::Options options) { switch (options & CertificateSelectionDialog::AnyFormat) { case CertificateSelectionDialog::OpenPGPFormat: return GpgME::OpenPGP; case CertificateSelectionDialog::CMSFormat: return GpgME::CMS; default: return GpgME::UnknownProtocol; } } } class CertificateSelectionDialog::Private { friend class ::Kleo::Dialogs::CertificateSelectionDialog; CertificateSelectionDialog *const q; public: explicit Private(CertificateSelectionDialog *qq); private: void reload() { Command *const cmd = new ReloadKeysCommand(nullptr); cmd->setParentWidget(q); cmd->start(); } void create() { auto cmd = new NewOpenPGPCertificateCommand; cmd->setParentWidget(q); cmd->start(); } void lookup() { const auto cmd = new LookupCertificatesCommand(nullptr); cmd->setParentWidget(q); cmd->setProtocol(protocolFromOptions(options)); cmd->start(); } void manageGroups() { KConfigDialog *dialog = KConfigDialog::exists(GroupsConfigDialog::dialogName()); if (dialog) { // reparent the dialog to ensure it's shown on top of the modal CertificateSelectionDialog dialog->setParent(q, Qt::Dialog); } else { dialog = new GroupsConfigDialog(q); } dialog->show(); } void slotKeysMayHaveChanged(); void slotCurrentViewChanged(QAbstractItemView *newView); void slotSelectionChanged(); void slotDoubleClicked(const QModelIndex &idx); private: bool acceptable(const std::vector &keys, const std::vector &groups) { return !keys.empty() || !groups.empty(); } void updateLabelText() { ui.label.setText(!customLabelText.isEmpty() ? customLabelText : (options & MultiSelection) ? i18n("Please select one or more of the following certificates:") : i18n("Please select one of the following certificates:")); } private: std::vector connectedViews; QString customLabelText; Options options = AnyCertificate | AnyFormat; struct UI { QLabel label; SearchBar searchBar; TabWidget tabWidget; QDialogButtonBox buttonBox; QPushButton *createButton = nullptr; } ui; void setUpUI(CertificateSelectionDialog *q) { KDAB_SET_OBJECT_NAME(ui.label); KDAB_SET_OBJECT_NAME(ui.searchBar); KDAB_SET_OBJECT_NAME(ui.tabWidget); KDAB_SET_OBJECT_NAME(ui.buttonBox); auto vlay = new QVBoxLayout(q); vlay->addWidget(&ui.label); vlay->addWidget(&ui.searchBar); vlay->addWidget(&ui.tabWidget, 1); vlay->addWidget(&ui.buttonBox); QPushButton *const okButton = ui.buttonBox.addButton(QDialogButtonBox::Ok); okButton->setEnabled(false); ui.buttonBox.addButton(QDialogButtonBox::Close); QPushButton *const reloadButton = ui.buttonBox.addButton(i18n("Reload"), QDialogButtonBox::ActionRole); reloadButton->setToolTip(i18nc("@info:tooltip", "Refresh certificate list")); QPushButton *const importButton = ui.buttonBox.addButton(i18n("Import..."), QDialogButtonBox::ActionRole); importButton->setToolTip(i18nc("@info:tooltip", "Import certificate from file")); importButton->setAccessibleName(i18n("Import certificate")); QPushButton *const lookupButton = ui.buttonBox.addButton(i18n("Lookup..."), QDialogButtonBox::ActionRole); lookupButton->setToolTip(i18nc("@info:tooltip", "Look up certificate on server")); lookupButton->setAccessibleName(i18n("Look up certificate")); ui.createButton = ui.buttonBox.addButton(i18n("New..."), QDialogButtonBox::ActionRole); ui.createButton->setToolTip(i18nc("@info:tooltip", "Create a new OpenPGP certificate")); ui.createButton->setAccessibleName(i18n("Create certificate")); QPushButton *const groupsButton = ui.buttonBox.addButton(i18n("Groups..."), QDialogButtonBox::ActionRole); groupsButton->setToolTip(i18nc("@info:tooltip", "Manage certificate groups")); groupsButton->setAccessibleName(i18n("Manage groups")); groupsButton->setVisible(Settings().groupsEnabled()); connect(&ui.buttonBox, &QDialogButtonBox::accepted, q, &CertificateSelectionDialog::accept); connect(&ui.buttonBox, &QDialogButtonBox::rejected, q, &CertificateSelectionDialog::reject); connect(reloadButton, &QPushButton::clicked, q, [this] () { reload(); }); connect(lookupButton, &QPushButton::clicked, q, [this] () { lookup(); }); connect(ui.createButton, &QPushButton::clicked, q, [this] () { create(); }); connect(groupsButton, &QPushButton::clicked, q, [this] () { manageGroups(); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, q, [this] () { slotKeysMayHaveChanged(); }); connect(importButton, &QPushButton::clicked, q, [importButton, q] () { importButton->setEnabled(false); auto cmd = new Kleo::ImportCertificateFromFileCommand(); connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [importButton]() { importButton->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } }; CertificateSelectionDialog::Private::Private(CertificateSelectionDialog *qq) : q(qq) { setUpUI(q); ui.tabWidget.setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q)); ui.tabWidget.setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(q)); const auto tagKeys = Tags::tagKeys(); ui.tabWidget.flatModel()->setRemarkKeys(tagKeys); ui.tabWidget.hierarchicalModel()->setRemarkKeys(tagKeys); ui.tabWidget.connectSearchBar(&ui.searchBar); connect(&ui.tabWidget, &TabWidget::currentViewChanged, q, [this] (QAbstractItemView *view) { slotCurrentViewChanged(view); }); updateLabelText(); q->setWindowTitle(i18nc("@title:window", "Certificate Selection")); } CertificateSelectionDialog::CertificateSelectionDialog(QWidget *parent) : QDialog(parent), d(new Private(this)) { const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc")); d->ui.tabWidget.loadViews(config.data()); const KConfigGroup geometry(config, "Geometry"); resize(geometry.readEntry("size", size())); d->slotKeysMayHaveChanged(); } CertificateSelectionDialog::~CertificateSelectionDialog() {} void CertificateSelectionDialog::setCustomLabelText(const QString &txt) { if (txt == d->customLabelText) { return; } d->customLabelText = txt; d->updateLabelText(); } QString CertificateSelectionDialog::customLabelText() const { return d->customLabelText; } void CertificateSelectionDialog::setOptions(Options options) { Q_ASSERT((options & CertificateSelectionDialog::AnyCertificate) != 0); Q_ASSERT((options & CertificateSelectionDialog::AnyFormat) != 0); if (d->options == options) { return; } d->options = options; d->ui.tabWidget.setMultiSelection(options & MultiSelection); d->slotKeysMayHaveChanged(); d->updateLabelText(); d->ui.createButton->setVisible(options & OpenPGPFormat); } CertificateSelectionDialog::Options CertificateSelectionDialog::options() const { return d->options; } void CertificateSelectionDialog::setStringFilter(const QString &filter) { d->ui.tabWidget.setStringFilter(filter); } void CertificateSelectionDialog::setKeyFilter(const std::shared_ptr &filter) { d->ui.tabWidget.setKeyFilter(filter); } namespace { void selectRows(const QAbstractItemView *view, const QModelIndexList &indexes) { if (!view) { return; } QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); for (const QModelIndex &idx : std::as_const(indexes)) { if (idx.isValid()) { sm->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } QModelIndexList getGroupIndexes(const KeyListModelInterface *model, const std::vector &groups) { QModelIndexList indexes; indexes.reserve(groups.size()); std::transform(groups.begin(), groups.end(), std::back_inserter(indexes), [model] (const KeyGroup &group) { return model->index(group); }); indexes.erase(std::remove_if(indexes.begin(), indexes.end(), [] (const QModelIndex &index) { return !index.isValid(); }), indexes.end()); return indexes; } } void CertificateSelectionDialog::selectCertificates(const std::vector &keys) { const auto *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); selectRows(d->ui.tabWidget.currentView(), model->indexes(keys)); } void CertificateSelectionDialog::selectCertificate(const Key &key) { selectCertificates(std::vector(1, key)); } void CertificateSelectionDialog::selectGroups(const std::vector &groups) { const auto *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); selectRows(d->ui.tabWidget.currentView(), getGroupIndexes(model, groups)); } namespace { QModelIndexList getSelectedRows(const QAbstractItemView *view) { if (!view) { return {}; } const QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); return sm->selectedRows(); } std::vector getGroups(const KeyListModelInterface *model, const QModelIndexList &indexes) { std::vector groups; groups.reserve(indexes.size()); std::transform(indexes.begin(), indexes.end(), std::back_inserter(groups), [model](const QModelIndex &idx) { return model->group(idx); }); groups.erase(std::remove_if(groups.begin(), groups.end(), std::mem_fn(&Kleo::KeyGroup::isNull)), groups.end()); return groups; } } std::vector CertificateSelectionDialog::selectedCertificates() const { const KeyListModelInterface *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); return model->keys(getSelectedRows(d->ui.tabWidget.currentView())); } Key CertificateSelectionDialog::selectedCertificate() const { const std::vector keys = selectedCertificates(); return keys.empty() ? Key() : keys.front(); } std::vector CertificateSelectionDialog::selectedGroups() const { const KeyListModelInterface *const model = d->ui.tabWidget.currentModel(); Q_ASSERT(model); return getGroups(model, getSelectedRows(d->ui.tabWidget.currentView())); } void CertificateSelectionDialog::hideEvent(QHideEvent *e) { KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kleopatracertificateselectiondialogrc")); d->ui.tabWidget.saveViews(config.data()); KConfigGroup geometry(config, "Geometry"); geometry.writeEntry("size", size()); QDialog::hideEvent(e); } void CertificateSelectionDialog::Private::slotKeysMayHaveChanged() { q->setEnabled(true); std::vector keys = (options & SecretKeys) ? KeyCache::instance()->secretKeys() : KeyCache::instance()->keys(); q->filterAllowedKeys(keys, options); const std::vector groups = (options & IncludeGroups) ? KeyCache::instance()->groups() : std::vector(); const std::vector selectedKeys = q->selectedCertificates(); const std::vector selectedGroups = q->selectedGroups(); if (AbstractKeyListModel *const model = ui.tabWidget.flatModel()) { model->setKeys(keys); model->setGroups(groups); } if (AbstractKeyListModel *const model = ui.tabWidget.hierarchicalModel()) { model->setKeys(keys); model->setGroups(groups); } q->selectCertificates(selectedKeys); q->selectGroups(selectedGroups); } void CertificateSelectionDialog::filterAllowedKeys(std::vector &keys, int options) { auto end = keys.end(); switch (options & AnyFormat) { case OpenPGPFormat: end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != OpenPGP; }); break; case CMSFormat: end = std::remove_if(keys.begin(), end, [](const Key &key) { return key.protocol() != CMS; }); break; default: case AnyFormat: ; } switch (options & AnyCertificate) { case SignOnly: end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canReallySign(); }); break; case EncryptOnly: end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.canEncrypt(); }); break; default: case AnyCertificate: ; } if (options & SecretKeys) { end = std::remove_if(keys.begin(), end, [](const Key &key) { return !key.hasSecret(); }); } keys.erase(end, keys.end()); } void CertificateSelectionDialog::Private::slotCurrentViewChanged(QAbstractItemView *newView) { if (!Kleo::contains(connectedViews, newView)) { connectedViews.push_back(newView); connect(newView, &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &index) { slotDoubleClicked(index); }); Q_ASSERT(newView->selectionModel()); connect(newView->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this] (const QItemSelection &, const QItemSelection &) { slotSelectionChanged(); }); } slotSelectionChanged(); } void CertificateSelectionDialog::Private::slotSelectionChanged() { if (QPushButton *const pb = ui.buttonBox.button(QDialogButtonBox::Ok)) { pb->setEnabled(acceptable(q->selectedCertificates(), q->selectedGroups())); } } void CertificateSelectionDialog::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = ui.tabWidget.currentView(); Q_ASSERT(view); const auto *const model = ui.tabWidget.currentModel(); Q_ASSERT(model); Q_UNUSED(model) QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); sm->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); QMetaObject::invokeMethod(q, [this]() {q->accept();}, Qt::QueuedConnection); } void CertificateSelectionDialog::accept() { if (d->acceptable(selectedCertificates(), selectedGroups())) { QDialog::accept(); } } #include "moc_certificateselectiondialog.cpp" diff --git a/src/dialogs/lookupcertificatesdialog.cpp b/src/dialogs/lookupcertificatesdialog.cpp index 93c9ba48e..932605589 100644 --- a/src/dialogs/lookupcertificatesdialog.cpp +++ b/src/dialogs/lookupcertificatesdialog.cpp @@ -1,350 +1,348 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/lookupcertificatesdialog.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 "lookupcertificatesdialog.h" #include "view/keytreeview.h" #include #include #include #include #include #include #include -#include #include #include #include #include -#include #include #include #include using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; static const int minimalSearchTextLength = 1; class LookupCertificatesDialog::Private { friend class ::Kleo::Dialogs::LookupCertificatesDialog; LookupCertificatesDialog *const q; public: explicit Private(LookupCertificatesDialog *qq); ~Private(); private: void slotSelectionChanged() { enableDisableWidgets(); } void slotSearchTextChanged() { enableDisableWidgets(); } void slotSearchClicked() { Q_EMIT q->searchTextChanged(ui.findED->text()); } void slotDetailsClicked() { Q_ASSERT(q->selectedCertificates().size() == 1); Q_EMIT q->detailsRequested(q->selectedCertificates().front()); } void slotSaveAsClicked() { Q_EMIT q->saveAsRequested(q->selectedCertificates()); } void readConfig(); void writeConfig(); void enableDisableWidgets(); QString searchText() const { return ui.findED->text().trimmed(); } std::vector selectedCertificates() const { const QAbstractItemView *const view = ui.resultTV->view(); if (!view) { return std::vector(); } const auto *const model = dynamic_cast(view->model()); Q_ASSERT(model); const QItemSelectionModel *const sm = view->selectionModel(); Q_ASSERT(sm); return model->keys(sm->selectedRows()); } int numSelectedCertificates() const { return ui.resultTV->selectedKeys().size(); } private: bool passive; struct Ui { QLabel *findLB; QLineEdit *findED; QPushButton *findPB; Kleo::KeyTreeView *resultTV; QPushButton *selectAllPB; QPushButton *deselectAllPB; QPushButton *detailsPB; QPushButton *saveAsPB; KMessageWidget *messageWidget; QDialogButtonBox *buttonBox; void setupUi(QDialog *dialog) { auto verticalLayout = new QVBoxLayout{dialog}; auto gridLayout = new QGridLayout{}; int row = 0; findLB = new QLabel{i18n("Find:"), dialog}; gridLayout->addWidget(findLB, row, 0, 1, 1); findED = new QLineEdit{dialog}; findLB->setBuddy(findED); gridLayout->addWidget(findED, row, 1, 1, 1); findPB = new QPushButton{i18n("Search"), dialog}; findPB->setAutoDefault(false); gridLayout->addWidget(findPB, row, 2, 1, 1); row++; gridLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}, row, 0, 1, 3); row++; resultTV = new Kleo::KeyTreeView(dialog); resultTV->setEnabled(true); resultTV->setMinimumSize(QSize(400, 0)); gridLayout->addWidget(resultTV, row, 0, 1, 2); auto buttonLayout = new QVBoxLayout{}; selectAllPB = new QPushButton{i18n("Select All"), dialog}; selectAllPB->setEnabled(false); selectAllPB->setAutoDefault(false); buttonLayout->addWidget(selectAllPB); deselectAllPB = new QPushButton{i18n("Deselect All"), dialog}; deselectAllPB->setEnabled(false); deselectAllPB->setAutoDefault(false); buttonLayout->addWidget(deselectAllPB); buttonLayout->addStretch(); detailsPB = new QPushButton{i18n("Details..."), dialog}; detailsPB->setEnabled(false); detailsPB->setAutoDefault(false); buttonLayout->addWidget(detailsPB); saveAsPB = new QPushButton{i18n("Save As..."), dialog}; saveAsPB->setEnabled(false); saveAsPB->setAutoDefault(false); buttonLayout->addWidget(saveAsPB); gridLayout->addLayout(buttonLayout, row, 2, 1, 1); row++; messageWidget = new KMessageWidget{dialog}; messageWidget->setMessageType(KMessageWidget::Information); messageWidget->setVisible(false); gridLayout->addWidget(messageWidget, row, 0, 1, 3); verticalLayout->addLayout(gridLayout); buttonBox = new QDialogButtonBox{dialog}; buttonBox->setStandardButtons(QDialogButtonBox::Close|QDialogButtonBox::Save); verticalLayout->addWidget(buttonBox); QObject::connect(findED, SIGNAL(returnPressed()), findPB, SLOT(animateClick())); QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); QObject::connect(findPB, SIGNAL(clicked()), dialog, SLOT(slotSearchClicked())); QObject::connect(detailsPB, SIGNAL(clicked()), dialog, SLOT(slotDetailsClicked())); QObject::connect(saveAsPB, SIGNAL(clicked()), dialog, SLOT(slotSaveAsClicked())); QObject::connect(findED, SIGNAL(textChanged(QString)), dialog, SLOT(slotSearchTextChanged())); QMetaObject::connectSlotsByName(dialog); } explicit Ui(LookupCertificatesDialog *q) { q->setWindowTitle(i18n("Lookup on Server")); setupUi(q); saveAsPB->hide(); // ### not yet implemented in LookupCertificatesCommand findED->setClearButtonEnabled(true); resultTV->setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q)); resultTV->setHierarchicalView(false); importPB()->setText(i18n("Import")); importPB()->setEnabled(false); connect(resultTV->view(), SIGNAL(doubleClicked(QModelIndex)), importPB(), SLOT(animateClick())); findED->setFocus(); connect(selectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::selectAll); connect(deselectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::clearSelection); } QPushButton *importPB() const { return buttonBox->button(QDialogButtonBox::Save); } QPushButton *closePB() const { return buttonBox->button(QDialogButtonBox::Close); } } ui; }; LookupCertificatesDialog::Private::Private(LookupCertificatesDialog *qq) : q(qq), passive(false), ui(q) { connect(ui.resultTV->view()->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged())); } LookupCertificatesDialog::Private::~Private() {} void LookupCertificatesDialog::Private::readConfig() { KConfigGroup configGroup(KSharedConfig::openStateConfig(), "LookupCertificatesDialog"); KConfigGroup resultKeysConfig = configGroup.group("ResultKeysView"); ui.resultTV->restoreLayout(resultKeysConfig); const QSize size = configGroup.readEntry("Size", QSize(600, 400)); if (size.isValid()) { q->resize(size); } } void LookupCertificatesDialog::Private::writeConfig() { KConfigGroup configGroup(KSharedConfig::openStateConfig(), "LookupCertificatesDialog"); configGroup.writeEntry("Size", q->size()); KConfigGroup resultKeysConfig = configGroup.group("ResultKeysView"); ui.resultTV->saveLayout(resultKeysConfig); configGroup.sync(); } LookupCertificatesDialog::LookupCertificatesDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { d->ui.findPB->setEnabled(false); d->readConfig(); } LookupCertificatesDialog::~LookupCertificatesDialog() { d->writeConfig(); } void LookupCertificatesDialog::setCertificates(const std::vector &certs) { d->ui.resultTV->view()->setFocus(); d->ui.resultTV->setKeys(certs); } std::vector LookupCertificatesDialog::selectedCertificates() const { return d->selectedCertificates(); } void LookupCertificatesDialog::setPassive(bool on) { if (d->passive == on) { return; } d->passive = on; d->enableDisableWidgets(); } bool LookupCertificatesDialog::isPassive() const { return d->passive; } void LookupCertificatesDialog::setSearchText(const QString &text) { d->ui.findED->setText(text); } QString LookupCertificatesDialog::searchText() const { return d->ui.findED->text(); } void LookupCertificatesDialog::showInformation(const QString &message) { d->ui.messageWidget->setText(message); if (message.isEmpty()) { d->ui.messageWidget->animatedHide(); } else { d->ui.messageWidget->animatedShow(); } } void LookupCertificatesDialog::accept() { Q_ASSERT(!selectedCertificates().empty()); Q_EMIT importRequested(selectedCertificates()); QDialog::accept(); } void LookupCertificatesDialog::Private::enableDisableWidgets() { // enable/disable everything except 'close', based on passive: const QList list = q->children(); for (QObject *const o : list) { if (QWidget *const w = qobject_cast(o)) { w->setDisabled(passive && w != ui.closePB() && w != ui.buttonBox); } } if (passive) { return; } ui.findPB->setEnabled(searchText().size() >= minimalSearchTextLength); const int n = q->selectedCertificates().size(); ui.detailsPB->setEnabled(n == 1); ui.saveAsPB->setEnabled(n == 1); ui.importPB()->setEnabled(n != 0); ui.importPB()->setDefault(false); // otherwise Import becomes default button if enabled and return triggers both a search and accept() } #include "moc_lookupcertificatesdialog.cpp" diff --git a/src/dialogs/nameandemailwidget.cpp b/src/dialogs/nameandemailwidget.cpp index d34e22b29..77c31a9e0 100644 --- a/src/dialogs/nameandemailwidget.cpp +++ b/src/dialogs/nameandemailwidget.cpp @@ -1,252 +1,251 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/nameandemailwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "nameandemailwidget.h" #include "view/errorlabel.h" #include "view/formtextinput.h" #include #include -#include #include #include #include "kleopatra_debug.h" using namespace Kleo; namespace { QString buildUserId(const QString &name, const QString &email) { if (name.isEmpty()) { return email; } else if (email.isEmpty()) { return name; } else { return QStringLiteral("%1 <%2>").arg(name, email); } } } class NameAndEmailWidget::Private { NameAndEmailWidget *const q; public: struct { std::unique_ptr> nameInput; std::unique_ptr> emailInput; } ui; explicit Private(NameAndEmailWidget *qq) : q{qq} { auto mainLayout = new QVBoxLayout{q}; { ui.nameInput = FormTextInput::create(q); ui.nameInput->setLabelText(i18nc("@label", "Name")); ui.nameInput->setValueRequiredErrorMessage(i18n("Enter a name.")); setNamePattern({}); mainLayout->addWidget(ui.nameInput->label()); mainLayout->addWidget(ui.nameInput->hintLabel()); mainLayout->addWidget(ui.nameInput->errorLabel()); mainLayout->addWidget(ui.nameInput->widget()); } connect(ui.nameInput->widget(), &QLineEdit::textChanged, q, [this]() { Q_EMIT q->userIDChanged(); }); { ui.emailInput = FormTextInput::create(q); ui.emailInput->setLabelText(i18nc("@label", "Email address")); ui.emailInput->setValueRequiredErrorMessage(i18n("Enter an email address.")); setEmailPattern({}); mainLayout->addWidget(ui.emailInput->label()); mainLayout->addWidget(ui.emailInput->hintLabel()); mainLayout->addWidget(ui.emailInput->errorLabel()); mainLayout->addWidget(ui.emailInput->widget()); } connect(ui.emailInput->widget(), &QLineEdit::textChanged, q, [this]() { Q_EMIT q->userIDChanged(); }); } void setNamePattern(const QString ®exp) { if (regexp.isEmpty()) { ui.nameInput->setValidator(Validation::simpleName(Validation::Optional)); ui.nameInput->setInvalidEntryErrorMessage( i18n("The name must not include <, >, and @."), i18nc("text for screen readers", "The name must not include less-than sign, greater-than sign, and at sign.")); } else { ui.nameInput->setValidator(Validation::simpleName(regexp, Validation::Optional)); ui.nameInput->setInvalidEntryErrorMessage( i18n("The name must be in the format required by your organization and " "it must not include <, >, and @."), i18nc("text for screen readers", "The name must be in the format required by your organization and " "it must not include less-than sign, greater-than sign, and at sign.")); } } void setEmailPattern(const QString ®exp) { if (regexp.isEmpty()) { ui.emailInput->setValidator(Validation::email(Validation::Optional)); ui.emailInput->setInvalidEntryErrorMessage(i18n( "Enter an email address in the correct format, like name@example.com.")); } else { ui.emailInput->setValidator(Validation::email(regexp, Validation::Optional)); ui.emailInput->setInvalidEntryErrorMessage(i18n( "Enter an email address in the correct format required by your organization.")); } } QString name() const { return ui.nameInput->widget()->text().trimmed(); } QString email() const { return ui.emailInput->widget()->text().trimmed(); } }; NameAndEmailWidget::NameAndEmailWidget(QWidget *parent, Qt::WindowFlags f) : QWidget{parent, f} , d(new Private{this}) { } NameAndEmailWidget::~NameAndEmailWidget() = default; void NameAndEmailWidget::setName(const QString &name) { d->ui.nameInput->widget()->setText(name); } QString NameAndEmailWidget::name() const { return d->name(); } void NameAndEmailWidget::setNameIsRequired(bool required) { d->ui.nameInput->setIsRequired(required); } bool NameAndEmailWidget::nameIsRequired() const { return d->ui.nameInput->isRequired(); } void NameAndEmailWidget::setNameLabel(const QString &label) { if (label.isEmpty()) { d->ui.nameInput->setLabelText(i18nc("@label", "Name")); } else { d->ui.nameInput->setLabelText(label); } } QString NameAndEmailWidget::nameLabel() const { return d->ui.nameInput->label()->text(); } void NameAndEmailWidget::setNameHint(const QString &hint) { d->ui.nameInput->setHint(hint); } QString NameAndEmailWidget::nameHint() const { return d->ui.nameInput->hintLabel()->text(); } void NameAndEmailWidget::setNamePattern(const QString &pattern) { d->setNamePattern(pattern); } QString NameAndEmailWidget::nameError() const { return d->ui.nameInput->currentError(); } void NameAndEmailWidget::setEmail(const QString &email) { d->ui.emailInput->widget()->setText(email); } QString NameAndEmailWidget::email() const { return d->email(); } void NameAndEmailWidget::setEmailIsRequired(bool required) { d->ui.emailInput->setIsRequired(required); } bool NameAndEmailWidget::emailIsRequired() const { return d->ui.emailInput->isRequired(); } void NameAndEmailWidget::setEmailLabel(const QString &label) { if (label.isEmpty()) { d->ui.emailInput->setLabelText(i18nc("@label", "Email address")); } else { d->ui.emailInput->setLabelText(label); } } QString NameAndEmailWidget::emailLabel() const { return d->ui.emailInput->label()->text(); } void NameAndEmailWidget::setEmailHint(const QString &hint) { d->ui.emailInput->setHint(hint); } QString NameAndEmailWidget::emailHint() const { return d->ui.emailInput->hintLabel()->text(); } void NameAndEmailWidget::setEmailPattern(const QString &pattern) { d->setEmailPattern(pattern); } QString NameAndEmailWidget::emailError() const { return d->ui.emailInput->currentError(); } QString NameAndEmailWidget::userID() const { return buildUserId(name(), email()); } diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp index d85995cca..53082d3a8 100644 --- a/src/dialogs/revokekeydialog.cpp +++ b/src/dialogs/revokekeydialog.cpp @@ -1,299 +1,295 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokekeydialog.h 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 "revokekeydialog.h" #include "utils/accessibility.h" #include "view/errorlabel.h" #include #include -#include #include #include -#include #include -#include -#include #include #include #include #include #include #include #include #include #include #include #include #ifdef QGPGME_SUPPORTS_KEY_REVOCATION #include #endif #include #include using namespace Kleo; using namespace GpgME; namespace { class TextEdit : public QTextEdit { Q_OBJECT public: using QTextEdit::QTextEdit; Q_SIGNALS: void editingFinished(); protected: void focusOutEvent(QFocusEvent *event) override { Qt::FocusReason reason = event->reason(); if (reason != Qt::PopupFocusReason || !(QApplication::activePopupWidget() && QApplication::activePopupWidget()->parentWidget() == this)) { Q_EMIT editingFinished(); } QTextEdit::focusOutEvent(event); } }; } class RevokeKeyDialog::Private { friend class ::Kleo::RevokeKeyDialog; RevokeKeyDialog *const q; struct { QLabel *infoLabel = nullptr; QLabel *descriptionLabel = nullptr; TextEdit *description = nullptr; ErrorLabel *descriptionError = nullptr; QDialogButtonBox *buttonBox = nullptr; } ui; Key key; QButtonGroup reasonGroup; bool descriptionEditingInProgress = false; QString descriptionAccessibleName; public: Private(RevokeKeyDialog *qq) : q(qq) { q->setWindowTitle(i18nc("title:window", "Revoke Key")); auto mainLayout = new QVBoxLayout{q}; ui.infoLabel = new QLabel{q}; mainLayout->addWidget(ui.infoLabel); #ifdef QGPGME_SUPPORTS_KEY_REVOCATION auto groupBox = new QGroupBox{i18nc("@title:group", "Reason for revocation"), q}; reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "No reason specified"), q}, static_cast(RevocationReason::Unspecified)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key has been compromised"), q}, static_cast(RevocationReason::Compromised)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is superseded"), q}, static_cast(RevocationReason::Superseded)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is no longer used"), q}, static_cast(RevocationReason::NoLongerUsed)); reasonGroup.button(static_cast(RevocationReason::Unspecified))->setChecked(true); { auto boxLayout = new QVBoxLayout{groupBox}; for (auto radio : reasonGroup.buttons()) { boxLayout->addWidget(radio); } } mainLayout->addWidget(groupBox); #endif { ui.descriptionLabel = new QLabel{i18nc("@label:textbox", "Description (optional):"), q}; ui.description = new TextEdit{q}; ui.description->setAcceptRichText(false); // do not accept Tab as input; this is better for accessibility and // tabulators are not really that useful in the description ui.description->setTabChangesFocus(true); ui.descriptionLabel->setBuddy(ui.description); ui.descriptionError = new ErrorLabel{q}; ui.descriptionError->setVisible(false); mainLayout->addWidget(ui.descriptionLabel); mainLayout->addWidget(ui.description); mainLayout->addWidget(ui.descriptionError); } connect(ui.description, &TextEdit::editingFinished, q, [this]() { onDescriptionEditingFinished(); }); connect(ui.description, &TextEdit::textChanged, q, [this]() { onDescriptionTextChanged(); }); ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = ui.buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18nc("@action:button", "Revoke Key")); okButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete-remove"))); mainLayout->addWidget(ui.buttonBox); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); }); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); restoreGeometry(); } ~Private() { saveGeometry(); } private: void saveGeometry() { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "RevokeKeyDialog"); cfgGroup.writeEntry("Size", q->size()); cfgGroup.sync(); } void restoreGeometry(const QSize &defaultSize = {}) { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "RevokeKeyDialog"); const QSize size = cfgGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } } void checkAccept() { if (!descriptionHasAcceptableInput()) { KMessageBox::error(q, descriptionErrorMessage()); } else { q->accept(); } } bool descriptionHasAcceptableInput() const { return !q->description().contains(QLatin1String{"\n\n"}); } QString descriptionErrorMessage() const { QString message; if (!descriptionHasAcceptableInput()) { message = i18n("Error: The description must not contain empty lines."); } return message; } void updateDescriptionError() { const auto currentErrorMessage = ui.descriptionError->text(); const auto newErrorMessage = descriptionErrorMessage(); if (newErrorMessage == currentErrorMessage) { return; } if (currentErrorMessage.isEmpty() && descriptionEditingInProgress) { // delay showing the error message until editing is finished, so that we // do not annoy the user with an error message while they are still // entering the recipient; // on the other hand, we clear the error message immediately if it does // not apply anymore and we update the error message immediately if it // changed return; } ui.descriptionError->setVisible(!newErrorMessage.isEmpty()); ui.descriptionError->setText(newErrorMessage); updateAccessibleNameAndDescription(); } void updateAccessibleNameAndDescription() { // fall back to default accessible name if accessible name wasn't set explicitly if (descriptionAccessibleName.isEmpty()) { descriptionAccessibleName = getAccessibleName(ui.description); } const bool errorShown = ui.descriptionError->isVisible(); // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); // emulate this by setting the error message as accessible description of the input field const auto description = errorShown ? ui.descriptionError->text() : QString{}; if (ui.description->accessibleDescription() != description) { ui.description->setAccessibleDescription(description); } // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); // screen readers say something like "invalid entry" if this state is set; // emulate this by adding "invalid entry" to the accessible name of the input field // and its label const auto name = errorShown ? descriptionAccessibleName + QLatin1String{", "} + invalidEntryText() : descriptionAccessibleName; if (ui.descriptionLabel->accessibleName() != name) { ui.descriptionLabel->setAccessibleName(name); } if (ui.description->accessibleName() != name) { ui.description->setAccessibleName(name); } } void onDescriptionTextChanged() { descriptionEditingInProgress = true; updateDescriptionError(); } void onDescriptionEditingFinished() { descriptionEditingInProgress = false; updateDescriptionError(); } }; RevokeKeyDialog::RevokeKeyDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{new Private{this}} { } RevokeKeyDialog::~RevokeKeyDialog() = default; void RevokeKeyDialog::setKey(const GpgME::Key &key) { d->key = key; d->ui.infoLabel->setText( xi18n("You are about to revoke the following key:%1") .arg(Formatting::summaryLine(key))); } #ifdef QGPGME_SUPPORTS_KEY_REVOCATION GpgME::RevocationReason RevokeKeyDialog::reason() const { return static_cast(d->reasonGroup.checkedId()); } #endif QString RevokeKeyDialog::description() const { static const QRegularExpression whitespaceAtEndOfLine{QStringLiteral(R"([ \t\r]+\n)")}; static const QRegularExpression trailingWhitespace{QStringLiteral(R"(\s*$)")}; return d->ui.description->toPlainText().remove(whitespaceAtEndOfLine).remove(trailingWhitespace); } #include "revokekeydialog.moc" diff --git a/src/dialogs/subkeyswidget.cpp b/src/dialogs/subkeyswidget.cpp index 26f1c8509..76d53e28f 100644 --- a/src/dialogs/subkeyswidget.cpp +++ b/src/dialogs/subkeyswidget.cpp @@ -1,323 +1,322 @@ /* dialogs/subkeyswidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "subkeyswidget.h" #include "commands/changeexpirycommand.h" #ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT #include "commands/exportsecretsubkeycommand.h" #endif #include "commands/keytocardcommand.h" #include "commands/importpaperkeycommand.h" #include "exportdialog.h" #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(GpgME::Subkey) using namespace Kleo; using namespace Kleo::Commands; class SubKeysWidget::Private { SubKeysWidget *const q; public: Private(SubKeysWidget *qq) : q{qq} , ui{qq} { ui.subkeysTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.subkeysTree, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { tableContextMenuRequested(p); }); } private: void tableContextMenuRequested(const QPoint &p); public: GpgME::Key key; public: struct UI { QVBoxLayout *mainLayout; NavigatableTreeWidget *subkeysTree; UI(QWidget *widget) { mainLayout = new QVBoxLayout{widget}; mainLayout->setContentsMargins(0, 0, 0, 0); auto subkeysTreeLabel = new QLabel{i18nc("@label", "Subkeys:"), widget}; mainLayout->addWidget(subkeysTreeLabel); subkeysTree = new NavigatableTreeWidget{widget}; subkeysTreeLabel->setBuddy(subkeysTree); subkeysTree->setAccessibleName(i18nc("@label", "Subkeys")); subkeysTree->setRootIsDecorated(false); subkeysTree->setHeaderLabels({ i18nc("@title:column", "ID"), i18nc("@title:column", "Type"), i18nc("@title:column", "Valid From"), i18nc("@title:column", "Valid Until"), i18nc("@title:column", "Status"), i18nc("@title:column", "Strength"), i18nc("@title:column", "Usage"), i18nc("@title:column", "Primary"), i18nc("@title:column", "Storage"), }); mainLayout->addWidget(subkeysTree); } } ui; }; void SubKeysWidget::Private::tableContextMenuRequested(const QPoint &p) { auto item = ui.subkeysTree->itemAt(p); if (!item) { return; } const auto subkey = item->data(0, Qt::UserRole).value(); const bool isOpenPGPKey = subkey.parent().protocol() == GpgME::OpenPGP; auto menu = new QMenu(q); connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); bool hasActions = false; if (isOpenPGPKey && subkey.parent().hasSecret()) { hasActions = true; menu->addAction(i18n("Change End of Validity Period..."), q, [this, subkey]() { auto cmd = new ChangeExpiryCommand(subkey.parent()); cmd->setSubkey(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &ChangeExpiryCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); key.update(); q->setKey(key); }); cmd->setParentWidget(q); cmd->start(); } ); } if (isOpenPGPKey && subkey.canAuthenticate()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export OpenSSH key"), q, [this, subkey]() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(subkey, static_cast (GpgME::Context::ExportSSH)); dlg->exec(); }); } if (!subkey.isSecret()) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-import")), i18n("Restore printed backup"), q, [this, subkey] () { auto cmd = new ImportPaperKeyCommand(subkey.parent()); ui.subkeysTree->setEnabled(false); connect(cmd, &ImportPaperKeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } if (subkey.isSecret()) { hasActions = true; auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("send-to-symbolic")), i18n("Transfer to smartcard"), q, [this, subkey]() { auto cmd = new KeyToCardCommand(subkey); ui.subkeysTree->setEnabled(false); connect(cmd, &KeyToCardCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); action->setEnabled(!KeyToCardCommand::getSuitableCards(subkey).empty()); } #ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT const bool isPrimarySubkey = subkey.keyID() == key.keyID(); if (isOpenPGPKey && subkey.isSecret() && !isPrimarySubkey) { hasActions = true; menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18n("Export secret subkey"), q, [this, subkey]() { auto cmd = new ExportSecretSubkeyCommand{{subkey}}; ui.subkeysTree->setEnabled(false); connect(cmd, &ExportSecretSubkeyCommand::finished, q, [this]() { ui.subkeysTree->setEnabled(true); }); cmd->setParentWidget(q); cmd->start(); }); } #endif if (hasActions) { menu->popup(ui.subkeysTree->viewport()->mapToGlobal(p)); } else { delete menu; } } SubKeysWidget::SubKeysWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } SubKeysWidget::~SubKeysWidget() { } void SubKeysWidget::setKey(const GpgME::Key &key) { d->key = key; const auto currentItem = d->ui.subkeysTree->currentItem(); const QByteArray selectedKeyFingerprint = currentItem ? QByteArray(currentItem->data(0, Qt::UserRole).value().fingerprint()) : QByteArray(); d->ui.subkeysTree->clear(); const auto subkeys = key.subkeys(); for (const auto &subkey : subkeys) { auto item = new QTreeWidgetItem; item->setData(0, Qt::DisplayRole, Formatting::prettyID(subkey.keyID())); item->setData(0, Qt::AccessibleTextRole, Formatting::accessibleHexID(subkey.keyID())); item->setData(0, Qt::UserRole, QVariant::fromValue(subkey)); item->setData(1, Qt::DisplayRole, Kleo::Formatting::type(subkey)); item->setData(2, Qt::DisplayRole, Kleo::Formatting::creationDateString(subkey)); item->setData(2, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(subkey)); item->setData(3, Qt::DisplayRole, Kleo::Formatting::expirationDateString(subkey)); item->setData(3, Qt::AccessibleTextRole, Formatting::accessibleExpirationDate(subkey)); item->setData(4, Qt::DisplayRole, Kleo::Formatting::validityShort(subkey)); switch (subkey.publicKeyAlgorithm()) { case GpgME::Subkey::AlgoECDSA: case GpgME::Subkey::AlgoEDDSA: case GpgME::Subkey::AlgoECDH: item->setData(5, Qt::DisplayRole, QString::fromStdString(subkey.algoName())); break; default: item->setData(5, Qt::DisplayRole, QString::number(subkey.length())); } item->setData(6, Qt::DisplayRole, Kleo::Formatting::usageString(subkey)); const auto isPrimary = subkey.keyID() == key.keyID(); item->setData(7, Qt::DisplayRole, isPrimary ? QStringLiteral("✓") : QString()); item->setData(7, Qt::AccessibleTextRole, isPrimary ? i18nc("yes, is primary key", "yes") : i18nc("no, is not primary key", "no")); if (subkey.isCardKey()) { if (const char *serialNo = subkey.cardSerialNumber()) { item->setData(8, Qt::DisplayRole, i18nc("smart card ", "smart card %1", QString::fromUtf8(serialNo))); } else { item->setData(8, Qt::DisplayRole, i18n("smart card")); } } else if (isPrimary && key.hasSecret() && !subkey.isSecret()) { item->setData(8, Qt::DisplayRole, i18nc("key is 'offline key', i.e. secret key is not stored on this computer", "offline")); } else if (subkey.isSecret()) { item->setData(8, Qt::DisplayRole, i18n("on this computer")); } else { item->setData(8, Qt::DisplayRole, i18nc("unknown storage location", "unknown")); } d->ui.subkeysTree->addTopLevelItem(item); if (subkey.fingerprint() == selectedKeyFingerprint) { d->ui.subkeysTree->setCurrentItem(item); } } if (!key.hasSecret()) { // hide information about storage location for keys of other people d->ui.subkeysTree->hideColumn(8); } d->ui.subkeysTree->header()->resizeSections(QHeaderView::ResizeToContents); } GpgME::Key SubKeysWidget::key() const { return d->key; } SubKeysDialog::SubKeysDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Subkeys Details")); auto l = new QVBoxLayout(this); l->addWidget(new SubKeysWidget(this)); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::clicked, this, &QDialog::accept); l->addWidget(bbox); readConfig(); } SubKeysDialog::~SubKeysDialog() { writeConfig(); } void SubKeysDialog::readConfig() { KConfigGroup dialog(KSharedConfig::openStateConfig(), "SubKeysDialog"); const QSize size = dialog.readEntry("Size", QSize(820, 280)); if (size.isValid()) { resize(size); } } void SubKeysDialog::writeConfig() { KConfigGroup dialog(KSharedConfig::openStateConfig(), "SubKeysDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void SubKeysDialog::setKey(const GpgME::Key &key) { auto w = findChild(); Q_ASSERT(w); w->setKey(key); } GpgME::Key SubKeysDialog::key() const { auto w = findChild(); Q_ASSERT(w); return w->key(); } diff --git a/src/newcertificatewizard/listwidget.cpp b/src/newcertificatewizard/listwidget.cpp index 5779c89aa..70f26147d 100644 --- a/src/newcertificatewizard/listwidget.cpp +++ b/src/newcertificatewizard/listwidget.cpp @@ -1,228 +1,227 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/listwidget.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 "listwidget.h" #include "ui_listwidget.h" #include -#include #include #include #include #include using namespace Kleo::NewCertificateUi; namespace { class ItemDelegate : public QItemDelegate { Q_OBJECT public: explicit ItemDelegate(QObject *p = nullptr) : QItemDelegate(p), m_rx() {} explicit ItemDelegate(const QRegularExpression &rx, QObject *p = nullptr) : QItemDelegate(p), m_rx(rx) {} void setRegExpFilter(const QRegularExpression &rx) { m_rx = rx; } const QRegularExpression ®ExpFilter() const { return m_rx; } QWidget *createEditor(QWidget *p, const QStyleOptionViewItem &o, const QModelIndex &i) const override { QWidget *w = QItemDelegate::createEditor(p, o, i); if (m_rx.isValid()) if (auto const le = qobject_cast(w)) { le->setValidator(new QRegularExpressionValidator(m_rx, le)); } return w; } private: QRegularExpression m_rx; }; } class ListWidget::Private { friend class ::Kleo::NewCertificateUi::ListWidget; ListWidget *const q; public: explicit Private(ListWidget *qq) : q(qq), stringListModel(), ui(q) { ui.listView->setModel(&stringListModel); ui.listView->setItemDelegate(&delegate); connect(ui.listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged())); connect(&stringListModel, &QAbstractItemModel::dataChanged, q, &ListWidget::itemsChanged); connect(&stringListModel, &QAbstractItemModel::rowsInserted, q, &ListWidget::itemsChanged); connect(&stringListModel, &QAbstractItemModel::rowsRemoved, q, &ListWidget::itemsChanged); } private: void slotAdd() { const int idx = stringListModel.rowCount(); if (stringListModel.insertRows(idx, 1)) { stringListModel.setData(stringListModel.index(idx), defaultValue); editRow(idx); } } void slotRemove() { const int idx = selectedRow(); stringListModel.removeRows(idx, 1); selectRow(idx); } void slotUp() { const int idx = selectedRow(); swapRows(idx - 1, idx); selectRow(idx - 1); } void slotDown() { const int idx = selectedRow(); swapRows(idx, idx + 1); selectRow(idx + 1); } void slotSelectionChanged() { enableDisableActions(); } private: void editRow(int idx) { const QModelIndex mi = stringListModel.index(idx); if (!mi.isValid()) { return; } ui.listView->setCurrentIndex(mi); ui.listView->edit(mi); } QModelIndexList selectedIndexes() const { return ui.listView->selectionModel()->selectedRows(); } int selectedRow() const { const QModelIndexList mil = selectedIndexes(); return mil.empty() ? -1 : mil.front().row(); } void selectRow(int idx) { const QModelIndex mi = stringListModel.index(idx); if (mi.isValid()) { ui.listView->setCurrentIndex(mi); } } void swapRows(int r1, int r2) { if (r1 < 0 || r2 < 0 || r1 >= stringListModel.rowCount() || r2 >= stringListModel.rowCount()) { return; } const QModelIndex m1 = stringListModel.index(r1); const QModelIndex m2 = stringListModel.index(r2); const QVariant data1 = m1.data(); const QVariant data2 = m2.data(); stringListModel.setData(m1, data2); stringListModel.setData(m2, data1); } void enableDisableActions() { const QModelIndexList mil = selectedIndexes(); ui.removeTB->setEnabled(!mil.empty()); ui.upTB->setEnabled(mil.size() == 1 && mil.front().row() > 0); ui.downTB->setEnabled(mil.size() == 1 && mil.back().row() < stringListModel.rowCount() - 1); } private: QStringListModel stringListModel; ItemDelegate delegate; QString defaultValue; struct UI : Ui_ListWidget { explicit UI(ListWidget *q) : Ui_ListWidget() { setupUi(q); addTB->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addTB->setAccessibleName(i18nc("@action:button", "Add Entry")); removeTB->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); removeTB->setAccessibleName(i18nc("@action:button", "Remove Entry")); upTB->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); upTB->setAccessibleName(i18nc("@action:button", "Move Up")); downTB->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); downTB->setAccessibleName(i18nc("@action:button", "Move Down")); } } ui; }; ListWidget::ListWidget(QWidget *p) : QWidget(p), d(new Private(this)) { } ListWidget::~ListWidget() {} QStringList ListWidget::items() const { return d->stringListModel.stringList(); } void ListWidget::setItems(const QStringList &items) { d->stringListModel.setStringList(items); } QRegularExpression ListWidget::regExpFilter() const { return d->delegate.regExpFilter(); } void ListWidget::setRegExpFilter(const QRegularExpression &rx) { d->delegate.setRegExpFilter(rx); } QString ListWidget::defaultValue() const { return d->defaultValue; } void ListWidget::setDefaultValue(const QString &df) { d->defaultValue = df; } #include "moc_listwidget.cpp" #include "listwidget.moc" diff --git a/src/newcertificatewizard/resultpage.cpp b/src/newcertificatewizard/resultpage.cpp index c43335826..2a30ab592 100644 --- a/src/newcertificatewizard/resultpage.cpp +++ b/src/newcertificatewizard/resultpage.cpp @@ -1,378 +1,376 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/resultpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "resultpage_p.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT # include "commands/exportsecretkeycommand.h" #else # include "commands/exportsecretkeycommand_old.h" #endif #include "utils/dragqueen.h" #include "utils/email.h" #include "utils/filedialog.h" #include "utils/scrollarea.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::Commands; using namespace Kleo::NewCertificateUi; using namespace GpgME; #ifndef QGPGME_SUPPORTS_SECRET_KEY_EXPORT using Kleo::Commands::Compat::ExportSecretKeyCommand; #endif struct ResultPage::UI { QTextBrowser *resultTB = nullptr; QTextBrowser *errorTB = nullptr; DragQueen *dragQueen = nullptr; QPushButton *restartWizardPB = nullptr; QGroupBox *nextStepsGB = nullptr; QPushButton *saveRequestToFilePB = nullptr; QPushButton *sendRequestByEMailPB = nullptr; QPushButton *makeBackupPB = nullptr; QPushButton *sendCertificateByEMailPB = nullptr; QPushButton *uploadToKeyserverPB = nullptr; QPushButton *createRevocationRequestPB = nullptr; QPushButton *createSigningCertificatePB = nullptr; QPushButton *createEncryptionCertificatePB = nullptr; UI(QWizardPage *parent) { auto mainLayout = new QVBoxLayout{parent}; const auto margins = mainLayout->contentsMargins(); mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); auto resultGB = new QGroupBox{i18nc("@title:group", "Result"), parent}; auto resultGBLayout = new QHBoxLayout{resultGB}; resultTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(resultTB); errorTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(errorTB); dragQueen = new Kleo::DragQueen{resultGB}; dragQueen->setToolTip(i18n("Drag this icon to your mail application's composer to attach the request to a mail.")); dragQueen->setAlignment(Qt::AlignCenter); resultGBLayout->addWidget(dragQueen); scrollAreaLayout->addWidget(resultGB); restartWizardPB = new QPushButton{i18n("Restart This Wizard (Keeps Your Parameters)"), parent}; scrollAreaLayout->addWidget(restartWizardPB); nextStepsGB = new QGroupBox{i18nc("@title:group", "Next Steps"), parent}; auto nextStepsGBLayout = new QVBoxLayout{nextStepsGB}; saveRequestToFilePB = new QPushButton{i18n("Save Certificate Request To File..."), nextStepsGB}; nextStepsGBLayout->addWidget(saveRequestToFilePB); sendRequestByEMailPB = new QPushButton{i18n("Send Certificate Request By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendRequestByEMailPB); makeBackupPB = new QPushButton{i18n("Make a Backup Of Your Key Pair..."), nextStepsGB}; nextStepsGBLayout->addWidget(makeBackupPB); sendCertificateByEMailPB = new QPushButton{i18n("Send Public Key By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendCertificateByEMailPB); uploadToKeyserverPB = new QPushButton{i18n("Upload Public Key To Directory Service..."), nextStepsGB}; nextStepsGBLayout->addWidget(uploadToKeyserverPB); createRevocationRequestPB = new QPushButton{i18n("Create Revocation Request..."), nextStepsGB}; nextStepsGBLayout->addWidget(createRevocationRequestPB); createSigningCertificatePB = new QPushButton{i18n("Create Signing Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createSigningCertificatePB); createEncryptionCertificatePB = new QPushButton{i18n("Create Encryption Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createEncryptionCertificatePB); scrollAreaLayout->addWidget(nextStepsGB); mainLayout->addWidget(scrollArea); } }; ResultPage::ResultPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} , initialized{false} , successfullyCreatedSigningCertificate{false} , successfullyCreatedEncryptionCertificate{false} { setObjectName(QString::fromUtf8("Kleo__NewCertificateUi__ResultPage")); connect(ui->saveRequestToFilePB, &QPushButton::clicked, this, &ResultPage::slotSaveRequestToFile); connect(ui->sendRequestByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendRequestByEMail); connect(ui->sendCertificateByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendCertificateByEMail); connect(ui->uploadToKeyserverPB, &QPushButton::clicked, this, &ResultPage::slotUploadCertificateToDirectoryServer); connect(ui->makeBackupPB, &QPushButton::clicked, this, &ResultPage::slotBackupCertificate); connect(ui->createRevocationRequestPB, &QPushButton::clicked, this, &ResultPage::slotCreateRevocationRequest); connect(ui->createSigningCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateSigningCertificate); connect(ui->createEncryptionCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateEncryptionCertificate); ui->dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui->errorTB, "plainText"); registerField(QStringLiteral("result"), ui->resultTB, "plainText"); registerField(QStringLiteral("url"), ui->dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... auto le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } ResultPage::~ResultPage() = default; void ResultPage::initializePage() { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui->resultTB ->setVisible(!error); ui->errorTB ->setVisible(error); ui->dragQueen ->setVisible(!error &&!pgp()); ui->restartWizardPB ->setVisible(error); ui->nextStepsGB ->setVisible(!error); ui->saveRequestToFilePB ->setVisible(!pgp()); ui->makeBackupPB ->setVisible(pgp()); ui->createRevocationRequestPB->setVisible(pgp() &&false); // not implemented ui->sendCertificateByEMailPB ->setVisible(pgp()); ui->sendRequestByEMailPB ->setVisible(!pgp()); ui->uploadToKeyserverPB ->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui->createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate &&!successfullyCreatedSigningCertificate); ui->createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate &&!successfullyCreatedEncryptionCertificate); if (error) { wizard()->setOptions(wizard()->options() & ~QWizard::NoCancelButtonOnLastPage); } else { wizard()->setOptions(wizard()->options() | QWizard::NoCancelButtonOnLastPage); } if (!initialized) { connect(ui->restartWizardPB, &QAbstractButton::clicked, this, [this]() { restartAtEnterDetailsPage(); }); } initialized = true; } bool ResultPage::isError() const { return !ui->errorTB->document()->isEmpty(); } bool ResultPage::isComplete() const { return !isError(); } Key ResultPage::key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } void ResultPage::slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { fileName += QLatin1String(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file %1 " "to file %2: %3", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", fileName), i18nc("@title", "Request Saved")); } void ResultPage::slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QFileInfo{QUrl(url()).toLocalFile()}); // attachment KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then drag the Kleopatra icon and drop it on the message compose window of your mail client." "If that does not work, either, save the request to a file, and then attach that."), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void ResultPage::slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } auto cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void ResultPage::slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = nullptr; if (fileName.isEmpty()) { return; } invokeMailer(i18n("My new public OpenPGP key"), // subject i18n("Please find attached my new public OpenPGP key."), // body QFileInfo{fileName}); KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then attach the file %1 manually.", fileName), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-openpgp-mailto-troubles")); } void ResultPage::slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void ResultPage::slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void ResultPage::slotCreateRevocationRequest() { } void ResultPage::slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel( this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); restartAtEnterDetailsPage(); } diff --git a/src/uiserver/echocommand.cpp b/src/uiserver/echocommand.cpp index ea850e09e..f33f9b38f 100644 --- a/src/uiserver/echocommand.cpp +++ b/src/uiserver/echocommand.cpp @@ -1,191 +1,190 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/echocommand.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 "echocommand.h" #include #include #include #include #include #include #include -#include #include #include using namespace Kleo; static const char option_prefix[] = "prefix"; class EchoCommand::Private { public: int operationsInFlight = 0; QByteArray buffer; }; EchoCommand::EchoCommand() : QObject(), AssuanCommandMixin(), d(new Private) {} EchoCommand::~EchoCommand() {} int EchoCommand::doStart() { const std::vector< std::shared_ptr > in = inputs(), msg = messages(); const std::vector< std::shared_ptr > out = outputs(); if (!in.empty() && out.empty()) { return makeError(GPG_ERR_NOT_SUPPORTED); } if (!msg.empty()) { return makeError(GPG_ERR_NOT_SUPPORTED); } if (hasOption(option_prefix) && !option(option_prefix).toByteArray().isEmpty()) { return makeError(GPG_ERR_NOT_IMPLEMENTED); } std::string keyword; if (hasOption("inquire")) { keyword = option("inquire").toString().toStdString(); if (keyword.empty()) { return makeError(GPG_ERR_INV_ARG); } } const std::string output = option("text").toString().toStdString(); // aaand ACTION: // 1. echo the command line though the status channel sendStatus("ECHO", output.empty() ? QString() : QLatin1String(output.c_str())); // 2. if --inquire was given, inquire more data from the client: if (!keyword.empty()) { if (const int err = inquire(keyword.c_str(), this, SLOT(slotInquireData(int,QByteArray)))) { return err; } else { ++d->operationsInFlight; } } // 3. if INPUT was given, start the data pump for input->output if (const std::shared_ptr i = in.at(0)->ioDevice()) { const std::shared_ptr o = out.at(0)->ioDevice(); ++d->operationsInFlight; connect(i.get(), &QIODevice::readyRead, this, &EchoCommand::slotInputReadyRead); connect(o.get(), &QIODevice::bytesWritten, this, &EchoCommand::slotOutputBytesWritten); if (i->bytesAvailable()) { slotInputReadyRead(); } } if (!d->operationsInFlight) { done(); } return 0; } void EchoCommand::doCanceled() { } void EchoCommand::slotInquireData(int rc, const QByteArray &data) { --d->operationsInFlight; if (rc) { done(rc); return; } try { sendStatus("ECHOINQ", QLatin1String(data)); if (!d->operationsInFlight) { done(); } } catch (const Exception &e) { done(e.error(), e.message()); } catch (const std::exception &e) { done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignCommand::Private::slotMicAlgDetermined: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignCommand::Private::slotMicAlgDetermined")); } } void EchoCommand::slotInputReadyRead() { const std::shared_ptr in = inputs().at(0)->ioDevice(); Q_ASSERT(in); QByteArray buffer; buffer.resize(in->bytesAvailable()); const qint64 read = in->read(buffer.data(), buffer.size()); if (read == - 1) { done(makeError(GPG_ERR_EIO)); return; } if (read == 0 || (!in->isSequential() && read == in->size())) { in->close(); } buffer.resize(read); d->buffer += buffer; slotOutputBytesWritten(); } void EchoCommand::slotOutputBytesWritten() { const std::shared_ptr out = outputs().at(0)->ioDevice(); Q_ASSERT(out); if (!d->buffer.isEmpty()) { if (out->bytesToWrite()) { return; } const qint64 written = out->write(d->buffer); if (written == -1) { done(makeError(GPG_ERR_EIO)); return; } d->buffer.remove(0, written); } if (out->isOpen() && d->buffer.isEmpty() && !inputs().at(0)->ioDevice()->isOpen()) { out->close(); if (!--d->operationsInFlight) { done(); } } } diff --git a/src/utils/accessibility.cpp b/src/utils/accessibility.cpp index c6930dbbd..0b906ea7a 100644 --- a/src/utils/accessibility.cpp +++ b/src/utils/accessibility.cpp @@ -1,188 +1,187 @@ /* utils/accessibility.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 "accessibility.h" #include #include #ifdef Q_OS_WIN32 #include #include #endif #include -#include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; static const char *accessibleNameProperty = "_kleo_accessibleName"; static const char *accessibleValueProperty = "_kleo_accessibleValue"; static const char *useAccessibleValueLabelProperty = "_kleo_useAccessibleValueLabel"; namespace { QString getAccessibleText(QWidget *widget, QAccessible::Text t) { QString name; if (const auto *const iface = QAccessible::queryAccessibleInterface(widget)) { name = iface->text(t); } return name; } } QString Kleo::getAccessibleName(QWidget *widget) { return getAccessibleText(widget, QAccessible::Name); } QString Kleo::getAccessibleDescription(QWidget *widget) { return getAccessibleText(widget, QAccessible::Description); } void Kleo::setAccessibleName(QAction *action, const QString &name) { action->setProperty(accessibleNameProperty, name); } QString Kleo::getAccessibleName(const QAction *action) { return action->property(accessibleNameProperty).toString(); } void Kleo::setAccessibleValue(QWidget *widget, const QString &value) { widget->setProperty(accessibleValueProperty, value); } QString Kleo::getAccessibleValue(const QWidget *widget) { return widget->property(accessibleValueProperty).toString(); } void Kleo::setRepresentAsAccessibleValueWidget(QWidget *widget, bool flag) { widget->setProperty(useAccessibleValueLabelProperty, flag ? flag : QVariant{}); } bool Kleo::representAsAccessibleValueWidget(const QWidget *widget) { return widget->property(useAccessibleValueLabelProperty).toBool(); } QString Kleo::invalidEntryText() { return i18nc("text for screen readers to indicate that the associated object, " "such as a form field, has an error", "invalid entry"); } QString Kleo::requiredText() { return i18nc("text for screen readers to indicate that the associated object, " "such as a form field must be filled out", "required"); } void Kleo::selectLabelText(QLabel *label) { if (!label || label->text().isEmpty()) { return; } if (label->textFormat() == Qt::PlainText) { label->setSelection(0, label->text().size()); } else if (label->textFormat() == Qt::RichText) { // unfortunately, there is no selectAll(); therefore, we need // to determine the "visual" length of the text by stripping // the label's text of all formatting information QTextDocument temp; temp.setHtml(label->text()); label->setSelection(0, temp.toRawText().size()); } else { qCDebug(KLEOPATRA_LOG) << "Label with unsupported text format" << label->textFormat() << "got focus"; } } namespace { static void notifyAccessibilityClientsAboutToolTip(const QPoint &pos, QWidget *parent) { #ifdef Q_OS_WIN32 // On Windows, the tool tip's parent widget is a desktop screen widget (see implementation of QToolTip::showText) QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED const auto desktop = QApplication::desktop(); const int screenNumber = desktop->isVirtualDesktop() ? desktop->screenNumber(pos) : desktop->screenNumber(parent); parent = desktop->screen(screenNumber); QT_WARNING_POP #else Q_UNUSED(pos); #endif if (!parent) { return; } if (auto toolTipLabel = parent->findChild(QStringLiteral("qtooltip_label"))) { // Qt explicitly does not notify accessibility clients about the tool tip being shown because // "Tooltips are read aloud twice in MS narrator." // The problem is that they are not read out by orca (on Linux) if the notification is omitted. // Therefore, we take care of notifying the accessibility clients. #ifndef QT_NO_ACCESSIBILITY QAccessibleEvent event(toolTipLabel, QAccessible::ObjectShow); QAccessible::updateAccessibility(&event); #endif } } } void Kleo::showToolTip(const QPoint &pos, const QString &text, QWidget *w) { using namespace std::chrono_literals; static const std::chrono::milliseconds timeout = 24h; QToolTip::showText(pos, text, w, {}, timeout.count()); notifyAccessibilityClientsAboutToolTip(pos, w); } LabelHelper::LabelHelper() { QAccessible::installActivationObserver(this); } LabelHelper::~LabelHelper() { QAccessible::removeActivationObserver(this); } void LabelHelper::addLabel(QLabel *label) { mLabels.push_back(label); accessibilityActiveChanged(QAccessible::isActive()); } void LabelHelper::accessibilityActiveChanged(bool active) { // Allow text labels to get focus if accessibility is active const auto focusPolicy = active ? Qt::StrongFocus : Qt::ClickFocus; std::for_each(std::cbegin(mLabels), std::cend(mLabels), [focusPolicy](const auto &label) { if (label) { label->setFocusPolicy(focusPolicy); } }); } diff --git a/src/utils/accessibility.h b/src/utils/accessibility.h index eb7b3b056..569f074f2 100644 --- a/src/utils/accessibility.h +++ b/src/utils/accessibility.h @@ -1,109 +1,108 @@ /* utils/accessibility.h 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 */ #pragma once #include #include -#include class QAction; class QLabel; class QObject; class QString; namespace Kleo { QString getAccessibleName(QWidget *widget); QString getAccessibleDescription(QWidget *widget); /** * Sets the accessible name of the action \p action. * * \note Qt does not provide an accessible object for a QAction. Therefore, * we store the accessible name as custom property of the action. * \sa getAccessibleName */ void setAccessibleName(QAction *action, const QString &name); /** * Returns the accessible name of the action \p action. * \sa setAccessibleName */ QString getAccessibleName(const QAction *action); /** * Sets \p value as accessible value of \p widget. * * Stores the string \p value as custom property of the widget \p widget * for retrieval by a QAccessibleWidget. * * \sa getAccessibleValue */ void setAccessibleValue(QWidget *widget, const QString &value); /** * Returns the accessible value of \p widget. * * \sa setAccessibleValue */ QString getAccessibleValue(const QWidget *widget); /** * Mark \p widget as being represented as AccessibleValueWidget. * * This is useful, if you want Windows UI Automation to treat the widget * as labelled value, i.e. a custom widget with a value and a name. * * \note: Don't use this on other platforms than Windows, unless you made * sure that it works as expected. * \sa representAsAccessibleValueWidget */ void setRepresentAsAccessibleValueWidget(QWidget *widget, bool); /** * Returns whether \p widget is marked as being represented as * AccessibleValueWidget. * * \sa setRepresentAsAccessibleValueWidget */ bool representAsAccessibleValueWidget(const QWidget *widget); QString invalidEntryText(); QString requiredText(); /** * Selects the text displayed by the label. Only \ref QLabel with text format * \c Qt::PlainText or \c Qt::RichText are supported. */ void selectLabelText(QLabel *label); /** * Shows \p text as a tool tip, with the global position \p pos as the point of interest. * Additionally to QToolTip::showText, it takes care of notifying accessibility clients * about the tool tip. * \sa QToolTip::showText */ void showToolTip(const QPoint &pos, const QString &text, QWidget *w); /** * Simple helper that sets the focus policy of the associated labels * to \c Qt::StrongFocus if an assistive tool is active. */ class LabelHelper: public QAccessible::ActivationObserver { public: LabelHelper(); ~LabelHelper() override; Q_DISABLE_COPY_MOVE(LabelHelper) void addLabel(QLabel *label); private: void accessibilityActiveChanged(bool active) override; std::vector> mLabels; }; } diff --git a/src/utils/email.cpp b/src/utils/email.cpp index 9ae9f9707..f2e39e387 100644 --- a/src/utils/email.cpp +++ b/src/utils/email.cpp @@ -1,52 +1,51 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/email.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "email.h" #include -#include #include #include #include #include void Kleo::invokeMailer(const QString &subject, const QString &body, const QFileInfo &attachment) { invokeMailer({}, subject, body, attachment); } void Kleo::invokeMailer(const QString &to, const QString &subject, const QString &body, const QFileInfo &attachment) { const auto attachmentPath = attachment.filePath(); qCDebug(KLEOPATRA_LOG) << __func__ << "to:" << to << "subject:" << subject << "body:" << body << "attachment:" << attachmentPath; // RFC 2368 says body's linebreaks need to be encoded as // "%0D%0A", so normalize body to CRLF: //body.replace(QLatin1Char('\n'), QStringLiteral("\r\n")).remove(QStringLiteral("\r\r")); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("body"), body); if (!attachmentPath.isEmpty()) { query.addQueryItem(QStringLiteral("attach"), attachmentPath); } QUrl url; url.setScheme(QStringLiteral("mailto")); url.setPath(to); url.setQuery(query); qCDebug(KLEOPATRA_LOG) << __func__ << "Calling QDesktopServices::openUrl" << url; QDesktopServices::openUrl(url); } diff --git a/src/utils/keyparameters.cpp b/src/utils/keyparameters.cpp index e1f98185b..73e89aeb6 100644 --- a/src/utils/keyparameters.cpp +++ b/src/utils/keyparameters.cpp @@ -1,367 +1,366 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/keyparameters.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyparameters.h" #include "keyusage.h" #include -#include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; namespace { QString encodeDomainName(const QString &domain) { const QByteArray encodedDomain = QUrl::toAce(domain); return encodedDomain.isEmpty() ? domain : QString::fromLatin1(encodedDomain); } QString encodeEmail(const QString &email) { const int at = email.lastIndexOf(QLatin1Char('@')); if (at < 0) { return email; } return email.left(at + 1) + encodeDomainName(email.mid(at + 1)); } } class KeyParameters::Private { friend class ::Kleo::KeyParameters; Protocol protocol; Subkey::PubkeyAlgo keyType = Subkey::AlgoUnknown; QString cardKeyRef; unsigned int keyLength = 0; QString keyCurve; KeyUsage keyUsage; Subkey::PubkeyAlgo subkeyType = Subkey::AlgoUnknown; unsigned int subkeyLength = 0; QString subkeyCurve; KeyUsage subkeyUsage; QString name; QString dn; std::vector emailAdresses; std::vector domainNames; std::vector uris; QDate expirationDate; public: explicit Private(Protocol proto) : protocol(proto) { } }; KeyParameters::KeyParameters() : KeyParameters{NoProtocol} { } KeyParameters::KeyParameters(Protocol protocol) : d{new Private{protocol}} { } KeyParameters::~KeyParameters() = default; KeyParameters::KeyParameters(const KeyParameters &other) : d{new Private{*other.d}} { } KeyParameters &KeyParameters::operator=(const KeyParameters &other) { *d = *other.d; return *this; } KeyParameters::KeyParameters(KeyParameters &&other) = default; KeyParameters &KeyParameters::operator=(KeyParameters &&other) = default; KeyParameters::Protocol KeyParameters::protocol() const { return d->protocol; } void KeyParameters::setKeyType(Subkey::PubkeyAlgo type) { d->keyType = type; } GpgME::Subkey::PubkeyAlgo KeyParameters::keyType() const { return d->keyType; } void KeyParameters::setCardKeyRef(const QString &cardKeyRef) { d->cardKeyRef = cardKeyRef; } QString KeyParameters::cardKeyRef() const { return d->cardKeyRef; } void KeyParameters::setKeyLength(unsigned int length) { d->keyLength = length; } unsigned int KeyParameters::keyLength() const { return d->keyLength; } void KeyParameters::setKeyCurve(const QString &curve) { d->keyCurve = curve; } QString KeyParameters::keyCurve() const { return d->keyCurve; } void KeyParameters::setKeyUsage(const KeyUsage &usage) { d->keyUsage = usage; } KeyUsage KeyParameters::keyUsage() const { return d->keyUsage; } void KeyParameters::setSubkeyType(Subkey::PubkeyAlgo type) { d->subkeyType = type; } Subkey::PubkeyAlgo KeyParameters::subkeyType() const { return d->subkeyType; } void KeyParameters::setSubkeyLength(unsigned int length) { d->subkeyLength = length; } unsigned int KeyParameters::subkeyLength() const { return d->subkeyLength; } void KeyParameters::setSubkeyCurve(const QString &curve) { d->subkeyCurve = curve; } QString KeyParameters::subkeyCurve() const { return d->subkeyCurve; } void KeyParameters::setSubkeyUsage(const KeyUsage &usage) { d->subkeyUsage = usage; } KeyUsage KeyParameters::subkeyUsage() const { return d->subkeyUsage; } void KeyParameters::setExpirationDate(const QDate &date) { d->expirationDate = date; } QDate KeyParameters::expirationDate() const { return d->expirationDate; } void KeyParameters::setName(const QString &name) { d->name = name; } QString KeyParameters::name() const { return d->name; } void KeyParameters::setDN(const QString &dn) { d->dn = dn; } QString KeyParameters::dn() const { return d->dn; } void KeyParameters::setEmail(const QString &email) { d->emailAdresses = {email}; } void KeyParameters::addEmail(const QString& email) { d->emailAdresses.push_back(email); } std::vector KeyParameters::emails() const { return d->emailAdresses; } void KeyParameters::addDomainName(const QString& domain) { d->domainNames.push_back(domain); } std::vector KeyParameters::domainNames() const { return d->domainNames; } void KeyParameters::addURI(const QString& uri) { d->uris.push_back(uri); } std::vector KeyParameters::uris() const { return d->uris; } namespace { QString serialize(Subkey::PubkeyAlgo algo) { return QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(algo)); } QString serialize(unsigned int number) { return QString::number(number); } QString serialize(KeyUsage keyUsage) { QStringList usages; if (keyUsage.canSign()) { usages << QStringLiteral("sign"); } if (keyUsage.canEncrypt()) { usages << QStringLiteral("encrypt"); } if (keyUsage.canAuthenticate()) { usages << QStringLiteral("auth"); } if (keyUsage.canCertify()) { usages << QStringLiteral("cert"); } return usages.join(QLatin1Char{' '}); } QString serialize(const QDate &date) { return date.toString(Qt::ISODate); } QString serialize(const char *key, const QString &value) { return QString::fromLatin1(key) + QLatin1Char(':') + value; } } QString KeyParameters::toString() const { QStringList keyParameters; keyParameters.push_back(QLatin1String("")); if (d->protocol == OpenPGP) { // for backward compatibility with GnuPG 2.0 and earlier keyParameters.push_back(QStringLiteral("%ask-passphrase")); } // add Key-Type as first parameter if (!d->cardKeyRef.isEmpty()) { keyParameters.push_back(serialize("Key-Type", QLatin1String{"card:"} + d->cardKeyRef)); } else if (d->keyType != Subkey::AlgoUnknown) { keyParameters.push_back(serialize("Key-Type", serialize(d->keyType))); } else { qCWarning(KLEOPATRA_LOG) << "KeyParameters::toString(): Key type is unset/empty"; } if (d->keyLength) { keyParameters.push_back(serialize("Key-Length", serialize(d->keyLength))); } if (!d->keyCurve.isEmpty()) { keyParameters.push_back(serialize("Key-Curve", d->keyCurve)); } keyParameters.push_back(serialize("Key-Usage", serialize(d->keyUsage))); if (d->subkeyType != Subkey::AlgoUnknown) { keyParameters.push_back(serialize("Subkey-Type", serialize(d->subkeyType))); if (d->subkeyUsage.value()) { keyParameters.push_back(serialize("Subkey-Usage", serialize(d->subkeyUsage))); } if (d->subkeyLength) { keyParameters.push_back(serialize("Subkey-Length", serialize(d->subkeyLength))); } if (!d->subkeyCurve.isEmpty()) { keyParameters.push_back(serialize("Subkey-Curve", d->subkeyCurve)); } } if (d->expirationDate.isValid()) { keyParameters.push_back(serialize("Expire-Date", serialize(d->expirationDate))); } if (!d->name.isEmpty()) { keyParameters.push_back(serialize("Name-Real", d->name)); } if (!d->dn.isEmpty()) { keyParameters.push_back(serialize("Name-DN", d->dn)); } std::transform(std::cbegin(d->emailAdresses), std::cend(d->emailAdresses), std::back_inserter(keyParameters), [this](const auto &email) { return serialize("Name-Email", (d->protocol == CMS) ? encodeEmail(email) : email); }); std::transform(std::cbegin(d->domainNames), std::cend(d->domainNames), std::back_inserter(keyParameters), [](const auto &domain) { return serialize("Name-DNS", encodeDomainName(domain)); }); std::transform(std::cbegin(d->uris), std::cend(d->uris), std::back_inserter(keyParameters), [](const auto &uri) { return serialize("Name-URI", uri); }); keyParameters.push_back(QLatin1String("")); return keyParameters.join(QLatin1Char('\n')); } diff --git a/src/utils/keys.cpp b/src/utils/keys.cpp index 2322e076b..a99d1a026 100644 --- a/src/utils/keys.cpp +++ b/src/utils/keys.cpp @@ -1,144 +1,143 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/keys.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 "keys.h" #include #include #include -#include // needed for GPGME_VERSION_NUMBER #include #include #include namespace { bool isLastValidUserID(const GpgME::UserID &userId) { if (Kleo::isRevokedOrExpired(userId)) { return false; } const auto userIds = userId.parent().userIDs(); const int numberOfValidUserIds = std::count_if(std::begin(userIds), std::end(userIds), [](const auto &u) { return !Kleo::isRevokedOrExpired(u); }); return numberOfValidUserIds == 1; } bool hasValidUserID(const GpgME::Key &key) { return Kleo::any_of(key.userIDs(), [](const auto &u) { return !Kleo::isRevokedOrExpired(u); }); } } bool Kleo::isSelfSignature(const GpgME::UserID::Signature &signature) { return !qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID()); } bool Kleo::isRevokedOrExpired(const GpgME::UserID &userId) { if (userId.isRevoked() || userId.parent().isExpired()) { return true; } const auto sigs = userId.signatures(); std::vector selfSigs; std::copy_if(std::begin(sigs), std::end(sigs), std::back_inserter(selfSigs), &Kleo::isSelfSignature); std::sort(std::begin(selfSigs), std::end(selfSigs)); // check the most recent signature const auto sig = !selfSigs.empty() ? selfSigs.back() : GpgME::UserID::Signature{}; return !sig.isNull() && (sig.isRevokation() || sig.isExpired()); } bool Kleo::canCreateCertifications(const GpgME::Key &key) { return key.canCertify() && canBeUsedForSecretKeyOperations(key); } bool Kleo::canBeCertified(const GpgME::Key &key) { return key.protocol() == GpgME::OpenPGP // && !key.isBad() // && hasValidUserID(key); } bool Kleo::canBeUsedForSecretKeyOperations(const GpgME::Key &key) { #if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2 // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available return key.subkey(0).isSecret(); #else // older versions of GpgME did not always set the secret flag for card keys return key.subkey(0).isSecret() || key.subkey(0).isCardKey(); #endif } bool Kleo::canRevokeUserID(const GpgME::UserID &userId) { return (!userId.isNull() // && userId.parent().protocol() == GpgME::OpenPGP && !isLastValidUserID(userId)); } bool Kleo::isSecretKeyStoredInKeyRing(const GpgME::Key &key) { return key.subkey(0).isSecret() && !key.subkey(0).isCardKey(); } bool Kleo::userHasCertificationKey() { const auto secretKeys = KeyCache::instance()->secretKeys(); return Kleo::any_of(secretKeys, [](const auto &k) { return (k.protocol() == GpgME::OpenPGP) && canCreateCertifications(k); }); } Kleo::CertificationRevocationFeasibility Kleo::userCanRevokeCertification(const GpgME::UserID::Signature &certification) { const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID()); const bool isSelfSignature = qstrcmp(certification.parent().parent().keyID(), certification.signerKeyID()) == 0; if (!certificationKey.hasSecret()) { return CertificationNotMadeWithOwnKey; } else if (isSelfSignature) { return CertificationIsSelfSignature; } else if (certification.isRevokation()) { return CertificationIsRevocation; } else if (certification.isExpired()) { return CertificationIsExpired; } else if (certification.isInvalid()) { return CertificationIsInvalid; } else if (!canCreateCertifications(certificationKey)) { return CertificationKeyNotAvailable; } return CertificationCanBeRevoked; } bool Kleo::userCanRevokeCertifications(const GpgME::UserID &userId) { if (userId.numSignatures() == 0) { qCWarning(KLEOPATRA_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available"; } return Kleo::any_of(userId.signatures(), [](const auto &certification) { return userCanRevokeCertification(certification) == CertificationCanBeRevoked; }); } bool Kleo::userIDBelongsToKey(const GpgME::UserID &userID, const GpgME::Key &key) { return !qstricmp(userID.parent().primaryFingerprint(), key.primaryFingerprint()); } diff --git a/src/utils/tags.cpp b/src/utils/tags.cpp index 180dae6b4..c586138be 100644 --- a/src/utils/tags.cpp +++ b/src/utils/tags.cpp @@ -1,65 +1,64 @@ /* utils/tags.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "tags.h" #include "tagspreferences.h" #include "kleopatra_debug.h" #include -#include #include using namespace Kleo; bool Tags::tagsEnabled() { return TagsPreferences().useTags(); } void Tags::enableTags() { TagsPreferences().setUseTags(true); KeyCache::mutableInstance()->enableRemarks(true); } GpgME::Key Tags::tagKey() { const auto tagKeyFpr = TagsPreferences().tagKey(); GpgME::Key key; if (tagKeyFpr.isEmpty()) { return key; } key = KeyCache::instance()->findByKeyIDOrFingerprint(tagKeyFpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Failed to find tag key: " << tagKeyFpr; return key; } return key; } std::vector Tags::tagKeys() { std::vector ret; for (const auto &key: KeyCache::instance()->keys()) { if (key.isNull() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid() || key.protocol() != GpgME::OpenPGP) { continue; } if (key.ownerTrust() >= GpgME::Key::Full) { ret.push_back(key); } } return ret; } void Tags::setTagKey(const GpgME::Key &key) { TagsPreferences().setTagKey(key.isNull() ? QString() : QString::fromLatin1(key.primaryFingerprint())); } diff --git a/src/utils/userinfo.cpp b/src/utils/userinfo.cpp index 5693b6dfa..59fc0a8d8 100644 --- a/src/utils/userinfo.cpp +++ b/src/utils/userinfo.cpp @@ -1,84 +1,83 @@ /* utils/userinfo.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 "userinfo.h" -#include #ifdef Q_OS_WIN #include "userinfo_win_p.h" #endif #include #include namespace { enum UserInfoDetail { UserInfoName, UserInfoEmailAddress }; static QString env_get_user_name(UserInfoDetail detail) { const auto var = qEnvironmentVariable("EMAIL"); if (!var.isEmpty()) { QString name, addrspec, comment; const auto result = KEmailAddress::splitAddress (var, name, addrspec, comment); if (result == KEmailAddress::AddressOk) { return (detail == UserInfoEmailAddress ? addrspec : name); } } return QString (); } } QString Kleo::userFullName() { const KEMailSettings e; auto name = e.getSetting(KEMailSettings::RealName); #ifdef Q_OS_WIN if (name.isEmpty()) { name = win_get_user_name(NameDisplay); } if (name.isEmpty()) { name = win_get_user_name(NameUnknown); } #endif if (name.isEmpty()) { name = env_get_user_name(UserInfoName); } return name; } QString Kleo::userEmailAddress() { const KEMailSettings e; auto mbox = e.getSetting(KEMailSettings::EmailAddress); #ifdef Q_OS_WIN if (mbox.isEmpty()) { mbox = win_get_user_name(NameUserPrincipal); } #endif if (mbox.isEmpty()) { mbox = env_get_user_name(UserInfoEmailAddress); } return mbox; } bool Kleo::userIsElevated() { #ifdef Q_OS_WIN static bool ret = win_user_is_elevated(); return ret; #else return false; #endif } diff --git a/src/view/openpgpkeycardwidget.h b/src/view/openpgpkeycardwidget.h index 1d6f2c6d2..f81e6cc35 100644 --- a/src/view/openpgpkeycardwidget.h +++ b/src/view/openpgpkeycardwidget.h @@ -1,53 +1,52 @@ /* view/openpgpkeycardwidget.h 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 */ #pragma once -#include #include #include namespace Kleo { namespace SmartCard { class Card; } class OpenPGPKeyCardWidget: public QWidget { Q_OBJECT public: enum Action { NoAction = 0x00, CreateCSR = 0x01, AllActions = CreateCSR, }; Q_DECLARE_FLAGS(Actions, Action) explicit OpenPGPKeyCardWidget(QWidget *parent = nullptr); ~OpenPGPKeyCardWidget() override; void setAllowedActions(Actions actions); public Q_SLOTS: void update(const SmartCard::Card *card); Q_SIGNALS: void createCSRRequested(const std::string &keyRef); private: class Private; std::unique_ptr d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(OpenPGPKeyCardWidget::Actions) } diff --git a/src/view/pgpcardwidget.h b/src/view/pgpcardwidget.h index dde8b8b8b..e03a7cb02 100644 --- a/src/view/pgpcardwidget.h +++ b/src/view/pgpcardwidget.h @@ -1,71 +1,70 @@ /* view/pgpcardwiget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include "commands/changepincommand.h" -#include #include #include #include class QLabel; class QPushButton; namespace Kleo { class GenCardKeyDialog; class OpenPGPKeyCardWidget; namespace SmartCard { struct KeyPairInfo; class OpenPGPCard; } // namespace SmartCard class PGPCardWidget: public QWidget { Q_OBJECT public: explicit PGPCardWidget(QWidget *parent = nullptr); void setCard(const SmartCard::OpenPGPCard* card); void doGenKey(GenCardKeyDialog *dlg); void genKeyDone(const GpgME::Error &err, const std::string &backup); public Q_SLOTS: void genkeyRequested(); void changeNameRequested(); void changeNameResult(const GpgME::Error &err); void changeUrlRequested(); void changeUrlResult(const GpgME::Error &err); void createKeyFromCardKeys(); void createCSR(const std::string &keyref); private: void doChangePin(const std::string &keyRef, Commands::ChangePinCommand::ChangePinMode mode = Commands::ChangePinCommand::NormalMode); private: QLabel *mSerialNumber = nullptr, *mCardHolderLabel = nullptr, *mVersionLabel = nullptr, *mUrlLabel = nullptr; QPushButton *mKeyForCardKeysButton = nullptr; OpenPGPKeyCardWidget *mKeysWidget = nullptr; QString mUrl; bool mCardIsEmpty = false; bool mIs21 = false; std::string mRealSerial; }; } // namespace Kleo diff --git a/src/view/welcomewidget.cpp b/src/view/welcomewidget.cpp index 8c0c6818e..f9d30ccc4 100644 --- a/src/view/welcomewidget.cpp +++ b/src/view/welcomewidget.cpp @@ -1,218 +1,217 @@ /* view/welcomewidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "welcomewidget.h" #include "htmllabel.h" #include #include #include #include #include #include -#include #include #include "commands/importcertificatefromfilecommand.h" #include "commands/newopenpgpcertificatecommand.h" #include #include #include static const QString templ = QStringLiteral( "

    %1

    " // Welcome "

    %2

    %3

    " // Intro + Explanation "
    • %4
    • %5
    " // "

    %6

    " // More info ""); using namespace Kleo; namespace { /** * A tool button that can be activated with the Enter and Return keys additionally to the Space key. */ class ToolButton : public QToolButton { Q_OBJECT public: using QToolButton::QToolButton; protected: void keyPressEvent(QKeyEvent *e) override { switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: { // forward as key press of Key_Select to QToolButton QKeyEvent alternateEvent{e->type(), Qt::Key_Select, e->modifiers(), e->nativeScanCode(), e->nativeVirtualKey(), e->nativeModifiers(), e->text(), e->isAutoRepeat(), static_cast(e->count())}; QToolButton::keyPressEvent(&alternateEvent); if (!alternateEvent.isAccepted()) { e->ignore(); } break; } default: QToolButton::keyPressEvent(e); } } void keyReleaseEvent(QKeyEvent *e) override { switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: { // forward as key release of Key_Select to QToolButton QKeyEvent alternateEvent{e->type(), Qt::Key_Select, e->modifiers(), e->nativeScanCode(), e->nativeVirtualKey(), e->nativeModifiers(), e->text(), e->isAutoRepeat(), static_cast(e->count())}; QToolButton::keyReleaseEvent(&alternateEvent); if (!alternateEvent.isAccepted()) { e->ignore(); } break; } default: QToolButton::keyReleaseEvent(e); } } }; } class WelcomeWidget::Private { public: Private(WelcomeWidget *qq): q(qq) { auto vLay = new QVBoxLayout(q); auto hLay = new QHBoxLayout; const QString welcome = i18nc("%1 is version", "Welcome to Kleopatra %1", #ifdef Q_OS_WIN Kleo::gpg4winVersion()); #else QStringLiteral(KLEOPATRA_VERSION_STRING)); #endif const QString introduction = i18n("Kleopatra is a front-end for the crypto software GnuPG."); const QString keyExplanation = i18n("For most actions you need either a public key (certificate) or your own private key."); const QString privateKeyExplanation = i18n("The private key is needed to decrypt or sign."); const QString publicKeyExplanation = i18n("The public key can be used by others to verify your identity or encrypt to you."); const QString wikiUrl = i18nc("More info about public key cryptography, please link to your local version of Wikipedia", "https://en.wikipedia.org/wiki/Public-key_cryptography"); const QString learnMore = i18nc("%1 is link a wiki article", "You can learn more about this on Wikipedia.", wikiUrl); const auto labelText = templ.arg(welcome).arg(introduction).arg(keyExplanation).arg(privateKeyExplanation).arg(publicKeyExplanation).arg(learnMore); mLabel = new HtmlLabel{labelText, q}; mLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); mLabel->setOpenExternalLinks(true); auto genKeyAction = new QAction(q); genKeyAction->setText(i18n("New Key Pair...")); genKeyAction->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-add"))); auto importAction = new QAction(q); importAction->setText(i18n("Import...")); importAction->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import"))); connect(importAction, &QAction::triggered, q, [this] () { import(); }); connect(genKeyAction, &QAction::triggered, q, [this] () { generate(); }); mGenerateBtn = new ToolButton{q}; mGenerateBtn->setDefaultAction(genKeyAction); mGenerateBtn->setIconSize(QSize(64, 64)); mGenerateBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); const auto generateBtnDescription = kxi18nc("@info", "Create a new OpenPGP key pair." "To create an S/MIME certificate request use " "New S/MIME Certification Request " "from the File menu instead."); mGenerateBtn->setToolTip(generateBtnDescription.toString()); mGenerateBtn->setAccessibleDescription(generateBtnDescription.toString(Kuit::PlainText)); KConfigGroup restrictions(KSharedConfig::openConfig(), "KDE Action Restrictions"); mGenerateBtn->setEnabled(restrictions.readEntry("action/file_new_certificate", true)); mImportBtn = new ToolButton{q}; mImportBtn->setDefaultAction(importAction); mImportBtn->setIconSize(QSize(64, 64)); mImportBtn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); const auto importBtnDescription = kxi18nc("@info", "Import certificate from a file." "To import from a public keyserver use Lookup on Server instead."); mImportBtn->setToolTip(importBtnDescription.toString()); mImportBtn->setAccessibleDescription(importBtnDescription.toString(Kuit::PlainText)); mImportBtn->setEnabled(restrictions.readEntry("action/file_import_certificate", true)); auto btnLayout = new QHBoxLayout; btnLayout->addStretch(-1); btnLayout->addWidget(mGenerateBtn); btnLayout->addWidget(mImportBtn); btnLayout->addStretch(-1); vLay->addStretch(-1); vLay->addLayout(hLay); vLay->addLayout(btnLayout); vLay->addStretch(-1); hLay->addStretch(-1); hLay->addWidget(mLabel); hLay->addStretch(-1); } void import() { mImportBtn->setEnabled(false); auto cmd = new Kleo::ImportCertificateFromFileCommand(); cmd->setParentWidget(q); QObject::connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this]() { mImportBtn->setEnabled(true); }); cmd->start(); } void generate() { mGenerateBtn->setEnabled(false); auto cmd = new NewOpenPGPCertificateCommand; cmd->setParentWidget(q); QObject::connect(cmd, &NewOpenPGPCertificateCommand::finished, q, [this]() { mGenerateBtn->setEnabled(true); }); cmd->start(); } WelcomeWidget *const q; HtmlLabel *mLabel = nullptr; ToolButton *mGenerateBtn = nullptr; ToolButton *mImportBtn = nullptr; }; WelcomeWidget::WelcomeWidget (QWidget *parent): QWidget(parent), d(new Private(this)) { } void WelcomeWidget::focusFirstChild(Qt::FocusReason reason) { d->mLabel->setFocus(reason); } #include "welcomewidget.moc"