diff --git a/src/commands/revokekeycommand.cpp b/src/commands/revokekeycommand.cpp index 09ab3b0f6..185ec9169 100644 --- a/src/commands/revokekeycommand.cpp +++ b/src/commands/revokekeycommand.cpp @@ -1,246 +1,313 @@ /* -*- 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 "kleopatra_debug.h" + #include "command_p.h" +#include "commands/exportopenpgpcertstoservercommand.h" #include "dialogs/revokekeydialog.h" #include "revokekeycommand.h" #include +#include +#include -#include - +#include +#include #include -#include "kleopatra_debug.h" -#include +#include + +#include +#include + +#include +#include +#include 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(); std::unique_ptr startJob(); void onJobResult(const Error &err); void showError(const Error &err); private: Key key; QPointer dialog; QPointer job; }; 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() { if (job) { job->slotCancel(); } job.clear(); } 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() { auto revokeJob = startJob(); if (!revokeJob) { finished(); return; } job = revokeJob.release(); } 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; } } 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::jobProgress, 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; } void RevokeKeyCommand::Private::onJobResult(const Error &err) { + if (err.isCanceled()) { + finished(); + return; + } + if (err) { showError(err); finished(); return; } - if (!err.isCanceled()) { - information(i18nc("@info", "The key was revoked successfully."), i18nc("@title:window", "Key Revoked")); - } - finished(); + auto job = QGpgME::openpgp()->publicKeyExportJob(true); + job->setExportFlags(GPGME_EXPORT_MODE_MINIMAL); + + connect(job, &QGpgME::ExportJob::result, q, [this](const auto &error, const auto &data) { + if (error.isCanceled()) { + finished(); + return; + } + + if (error) { + information(i18nc("@info", "The certificate was revoked successfully.")); + finished(); + return; + } + + auto name = Formatting::prettyName(key); + if (name.isEmpty()) { + name = Formatting::prettyEMail(key); + } + + auto filename = QStringLiteral("%1_%2_public_revoked.asc").arg(name, Formatting::prettyKeyID(key.shortKeyID())); + const auto dir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + if (QFileInfo::exists(QStringLiteral("%1/%2").arg(dir, filename))) { + filename = KFileUtils::suggestName(QUrl::fromLocalFile(dir), filename); + } + const auto path = QStringLiteral("%1/%2").arg(dir, filename); + QFile file(path); + file.open(QIODevice::WriteOnly); + file.write(data); + file.close(); + + if (haveKeyserverConfigured()) { + const auto code = KMessageBox::questionTwoActions( + parentWidgetOrView(), + xi18nc("@info", + "The certificate was revoked successfully.The revoked certificate was exported to " + "%1To make sure that your contacts receive the revoked certificate, " + "you can upload it to a keyserver now.", + path), + i18nc("@title:window", "Key Revoked"), + KGuiItem(i18nc("@action:button Upload a certificate", "Upload"), QIcon::fromTheme(QStringLiteral("view-certificate-export-server"))), + KStandardGuiItem::close()); + if (code == KMessageBox::PrimaryAction) { + auto const cmd = new Commands::ExportOpenPGPCertsToServerCommand(key); + cmd->start(); + } + } else { + information(xi18nc("@info", + "The certificate was revoked successfully.The revoked certificate was exported to " + "%1", + path)); + } + finished(); + }); + job->start({QString::fromLatin1(key.primaryFingerprint())}); } void RevokeKeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the revocation:" "%1", Formatting::errorAsString(err)), 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"