diff --git a/src/commands/certifygroupcommand.cpp b/src/commands/certifygroupcommand.cpp index 7c1d7a6a2..5e05074f1 100644 --- a/src/commands/certifygroupcommand.cpp +++ b/src/commands/certifygroupcommand.cpp @@ -1,311 +1,328 @@ /* commands/certifygroupcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2023 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "certifygroupcommand.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; namespace { struct CertificationResultData { std::vector userIds; GpgME::Error error; }; } class CertifyGroupCommand::Private : public Command::Private { friend class ::Kleo::CertifyGroupCommand; CertifyGroupCommand *q_func() const { return static_cast(q); } public: explicit Private(CertifyGroupCommand *qq); ~Private() override; void start(); private: void showDialog(); void certifyCertificates(); void startNextCertification(); void createJob(); void slotResult(const Error &err); void wrapUp(); private: KeyGroup group; std::vector certificates; QPointer dialog; std::vector userIdsToCertify; struct { Key certificationKey; QDate expirationDate; QString tags; bool exportable = false; bool sendToServer = false; } certificationOptions; struct { std::vector userIds; } jobData; QPointer job; std::vector results; }; CertifyGroupCommand::Private *CertifyGroupCommand::d_func() { return static_cast(d.get()); } const CertifyGroupCommand::Private *CertifyGroupCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() CertifyGroupCommand::Private::Private(CertifyGroupCommand *qq) : Command::Private(qq) { } CertifyGroupCommand::Private::~Private() = default; void CertifyGroupCommand::Private::start() { if (!group.isNull()) { const auto &groupKeys = group.keys(); certificates = std::vector(groupKeys.begin(), groupKeys.end()); } if (certificates.empty()) { finished(); return; } if (!allKeysHaveProtocol(certificates, GpgME::OpenPGP)) { const auto title = i18nc("@title:window", "Group Cannot Be Certified"); const auto message = i18nc("@info", "This group contains S/MIME certificates which cannot be certified."); information(message, title); finished(); return; } showDialog(); } void CertifyGroupCommand::Private::showDialog() { dialog = new CertifyCertificateDialog; dialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(dialog); connect(dialog, &QDialog::accepted, q, [this]() { certifyCertificates(); }); connect(dialog, &QDialog::rejected, q, [this]() { canceled(); }); if (!group.isNull()) { dialog->setGroupName(group.name()); } dialog->setCertificatesToCertify(certificates); dialog->show(); } void CertifyGroupCommand::Private::certifyCertificates() { userIdsToCertify = dialog->selectedUserIDs(); if (userIdsToCertify.empty()) { canceled(); return; } certificationOptions.certificationKey = dialog->selectedSecretKey(); certificationOptions.expirationDate = dialog->expirationDate(); certificationOptions.tags = dialog->tags(); certificationOptions.exportable = dialog->exportableCertificationSelected(); certificationOptions.sendToServer = dialog->sendToServer(); startNextCertification(); } void CertifyGroupCommand::Private::startNextCertification() { Q_ASSERT(!userIdsToCertify.empty()); const auto nextKey = userIdsToCertify.front().parent(); // for now we only deal with primary user IDs jobData.userIds = {userIdsToCertify.front()}; userIdsToCertify.erase(userIdsToCertify.begin()); const std::vector userIdIndexes = {0}; createJob(); job->setUserIDsToSign(userIdIndexes); if (const Error err = job->start(nextKey)) { QMetaObject::invokeMethod( q, [this, err]() { slotResult(err); }, Qt::QueuedConnection); } } void CertifyGroupCommand::Private::createJob() { Q_ASSERT(!job); std::unique_ptr newJob{QGpgME::openpgp()->signKeyJob()}; newJob->setDupeOk(true); newJob->setSigningKey(certificationOptions.certificationKey); newJob->setExportable(certificationOptions.exportable); if (!certificationOptions.tags.isEmpty()) { // do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142) newJob->setRemark(certificationOptions.tags); } if (!certificationOptions.expirationDate.isNull()) { newJob->setExpirationDate(certificationOptions.expirationDate); } connect(newJob.get(), &QGpgME::SignKeyJob::result, q, [this](const GpgME::Error &result) { slotResult(result); }); job = newJob.release(); } void CertifyGroupCommand::Private::slotResult(const Error &err) { results.push_back({ jobData.userIds, err, }); if (err.isCanceled()) { finished(); return; } - if (err) { - // for now we only deal with primary user IDs - Q_ASSERT(jobData.userIds.size() == 1); - const Key key = jobData.userIds.front().parent(); - const QString message = i18nc("@info", "

Certifying the certificate %1 failed.

", Formatting::formatForComboBox(key)) - + xi18nc("@info", "Error: %1", Formatting::errorAsString(err)); - error(message); - } if (!userIdsToCertify.empty()) { job.clear(); jobData.userIds.clear(); startNextCertification(); return; } wrapUp(); } -static QString resultSummary(int successCount, int totalCount) +static QString resultSummary(const std::vector &results) { + Q_ASSERT(!results.empty()); + + const int totalCount = results.size(); + const int successCount = Kleo::count_if(results, [](const auto &result) { + return !result.error; + }); + if (successCount == totalCount) { return i18nc("@info", "All certificates were certified successfully."); } if (successCount == 0) { - return i18nc("@info", "The certification of all certificates failed."); + // we assume that all attempted certifications failed for the same reason + return xi18nc("@info", + "The certification of all certificates failed." + "Error: %1", + Formatting::errorAsString(results.front().error)); } return i18ncp("@info", // "1 of %2 certificates was certified successfully.", "%1 of %2 certificates were certified successfully.", successCount, totalCount); } void CertifyGroupCommand::Private::wrapUp() { Q_ASSERT(userIdsToCertify.empty()); + Q_ASSERT(!results.empty()); const int successCount = Kleo::count_if(results, [](const auto &result) { return !result.error; }); const bool sendToServer = (successCount > 0) && certificationOptions.exportable && certificationOptions.sendToServer; - QString message = QLatin1String{"

"} + resultSummary(successCount, results.size()) + QLatin1String{"

"}; + QString message = QLatin1String{"

"} + resultSummary(results) + QLatin1String{"

"}; if (sendToServer) { message += i18nc("@info", "

Next the certified certificates will be uploaded to the configured certificate directory.

"); } + const auto failedUserIdsInfo = std::accumulate(results.cbegin(), results.cend(), QStringList{}, [](auto failedUserIds, const auto &result) { + if (result.error) { + failedUserIds.push_back(i18nc("A user ID (an error description)", + "%1 (%2)", + Formatting::formatForComboBox(result.userIds.front().parent()), + Formatting::errorAsString(result.error))); + } + return failedUserIds; + }); + if (successCount > 0) { - information(message, i18nc("@title:window", "Certification Completed")); + if (failedUserIdsInfo.size() > 0) { + message += i18nc("@info", "

Certifying the following certificates failed:

"); + } + informationList(message, failedUserIdsInfo, i18nc("@title:window", "Certification Completed")); } else { error(message); } if (sendToServer) { const auto certificatesToSendToServer = std::accumulate(results.cbegin(), results.cend(), std::vector{}, [](auto keys, const auto &result) { if (!result.error) { keys.push_back(result.userIds.front().parent()); } return keys; }); const auto cmd = new ExportOpenPGPCertsToServerCommand(certificatesToSendToServer); cmd->start(); } if (!certificationOptions.tags.isEmpty()) { Tags::enableTags(); } finished(); } CertifyGroupCommand::CertifyGroupCommand(const KeyGroup &group) : Command{new Private{this}} { d->group = group; } CertifyGroupCommand::~CertifyGroupCommand() = default; void CertifyGroupCommand::doStart() { d->start(); } void CertifyGroupCommand::doCancel() { if (d->dialog) { d->dialog->close(); } if (d->job) { d->job->slotCancel(); } } #undef d #undef q #include "moc_certifygroupcommand.cpp" diff --git a/src/commands/command_p.h b/src/commands/command_p.h index 3509d0b91..c5e364a0d 100644 --- a/src/commands/command_p.h +++ b/src/commands/command_p.h @@ -1,129 +1,141 @@ /* -*- 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 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(); doFinish(); 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 informationList(const QString &text, + const QStringList &strlist, + const QString &title = {}, + const QString &dontShowAgainName = {}, + KMessageBox::Options options = KMessageBox::Notify) const + { + if (parentWId_) { + KMessageBox::informationListWId(parentWId_, text, strlist, title, dontShowAgainName, options); + } else { + KMessageBox::informationList(parentWidgetOrView(), text, strlist, title, dontShowAgainName, options); + } + } void applyWindowID(QWidget *w) const { q->applyWindowID(w); } private: virtual void doFinish() { } private: bool autoDelete : 1; bool warnWhenRunningAtShutdown : 1; std::vector keys_; QPointer view_; QPointer parentWidget_; WId parentWId_ = 0; QPointer controller_; };