diff --git a/src/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp index 76f4b92b6..44ba336ac 100644 --- a/src/commands/revokecertificationcommand.cpp +++ b/src/commands/revokecertificationcommand.cpp @@ -1,357 +1,359 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/revokecertificationcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "revokecertificationcommand.h" #include "command_p.h" #include "exportopenpgpcertstoservercommand.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; using namespace QGpgME; namespace { enum class InputType { Key, UserIDs, Certifications, }; struct CertificationData { UserID userId; Key certificationKey; }; static std::vector getCertificationKeys(const GpgME::UserID &userId) { std::vector keys; if (userId.numSignatures() == 0) { qCWarning(KLEOPATRA_LOG) << __func__ << "- Error: Signatures of user ID" << QString::fromUtf8(userId.id()) << "not available"; return keys; } std::vector revokableCertifications; Kleo::copy_if(userId.signatures(), std::back_inserter(revokableCertifications), [](const auto &certification) { return userCanRevokeCertification(certification) == CertificationCanBeRevoked; }); Kleo::transform(revokableCertifications, std::back_inserter(keys), [](const auto &certification) { return KeyCache::instance()->findByKeyIDOrFingerprint(certification.signerKeyID()); }); return keys; } static bool confirmRevocations(QWidget *parent, const std::vector &certifications) { KMessageBox::ButtonCode answer; if (certifications.size() == 1) { const auto [userId, certificationKey] = certifications.front(); const auto message = xi18nc("@info", "You are about to revoke the certification of user ID%1made with the key%2.", QString::fromUtf8(userId.id()), Formatting::formatForComboBox(certificationKey)); answer = KMessageBox::questionYesNo(parent, message, i18nc("@title:window", "Confirm Revocation"), KGuiItem{i18n("Revoke Certification")}, KStandardGuiItem::cancel()); } else { QStringList l; Kleo::transform(certifications, std::back_inserter(l), [](const auto &c) { return i18n("User ID '%1' certified with key %2", QString::fromUtf8(c.userId.id()), Formatting::formatForComboBox(c.certificationKey)); }); const auto message = i18np("You are about to revoke the following certification:", // "You are about to revoke the following %1 certifications:", certifications.size()); answer = KMessageBox::questionYesNoList(parent, message, l, i18nc("@title:window", "Confirm Revocation"), KGuiItem{i18n("Revoke Certifications")}, KStandardGuiItem::cancel()); } return answer == KMessageBox::Yes; } } class RevokeCertificationCommand::Private : public Command::Private { friend class ::Kleo::Commands::RevokeCertificationCommand; RevokeCertificationCommand *q_func() const { return static_cast(q); } public: Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c = nullptr); void init(); private: std::vector getCertificationsToRevoke(); void scheduleNextRevocation(); QGpgME::QuickJob *createJob(); void slotResult(const Error &err); private: InputType inputType = InputType::Key; Key certificationTarget; std::vector uids; std::vector certificationsToRevoke; std::vector completedRevocations; QPointer job; }; RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() { return static_cast(d.get()); } const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RevokeCertificationCommand::Private::Private(InputType i, RevokeCertificationCommand *qq, KeyListController *c) : Command::Private{qq, c} , inputType{i} { } void RevokeCertificationCommand::Private::init() { const std::vector keys_ = keys(); if (keys_.size() != 1) { qCWarning(KLEOPATRA_LOG) << q << "Expected exactly one key, but got" << keys_.size(); return; } if (keys_.front().protocol() != GpgME::OpenPGP) { qCWarning(KLEOPATRA_LOG) << q << "Expected OpenPGP key, but got" << keys_.front().protocolAsString(); return; } certificationTarget = keys_.front(); } std::vector RevokeCertificationCommand::Private::getCertificationsToRevoke() { if (inputType != InputType::Certifications) { // ensure that the certifications of the key have been loaded if (certificationTarget.userID(0).numSignatures() == 0) { certificationTarget.update(); } // build list of user IDs and revokable certifications const auto userIDsToConsider = (inputType == InputType::Key) ? certificationTarget.userIDs() : uids; for (const auto &userId : userIDsToConsider) { Kleo::transform(getCertificationKeys(userId), std::back_inserter(certificationsToRevoke), [userId](const auto &k) { return CertificationData{userId, k}; }); } } Kleo::erase_if(certificationsToRevoke, [](const auto &c) { return c.certificationKey.isNull(); }); return certificationsToRevoke; } void RevokeCertificationCommand::Private::scheduleNextRevocation() { if (!certificationsToRevoke.empty()) { const auto nextCertification = certificationsToRevoke.back(); job = createJob(); if (!job) { qCWarning(KLEOPATRA_LOG) << q << "Failed to create job"; finished(); return; } job->startRevokeSignature(certificationTarget, nextCertification.certificationKey, {nextCertification.userId}); } else { const auto message = xi18ncp("@info", "The certification has been revoked successfully." "Do you want to publish the revocation?", "%1 certifications have been revoked successfully." "Do you want to publish the revocations?", completedRevocations.size()); const auto yesButton = KGuiItem{i18ncp("@action:button", "Publish Revocation", "Publish Revocations", completedRevocations.size()), QIcon::fromTheme(QStringLiteral("view-certificate-export-server"))}; const auto answer = KMessageBox::questionYesNo(parentWidgetOrView(), message, i18nc("@title:window", "Confirm Publication"), yesButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::Dangerous); if (answer == KMessageBox::Yes) { const auto cmd = new ExportOpenPGPCertsToServerCommand{certificationTarget}; cmd->start(); } finished(); } } void RevokeCertificationCommand::Private::slotResult(const Error &err) { if (err.isCanceled()) { canceled(); return; } if (err) { const auto failedRevocation = certificationsToRevoke.back(); error(xi18nc("@info", "The revocation of the certification of user ID%1made with key%2failed:" "%3", Formatting::prettyNameAndEMail(failedRevocation.userId), Formatting::formatForComboBox(failedRevocation.certificationKey), QString::fromUtf8(err.asString()))); finished(); return; } completedRevocations.push_back(certificationsToRevoke.back()); certificationsToRevoke.pop_back(); scheduleNextRevocation(); } QGpgME::QuickJob *RevokeCertificationCommand::Private::createJob() { const auto j = QGpgME::openpgp()->quickJob(); if (j) { connect(j, &Job::progress, q, &Command::progress); connect(j, &QGpgME::QuickJob::result, q, [this](const GpgME::Error &error) { slotResult(error); }); } return j; } RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c) : Command{v, new Private{InputType::Key, this, c}} { d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::Key &key) : Command{key, new Private{InputType::Key, this}} { d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid) : Command{uid.parent(), new Private{InputType::UserIDs, this}} { std::vector(1, uid).swap(d->uids); d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const std::vector &uids) : Command{uids.empty() ? Key{} : uids.front().parent(), new Private{InputType::UserIDs, this}} { d->uids = uids; d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature) : Command{signature.parent().parent(), new Private{InputType::Certifications, this}} { if (!signature.isNull()) { const Key certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()); d->certificationsToRevoke = {{signature.parent(), certificationKey}}; } d->init(); } RevokeCertificationCommand::~RevokeCertificationCommand() { qCDebug(KLEOPATRA_LOG) << this << __func__; } // static bool RevokeCertificationCommand::isSupported() { return engineInfo(GpgEngine).engineVersion() >= "2.2.24"; } void RevokeCertificationCommand::doStart() { if (d->certificationTarget.isNull()) { d->finished(); return; } if (!Kleo::all_of(d->uids, userIDBelongsToKey(d->certificationTarget))) { qCWarning(KLEOPATRA_LOG) << this << "User ID <-> Key mismatch!"; d->finished(); return; } const auto certificationsToRevoke = d->getCertificationsToRevoke(); if (certificationsToRevoke.empty()) { switch (d->inputType) { case InputType::Key: d->information(i18n("You cannot revoke any certifications of this key.")); break; case InputType::UserIDs: d->information(i18np("You cannot revoke any certifications of this user ID.", // "You cannot revoke any certifications of these user IDs.", d->uids.size())); break; case InputType::Certifications: d->information(i18n("You cannot revoke this certification.")); break; } d->finished(); return; } if (!confirmRevocations(d->parentWidgetOrView(), certificationsToRevoke)) { d->canceled(); return; } d->scheduleNextRevocation(); } void RevokeCertificationCommand::doCancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; if (d->job) { d->job->slotCancel(); } } #undef d #undef q + +#include "moc_revokecertificationcommand.cpp"