Page MenuHome GnuPG

No OneTemporary

diff --git a/src/commands/exportopenpgpcertstoservercommand.cpp b/src/commands/exportopenpgpcertstoservercommand.cpp
index 99e7973ce..dd407bcb6 100644
--- a/src/commands/exportopenpgpcertstoservercommand.cpp
+++ b/src/commands/exportopenpgpcertstoservercommand.cpp
@@ -1,182 +1,182 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportopenpgpcertstoservercommand.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 <config-kleopatra.h>
#include "exportopenpgpcertstoservercommand.h"
#include "command_p.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyHelpers>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(KeyListController *c)
: GnuPGProcessCommand(c)
{
}
ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(QAbstractItemView *v, KeyListController *c)
: GnuPGProcessCommand(v, c)
{
}
ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const Key &key)
: GnuPGProcessCommand(key)
{
}
ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const std::vector<Key> &keys)
: GnuPGProcessCommand(keys)
{
}
ExportOpenPGPCertsToServerCommand::~ExportOpenPGPCertsToServerCommand() = default;
static bool confirmExport(const std::vector<Key> &pgpKeys, QWidget *parentWidget)
{
auto notCertifiedKeys = std::accumulate(pgpKeys.cbegin(), pgpKeys.cend(), QStringList{}, [](auto keyNames, const auto &key) {
const bool allValidUserIDsAreCertifiedByUser = std::ranges::all_of(key.userIDs(), [](const UserID &userId) {
return userId.isBad() || Kleo::userIDIsCertifiedByUser(userId);
});
if (!allValidUserIDsAreCertifiedByUser) {
keyNames.push_back(Formatting::formatForComboBox(key));
}
return keyNames;
});
if (!notCertifiedKeys.empty()) {
if (pgpKeys.size() == 1) {
const auto answer = KMessageBox::warningContinueCancel( //
parentWidget,
xi18nc("@info",
"<para>You haven't certified all valid user IDs of this certificate "
"with an exportable certification. People relying on your certifications "
"may not be able to verify the certificate.</para>"
"<para>Do you want to continue the export?</para>"),
i18nc("@title:window", "Confirm Certificate Export"),
KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", 1)},
KStandardGuiItem::cancel(),
QStringLiteral("confirm-upload-of-uncertified-keys"));
return answer == KMessageBox::Continue;
} else {
std::sort(notCertifiedKeys.begin(), notCertifiedKeys.end());
const auto answer = KMessageBox::warningContinueCancelList( //
parentWidget,
xi18nc("@info",
"<para>You haven't certified all valid user IDs of the certificates listed below "
"with exportable certifications. People relying on your certifications "
"may not be able to verify the certificates.</para>"
"<para>Do you want to continue the export?</para>"),
notCertifiedKeys,
i18nc("@title:window", "Confirm Certificate Export"),
KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", pgpKeys.size())},
KStandardGuiItem::cancel(),
QStringLiteral("confirm-upload-of-uncertified-keys"));
return answer == KMessageBox::Continue;
}
}
return true;
}
bool ExportOpenPGPCertsToServerCommand::preStartHook(QWidget *parent) const
{
if (!haveKeyserverConfigured()) {
d->error(i18ncp("@info",
"Exporting the certificate to a key server is not possible "
"because the usage of key servers has been disabled explicitly.",
"Exporting the certificates to a key server is not possible "
"because the usage of key servers has been disabled explicitly.",
d->keys().size()));
return false;
}
- if (!confirmExport(d->keys(), parent)) {
+ if (interactive() && !confirmExport(d->keys(), parent)) {
return false;
}
- return keyserver().startsWith(QLatin1StringView("ldap"))
+ return !interactive() || keyserver().startsWith(QLatin1StringView("ldap"))
|| KMessageBox::warningContinueCancel(
parent,
xi18nc("@info",
"<para>When OpenPGP certificates have been exported to a public directory server, "
"it is nearly impossible to remove them again.</para>"
"<para>Before exporting your certificate to a public directory server, make sure that you "
"have created a revocation certificate so you can revoke the certificate if it is compromised, lost, or you forgot the passphrase.</para>"
"<para>Are you sure you want to continue?</para>"),
i18nc("@title:window", "OpenPGP Certificate Export"),
KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", d->keys().size())},
KStandardGuiItem::cancel(),
QStringLiteral("warn-export-openpgp-nonrevocable"))
== KMessageBox::Continue;
}
QStringList ExportOpenPGPCertsToServerCommand::arguments() const
{
QStringList result;
result << gpgPath();
result << QStringLiteral("--send-keys");
const auto keys = d->keys();
for (const Key &key : keys) {
result << QLatin1StringView(key.primaryFingerprint());
}
return result;
}
QString ExportOpenPGPCertsToServerCommand::errorCaption() const
{
return i18nc("@title:window", "OpenPGP Certificate Export Error");
}
QString ExportOpenPGPCertsToServerCommand::successCaption() const
{
return i18nc("@title:window", "OpenPGP Certificate Export Finished");
}
QString ExportOpenPGPCertsToServerCommand::crashExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>The GPG process that tried to export OpenPGP certificates "
"ended prematurely because of an unexpected error.</para>"
"<para>Please check the output of <icode>%1</icode> for details.</para>",
args.join(QLatin1Char(' ')));
}
QString ExportOpenPGPCertsToServerCommand::errorExitMessage(const QStringList &args) const
{
// ki18n(" ") as initializer because initializing with empty string leads to
// (I18N_EMPTY_MESSAGE)
const auto errorLines = errorString().split(QLatin1Char{'\n'});
const auto errorText = std::accumulate(errorLines.begin(), errorLines.end(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &line) {
return kxi18nc("@info used for concatenating multiple lines of text with line breaks; most likely this shouldn't be translated", "%1<nl />%2")
.subs(temp)
.subs(line);
});
return xi18nc("@info",
"<para>An error occurred while trying to export OpenPGP certificates.</para> "
"<para>The output of <command>%1</command> was:<nl /><message>%2</message></para>",
args[0],
errorText);
}
QString ExportOpenPGPCertsToServerCommand::successMessage(const QStringList &) const
{
return i18nc("@info", "OpenPGP certificates exported successfully.");
}
#include "moc_exportopenpgpcertstoservercommand.cpp"
diff --git a/src/commands/gnupgprocesscommand.cpp b/src/commands/gnupgprocesscommand.cpp
index df621d3a9..069c45136 100644
--- a/src/commands/gnupgprocesscommand.cpp
+++ b/src/commands/gnupgprocesscommand.cpp
@@ -1,390 +1,411 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/gnupgprocesscommand.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 <config-kleopatra.h>
#include "gnupgprocesscommand.h"
#include "command_p.h"
#include <Libkleo/GnuPG>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <KWindowSystem>
#include <QByteArray>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPointer>
#include <QProcess>
#include <QPushButton>
#include <QString>
#include <QTextEdit>
#include <QTimer>
#include <QVBoxLayout>
static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
using namespace Kleo;
using namespace Kleo::Commands;
namespace
{
class OutputDialog : public QDialog
{
Q_OBJECT
public:
explicit OutputDialog(QWidget *parent = nullptr)
: QDialog(parent)
, vlay(this)
, logTextWidget(this)
, buttonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close, Qt::Horizontal, this)
{
Q_SET_OBJECT_NAME(vlay);
Q_SET_OBJECT_NAME(logTextWidget);
Q_SET_OBJECT_NAME(buttonBox);
logTextWidget.setReadOnly(true);
vlay.addWidget(&logTextWidget, 1);
vlay.addWidget(&buttonBox);
connect(closeButton(), &QAbstractButton::clicked, this, &QWidget::close);
connect(cancelButton(), &QAbstractButton::clicked, this, &OutputDialog::slotCancelClicked);
resize(600, 500);
}
Q_SIGNALS:
void cancelRequested();
public Q_SLOTS:
void message(const QString &s)
{
logTextWidget.append(s);
logTextWidget.ensureCursorVisible();
}
void setComplete(bool complete)
{
cancelButton()->setVisible(!complete);
}
private Q_SLOTS:
void slotCancelClicked()
{
cancelButton()->hide();
Q_EMIT cancelRequested();
}
private:
QAbstractButton *closeButton() const
{
return buttonBox.button(QDialogButtonBox::Close);
}
QAbstractButton *cancelButton() const
{
return buttonBox.button(QDialogButtonBox::Cancel);
}
private:
QVBoxLayout vlay;
QTextEdit logTextWidget;
QDialogButtonBox buttonBox;
};
}
class GnuPGProcessCommand::Private : Command::Private
{
friend class ::Kleo::Commands::GnuPGProcessCommand;
GnuPGProcessCommand *q_func() const
{
return static_cast<GnuPGProcessCommand *>(q);
}
public:
explicit Private(GnuPGProcessCommand *qq, KeyListController *c);
~Private() override;
private:
void init();
void ensureDialogCreated()
{
if (!showsOutputWindow) {
return;
}
if (!dialog) {
dialog = new OutputDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose);
applyWindowID(dialog);
connect(dialog.data(), &OutputDialog::cancelRequested, q, &Command::cancel);
dialog->setWindowTitle(i18nc("@title:window", "Subprocess Diagnostics"));
}
}
void ensureDialogVisible()
{
if (!showsOutputWindow) {
return;
}
ensureDialogCreated();
if (dialog->isVisible()) {
dialog->raise();
} else {
dialog->show();
}
}
void message(const QString &msg)
{
if (dialog) {
dialog->message(msg);
} else {
qCDebug(KLEOPATRA_LOG) << msg;
}
}
private:
void slotProcessFinished(int, QProcess::ExitStatus);
void slotProcessReadyReadStandardError();
private:
QProcess process;
QPointer<OutputDialog> dialog;
QStringList arguments;
QByteArray errorBuffer;
bool ignoresSuccessOrFailure;
bool showsOutputWindow;
bool canceled;
+ bool success = true;
+ bool interactive = true;
};
GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
GnuPGProcessCommand::Private::Private(GnuPGProcessCommand *qq, KeyListController *c)
: Command::Private(qq, c)
, process()
, dialog()
, errorBuffer()
, ignoresSuccessOrFailure(false)
, showsOutputWindow(false)
, canceled(false)
{
process.setReadChannel(QProcess::StandardError);
}
GnuPGProcessCommand::Private::~Private()
{
}
GnuPGProcessCommand::GnuPGProcessCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
GnuPGProcessCommand::GnuPGProcessCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
GnuPGProcessCommand::GnuPGProcessCommand(const GpgME::Key &key)
: Command(key, new Private(this, nullptr))
{
d->init();
}
GnuPGProcessCommand::GnuPGProcessCommand(const std::vector<GpgME::Key> &keys)
: Command(keys, new Private(this, nullptr))
{
d->init();
}
void GnuPGProcessCommand::Private::init()
{
connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) {
slotProcessFinished(exitCode, status);
});
q->m_procReadyReadStdErrConnection = connect(&process, &QProcess::readyReadStandardError, q, [this]() {
slotProcessReadyReadStandardError();
});
}
GnuPGProcessCommand::~GnuPGProcessCommand()
{
}
QDialog *GnuPGProcessCommand::dialog() const
{
return d->dialog;
}
bool GnuPGProcessCommand::preStartHook(QWidget *) const
{
return true;
}
void GnuPGProcessCommand::postSuccessHook(QWidget *)
{
}
void GnuPGProcessCommand::doStart()
{
if (!preStartHook(d->parentWidgetOrView())) {
d->finished();
return;
}
d->arguments = arguments();
d->process.setProgram(d->arguments.takeFirst());
d->process.setArguments(d->arguments);
// Historically code using this expects arguments first to be the program.
d->arguments.prepend(d->process.program());
d->process.start();
if (!d->process.waitForStarted()) {
d->error(i18n("Unable to start process %1. "
"Please check your installation.",
d->arguments[0]),
errorCaption());
d->finished();
} else {
d->ensureDialogVisible();
d->message(i18n("Starting %1...", d->arguments.join(QLatin1Char(' '))));
}
}
void GnuPGProcessCommand::doCancel()
{
d->canceled = true;
if (d->process.state() != QProcess::NotRunning) {
d->process.terminate();
QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
}
}
void GnuPGProcessCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
{
if (!canceled) {
if (status == QProcess::CrashExit) {
+ success = false;
const QString msg = q->crashExitMessage(arguments);
if (!msg.isEmpty()) {
error(msg, q->errorCaption());
}
} else if (ignoresSuccessOrFailure) {
- if (dialog) {
+ success = true;
+ if (dialog && interactive) {
message(i18n("Process finished"));
} else {
;
}
} else if (code) {
+ success = false;
const QString msg = q->errorExitMessage(arguments);
if (!msg.isEmpty()) {
error(q->errorExitMessage(arguments), q->errorCaption());
}
} else {
+ success = true;
q->postSuccessHook(parentWidgetOrView());
const QString successMessage = q->successMessage(arguments);
- if (!successMessage.isNull()) {
+ if (!successMessage.isNull() && interactive) {
if (dialog) {
message(successMessage);
} else {
information(successMessage, q->successCaption());
}
}
}
}
if (dialog) {
dialog->setComplete(true);
}
finished();
}
void GnuPGProcessCommand::Private::slotProcessReadyReadStandardError()
{
auto ba = process.readAllStandardError();
errorBuffer += ba;
while (ba.endsWith('\n') || ba.endsWith('\r')) {
ba.chop(1);
}
message(Kleo::stringFromGpgOutput(ba));
}
QString GnuPGProcessCommand::errorString() const
{
return Kleo::stringFromGpgOutput(d->errorBuffer);
}
void GnuPGProcessCommand::setIgnoresSuccessOrFailure(bool ignores)
{
d->ignoresSuccessOrFailure = ignores;
}
bool GnuPGProcessCommand::ignoresSuccessOrFailure() const
{
return d->ignoresSuccessOrFailure;
}
void GnuPGProcessCommand::setShowsOutputWindow(bool show)
{
if (show == d->showsOutputWindow) {
return;
}
d->showsOutputWindow = show;
if (show) {
d->ensureDialogCreated();
} else {
if (d->dialog) {
d->dialog->deleteLater();
}
d->dialog = nullptr;
}
}
bool GnuPGProcessCommand::showsOutputWindow() const
{
return d->showsOutputWindow;
}
QProcess *GnuPGProcessCommand::process()
{
return &d->process;
}
QString GnuPGProcessCommand::successCaption() const
{
return QString();
}
QString GnuPGProcessCommand::successMessage(const QStringList &args) const
{
Q_UNUSED(args)
return QString();
}
+bool GnuPGProcessCommand::success() const
+{
+ return d->success;
+}
+
+void GnuPGProcessCommand::setInteractive(bool interactive)
+{
+ d->interactive = interactive;
+}
+
+bool GnuPGProcessCommand::interactive() const
+{
+ return d->interactive;
+}
+
#undef d
#undef q
#include "gnupgprocesscommand.moc"
#include "moc_gnupgprocesscommand.cpp"
diff --git a/src/commands/gnupgprocesscommand.h b/src/commands/gnupgprocesscommand.h
index 58d9eacdb..f2e1ba7fe 100644
--- a/src/commands/gnupgprocesscommand.h
+++ b/src/commands/gnupgprocesscommand.h
@@ -1,71 +1,74 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/gnupgprocesscommand.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <commands/command.h>
#include <QStringList>
class QString;
class QProcess;
namespace Kleo
{
namespace Commands
{
class GnuPGProcessCommand : public Command
{
Q_OBJECT
protected:
GnuPGProcessCommand(QAbstractItemView *view, KeyListController *parent);
explicit GnuPGProcessCommand(KeyListController *parent);
explicit GnuPGProcessCommand(const GpgME::Key &key);
explicit GnuPGProcessCommand(const std::vector<GpgME::Key> &keys);
~GnuPGProcessCommand() override;
public:
QDialog *dialog() const;
void setShowsOutputWindow(bool show);
+ bool success() const;
+ void setInteractive(bool interactive);
+ bool interactive() const;
private:
virtual bool preStartHook(QWidget *parentWidget) const;
virtual QStringList arguments() const = 0;
virtual QString errorCaption() const = 0;
virtual QString successCaption() const;
virtual QString crashExitMessage(const QStringList &args) const = 0;
virtual QString errorExitMessage(const QStringList &args) const = 0;
virtual QString successMessage(const QStringList &args) const;
virtual void postSuccessHook(QWidget *parentWidget);
protected:
QString errorString() const;
void setIgnoresSuccessOrFailure(bool ignore);
bool ignoresSuccessOrFailure() const;
bool showsOutputWindow() const;
QProcess *process();
void doStart() override;
void doCancel() override;
QMetaObject::Connection m_procReadyReadStdErrConnection;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
}
}
diff --git a/src/commands/revokekeycommand.cpp b/src/commands/revokekeycommand.cpp
index 185ec9169..86933e1a5 100644
--- a/src/commands/revokekeycommand.cpp
+++ b/src/commands/revokekeycommand.cpp
@@ -1,313 +1,332 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
-#include "kleopatra_debug.h"
-
#include "command_p.h"
#include "commands/exportopenpgpcertstoservercommand.h"
#include "dialogs/revokekeydialog.h"
+#include "kleopatra_debug.h"
#include "revokekeycommand.h"
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyCache>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <QGpgME/RevokeKeyJob>
#include <gpgme.h>
#include <KFileUtils>
#include <KLocalizedString>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
using namespace Kleo;
using namespace GpgME;
class RevokeKeyCommand::Private : public Command::Private
{
friend class ::RevokeKeyCommand;
RevokeKeyCommand *q_func() const
{
return static_cast<RevokeKeyCommand *>(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<QGpgME::RevokeKeyJob> startJob();
void onJobResult(const Error &err);
void showError(const Error &err);
+ void exportFinished(const Error &error, const QByteArray &data);
private:
Key key;
QPointer<RevokeKeyDialog> dialog;
QPointer<QGpgME::RevokeKeyJob> job;
+ bool upload = false;
};
RevokeKeyCommand::Private *RevokeKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const RevokeKeyCommand::Private *RevokeKeyCommand::d_func() const
{
return static_cast<const Private *>(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<Key> &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<std::string> toStdStrings(const QStringList &l)
{
std::vector<std::string> 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<std::string> lines;
if (!description.isEmpty()) {
lines = toStdStrings(description.split(QLatin1Char('\n')));
}
return lines;
}
}
std::unique_ptr<QGpgME::RevokeKeyJob> RevokeKeyCommand::Private::startJob()
{
std::unique_ptr<QGpgME::RevokeKeyJob> 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());
+ upload = dialog->uploadToKeyserver();
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;
}
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", "<para>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",
- "<para>The certificate was revoked successfully.</para><para>The revoked certificate was exported to "
- "<filename>%1</filename></para><para>To make sure that your contacts receive the revoked certificate, "
- "you can upload it to a keyserver now.</para>",
- 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",
- "<para>The certificate was revoked successfully.</para><para>The revoked certificate was exported to "
- "<filename>%1</filename></para>",
- path));
- }
- finished();
+ exportFinished(error, data);
});
job->start({QString::fromLatin1(key.primaryFingerprint())});
}
void RevokeKeyCommand::Private::showError(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred during the revocation:</para>"
"<para><message>%1</message></para>",
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();
}
+void RevokeKeyCommand::Private::exportFinished(const Error &error, const QByteArray &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);
+ if (!file.open(QIODevice::WriteOnly)) {
+ information(i18nc("@info", "The certificate was revoked successfully."));
+ finished();
+ return;
+ }
+ file.write(data);
+ file.close();
+
+ if (upload) {
+ auto const cmd = new Commands::ExportOpenPGPCertsToServerCommand(key);
+ cmd->setInteractive(false);
+ cmd->start();
+ connect(cmd, &Command::finished, q, [cmd, path, this]() {
+ if (cmd->success()) {
+ information(xi18nc("@info",
+ "<para>The certificate was revoked successfully and uploaded to the configured keyserver.</para><para>The revoked "
+ "certificate was exported to "
+ "<filename>%1</filename>.</para><para>You can send this file to your communication partners with the instruction to import "
+ "it. Usually, you would send it together with your new certificate.</para>",
+ path));
+ } else {
+ information(xi18nc("@info",
+ "<para>The certificate was revoked successfully.</para><para>The revoked certificate was exported to "
+ "<filename>%1</filename></para><para>You can send this file to your communication partners with the instruction to import "
+ "it. Usually, you would send it together with your new certificate.</para>",
+ path));
+ }
+
+ finished();
+ });
+ } else {
+ information(xi18nc("@info",
+ "<para>The certificate was revoked successfully.</para><para>The revoked certificate was exported to "
+ "<filename>%1</filename></para><para>You can send this file to your communication partners with the instruction to import it. "
+ "Usually, you would send it together with your new certificate.</para>",
+ path));
+ finished();
+ }
+}
+
#undef d
#undef q
#include "moc_revokekeycommand.cpp"
diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp
index 2eac0e1f0..c279bd36b 100644
--- a/src/dialogs/revokekeydialog.cpp
+++ b/src/dialogs/revokekeydialog.cpp
@@ -1,314 +1,326 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/revokekeydialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "revokekeydialog.h"
#include "dialogs/animatedexpander.h"
#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <Libkleo/Formatting>
+#include <Libkleo/GnuPG>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <QApplication>
#include <QButtonGroup>
+#include <QCheckBox>
#include <QDialogButtonBox>
#include <QFocusEvent>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QRegularExpression>
#include <QTextEdit>
#include <QVBoxLayout>
#include <gpgme++/global.h>
#include <gpgme++/key.h>
#include <kleopatra_debug.h>
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);
}
QSize minimumSizeHint() const override
{
return {0, fontMetrics().height() * 3};
}
};
}
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;
+ QCheckBox *keyserverCheckbox = nullptr;
} ui;
Key key;
QButtonGroup reasonGroup;
bool descriptionEditingInProgress = false;
QString descriptionAccessibleName;
public:
Private(RevokeKeyDialog *qq)
: q(qq)
{
q->setWindowTitle(i18nc("title:window", "Revoke Certificate"));
auto mainLayout = new QVBoxLayout{q};
ui.infoLabel = new QLabel{q};
auto infoGroupBox = new QGroupBox{i18nc("@title:group", "Information")};
infoGroupBox->setFlat(true);
auto infoLayout = new QVBoxLayout;
infoGroupBox->setLayout(infoLayout);
infoLayout->addWidget(ui.infoLabel);
mainLayout->addWidget(infoGroupBox);
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate has been compromised"), q}, static_cast<int>(RevocationReason::Compromised));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate is superseded"), q}, static_cast<int>(RevocationReason::Superseded));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Certificate is no longer used"), q}, static_cast<int>(RevocationReason::NoLongerUsed));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "For a different reason"), q}, static_cast<int>(RevocationReason::Unspecified));
reasonGroup.button(static_cast<int>(RevocationReason::Unspecified))->setChecked(true);
auto reasonLayout = new QVBoxLayout;
auto expander = new AnimatedExpander(i18nc("@title", "Reason for Revocation (optional)"));
connect(expander, &AnimatedExpander::startExpanding, q, [this, expander]() {
q->resize(q->size().width(), std::max(q->sizeHint().height() + expander->contentHeight() + 20, q->size().height()));
});
expander->setContentLayout(reasonLayout);
+ ui.keyserverCheckbox = new QCheckBox(i18nc("@option:check", "Upload revoked certificate to keyserver"));
+ ui.keyserverCheckbox->setEnabled(haveKeyserverConfigured());
+ mainLayout->addWidget(ui.keyserverCheckbox);
+
mainLayout->addWidget(expander);
mainLayout->addStretch(1);
for (auto radio : reasonGroup.buttons()) {
reasonLayout->addWidget(radio);
}
{
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);
reasonLayout->addWidget(ui.descriptionLabel);
reasonLayout->addWidget(ui.description);
reasonLayout->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 Certificate"));
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(), QStringLiteral("RevokeKeyDialog"));
cfgGroup.writeEntry("Size", q->size());
cfgGroup.sync();
}
void restoreGeometry(const QSize &defaultSize = {})
{
KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), QStringLiteral("RevokeKeyDialog"));
const QSize size = cfgGroup.readEntry("Size", defaultSize);
if (size.isValid()) {
q->resize(size);
} else {
q->resize(q->minimumSizeHint());
}
}
void checkAccept()
{
if (!descriptionHasAcceptableInput()) {
KMessageBox::error(q, descriptionErrorMessage());
} else {
q->accept();
}
}
bool descriptionHasAcceptableInput() const
{
return !q->description().contains(QLatin1StringView{"\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 + QLatin1StringView{", "} + 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(
xi18nc("@info",
"<para>You are about to revoke the following certificate:</para><para>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%1</para><para><emphasis "
"strong='true'>The revocation will take effect immediately and cannot be reverted.</emphasis></para><para>Consequences: <list>"
"<item>You can still decrypt everything encrypted for this certificate.</item>"
"<item>All signatures made with it will become invalid.</item>"
"<item>You cannot sign anything with this certificate anymore.</item>"
"<item>You cannot certify other certificates with it anymore.</item>"
"<item>Other people can no longer encrypt with it after receiving the revocation.</item></list></para>")
.arg(Formatting::summaryLine(key)));
}
GpgME::RevocationReason RevokeKeyDialog::reason() const
{
return static_cast<RevocationReason>(d->reasonGroup.checkedId());
}
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);
}
+bool RevokeKeyDialog::uploadToKeyserver() const
+{
+ return d->ui.keyserverCheckbox->isChecked();
+}
+
#include "revokekeydialog.moc"
#include "moc_revokekeydialog.cpp"
diff --git a/src/dialogs/revokekeydialog.h b/src/dialogs/revokekeydialog.h
index 876198e09..47b14782a 100644
--- a/src/dialogs/revokekeydialog.h
+++ b/src/dialogs/revokekeydialog.h
@@ -1,45 +1,46 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <config-kleopatra.h>
#include <QDialog>
#include <memory>
namespace GpgME
{
class Key;
enum class RevocationReason;
}
namespace Kleo
{
class RevokeKeyDialog : public QDialog
{
Q_OBJECT
public:
explicit RevokeKeyDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~RevokeKeyDialog() override;
void setKey(const GpgME::Key &key);
GpgME::RevocationReason reason() const;
QString description() const;
+ bool uploadToKeyserver() const;
private:
class Private;
const std::unique_ptr<Private> d;
};
} // namespace Kleo

File Metadata

Mime Type
text/x-diff
Expires
Sat, Feb 1, 9:26 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c6/74/4547ea824ac8051de53c7fa3a562

Event Timeline