Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/autotests/stripsuffixtest.cpp b/autotests/stripsuffixtest.cpp
index 9c971e223..74abb86b5 100644
--- a/autotests/stripsuffixtest.cpp
+++ b/autotests/stripsuffixtest.cpp
@@ -1,57 +1,57 @@
/* -*- mode: c++; c-basic-offset:4 -*-
autotests/stripsuffixtest.cpp
This file is part of Kleopatra's test suite.
SPDX-FileCopyrightText: 2022 Carlo Vanini <silhusk@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QDebug>
#include <QTest>
#include "utils/path-helper.h"
class StripSuffixTest: public QObject
{
Q_OBJECT
private Q_SLOTS:
void testStripSuffix_data();
void testStripSuffix();
};
void StripSuffixTest::testStripSuffix_data()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<QString>("baseName");
- QTest::newRow("absolute path")
- << QString::fromLatin1("/home/user/test.sig")
+ QTest::newRow("absolute path") //
+ << QString::fromLatin1("/home/user/test.sig") //
<< QString::fromLatin1("/home/user/test");
- QTest::newRow("relative path")
- << QString::fromLatin1("home/user.name/test.sig")
+ QTest::newRow("relative path") //
+ << QString::fromLatin1("home/user.name/test.sig") //
<< QString::fromLatin1("home/user.name/test");
- QTest::newRow("file name")
- << QString::fromLatin1("t.sig")
+ QTest::newRow("file name") //
+ << QString::fromLatin1("t.sig") //
<< QString::fromLatin1("./t");
- QTest::newRow("short extension")
- << QString::fromLatin1("/path/to/test.s")
+ QTest::newRow("short extension") //
+ << QString::fromLatin1("/path/to/test.s") //
<< QString::fromLatin1("/path/to/test");
- QTest::newRow("long extension")
- << QString::fromLatin1("/test.sign")
+ QTest::newRow("long extension") //
+ << QString::fromLatin1("/test.sign") //
<< QString::fromLatin1("/test");
- QTest::newRow("multiple extension")
- << QString::fromLatin1("some/test.tar.gz.asc")
+ QTest::newRow("multiple extension") //
+ << QString::fromLatin1("some/test.tar.gz.asc") //
<< QString::fromLatin1("some/test.tar.gz");
}
void StripSuffixTest::testStripSuffix()
{
QFETCH(QString, fileName);
QFETCH(QString, baseName);
QCOMPARE(Kleo::stripSuffix(fileName), baseName);
}
QTEST_MAIN(StripSuffixTest)
#include "stripsuffixtest.moc"
diff --git a/src/aboutdata.cpp b/src/aboutdata.cpp
index 75434320e..19d33b605 100644
--- a/src/aboutdata.cpp
+++ b/src/aboutdata.cpp
@@ -1,120 +1,119 @@
/*
aboutdata.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include <version-kleopatra.h>
#include "aboutdata.h"
#include <Libkleo/GnuPG>
#include <QLocale>
#include <KLocalizedString>
#include <KLazyLocalizedString>
static const char kleopatra_version[] = KLEOPATRA_VERSION_STRING;
struct about_data {
const KLazyLocalizedString name;
const KLazyLocalizedString desc;
const char *email;
const char *web;
};
static const about_data authors[] = {
{kli18n("Andre Heinecke"), kli18n("Current Maintainer"),
"aheinecke@gnupg.org", nullptr},
{kli18n("Marc Mutz"), kli18n("Former Maintainer"), "mutz@kde.org", nullptr},
{kli18n("Steffen Hansen"), kli18n("Former Maintainer"), "hansen@kde.org",
nullptr},
{kli18n("Matthias Kalle Dalheimer"), kli18n("Original Author"),
"kalle@kde.org", nullptr},
};
static const about_data credits[] = {
{kli18n("David Faure"),
kli18n("Backend configuration framework, KIO integration"),
"faure@kde.org", nullptr},
{kli18n("Michel Boyer de la Giroday"),
kli18n("Key-state dependent colors and fonts in the certificates list"),
"michel@klaralvdalens-datakonsult.se", nullptr},
{kli18n("Thomas Moenicke"), kli18n("Artwork"), "tm@php-qt.org", nullptr},
{kli18n("Frank Osterfeld"),
kli18n("Resident gpgme/win wrangler, UI Server commands and dialogs"),
"osterfeld@kde.org", nullptr},
{kli18n("Karl-Heinz Zimmer"),
kli18n("DN display ordering support, infrastructure"), "khz@kde.org",
nullptr},
{kli18n("Laurent Montel"), kli18n("Qt5 port, general code maintenance"),
"montel@kde.org", nullptr},
};
AboutData::AboutData()
: KAboutData(QStringLiteral("kleopatra"),
(Kleo::brandingWindowTitle().isEmpty() ?
i18n("Kleopatra") : Kleo::brandingWindowTitle()),
#ifdef Q_OS_WIN
Kleo::gpg4winVersion(),
Kleo::gpg4winDescription(),
#else
QLatin1String(kleopatra_version),
i18n("Certificate Manager and Unified Crypto GUI"),
#endif
KAboutLicense::GPL,
- i18n("(c) 2002 Steffen\u00A0Hansen, Matthias\u00A0Kalle\u00A0" "Dalheimer, Klar\u00E4lvdalens\u00A0" "Datakonsult\u00A0" "AB\n"
- "(c) 2004, 2007, 2008, 2009 Marc\u00A0Mutz, Klar\u00E4lvdalens\u00A0" "Datakonsult\u00A0" "AB") +
- QLatin1Char('\n') +
- i18n("(c) 2016-2018 Intevation GmbH") +
- QLatin1Char('\n') +
- i18n("(c) 2010-%1 The Kleopatra developers, g10 Code GmbH", QStringLiteral("2023"))
-#ifdef Q_OS_WIN
- , Kleo::gpg4winLongDescription()
-#endif
+ i18n("(c) 2002 Steffen\u00A0Hansen, Matthias\u00A0Kalle\u00A0Dalheimer, Klar\u00E4lvdalens\u00A0Datakonsult\u00A0AB\n"
+ "(c) 2004, 2007, 2008, 2009 Marc\u00A0Mutz, Klar\u00E4lvdalens\u00A0Datakonsult\u00A0AB") //
+ + QLatin1Char('\n') //
+ + i18n("(c) 2016-2018 Intevation GmbH") //
+ + QLatin1Char('\n') //
+ + i18n("(c) 2010-%1 The Kleopatra developers, g10 Code GmbH", QStringLiteral("2023"))
)
{
+#ifdef Q_OS_WIN
+ setOtherText(Kleo::gpg4winLongDescription());
+#endif
using ::authors;
using ::credits;
for (unsigned int i = 0; i < sizeof authors / sizeof * authors; ++i) {
addAuthor(KLocalizedString(authors[i].name).toString(), KLocalizedString(authors[i].desc).toString(),
QLatin1String(authors[i].email), QLatin1String(authors[i].web));
}
for (unsigned int i = 0; i < sizeof credits / sizeof * credits; ++i) {
addCredit(KLocalizedString(credits[i].name).toString(), KLocalizedString(credits[i].desc).toString(),
QLatin1String(credits[i].email), QLatin1String(credits[i].web));
}
/* For Linux it is possible that kleo is shipped as part
* of a Gpg4win based Appimage with according about data. */
if (Kleo::gpg4winSignedversion()) {
setVersion(Kleo::gpg4winVersion().toUtf8());
setShortDescription(Kleo::gpg4winDescription());
setOtherText(Kleo::gpg4winLongDescription());
/* Bug reporting page is only available in german and english */
if (QLocale().uiLanguages().first().startsWith(QStringLiteral("de"))) {
setBugAddress("https://gnupg.com/vsd/report.de.html");
} else {
setBugAddress("https://gnupg.com/vsd/report.html");
}
} else {
#ifdef Q_OS_WIN
setBugAddress("https://dev.gnupg.org/u/rgpg4win");
#endif
}
const auto backendVersions = Kleo::backendVersionInfo();
if (!backendVersions.empty()) {
- setOtherText(i18nc("Preceeds a list of applications/libraries used by Kleopatra",
- "Uses:") +
- QLatin1String{"<ul><li>"} +
- backendVersions.join(QLatin1String{"</li><li>"}) +
- QLatin1String{"</li></ul>"} +
- otherText());
+ setOtherText(i18nc("Preceeds a list of applications/libraries used by Kleopatra", "Uses:") //
+ + QLatin1String{"<ul><li>"} //
+ + backendVersions.join(QLatin1String{"</li><li>"}) //
+ + QLatin1String{"</li></ul>"} //
+ + otherText());
}
}
diff --git a/src/accessibility/accessiblerichtextlabel.cpp b/src/accessibility/accessiblerichtextlabel.cpp
index 27a518d31..ec1c685f9 100644
--- a/src/accessibility/accessiblerichtextlabel.cpp
+++ b/src/accessibility/accessiblerichtextlabel.cpp
@@ -1,252 +1,252 @@
/*
accessibility/accessiblerichtextlabel.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 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 "accessiblerichtextlabel_p.h"
#include "accessiblelink_p.h"
#include <interfaces/anchorprovider.h>
#include <QLabel>
#include <QTextDocument>
using namespace Kleo;
struct AccessibleRichTextLabel::ChildData {
QAccessible::Id id = 0;
};
AccessibleRichTextLabel::AccessibleRichTextLabel(QWidget *w)
: QAccessibleWidget{w, QAccessible::StaticText}
{
Q_ASSERT(qobject_cast<QLabel *>(w));
}
AccessibleRichTextLabel::~AccessibleRichTextLabel()
{
clearChildCache();
}
void *AccessibleRichTextLabel::interface_cast(QAccessible::InterfaceType t)
{
if (t == QAccessible::TextInterface) {
return static_cast<QAccessibleTextInterface *>(this);
}
return QAccessibleWidget::interface_cast(t);
}
QAccessible::State AccessibleRichTextLabel::state() const
{
QAccessible::State state = QAccessibleWidget::state();
state.readOnly = true;
state.selectableText = true;
return state;
}
QString AccessibleRichTextLabel::text(QAccessible::Text t) const
{
QString str;
switch (t) {
case QAccessible::Name:
str = widget()->accessibleName();
if (str.isEmpty()) {
str = displayText();
}
break;
default:
break;
}
if (str.isEmpty()) {
str = QAccessibleWidget::text(t);
}
return str;
}
QAccessibleInterface *AccessibleRichTextLabel::focusChild() const
{
if (const auto *const ap = anchorProvider()) {
const int childIndex = ap->selectedAnchor();
if (childIndex >= 0) {
return child(childIndex);
}
}
return QAccessibleWidget::focusChild();
}
QAccessibleInterface *AccessibleRichTextLabel::child(int index) const
{
const auto *const ap = anchorProvider();
if (ap && index >= 0 && index < ap->numberOfAnchors()) {
auto &childData = childCache()[index];
if (childData.id != 0) {
return QAccessible::accessibleInterface(childData.id);
}
QAccessibleInterface *iface = new AccessibleLink{widget(), index};
childData.id = QAccessible::registerAccessibleInterface(iface);
return iface;
}
return nullptr;
}
int AccessibleRichTextLabel::childCount() const
{
if (const auto *const ap = anchorProvider()) {
return ap->numberOfAnchors();
}
return 0;
}
int AccessibleRichTextLabel::indexOfChild(const QAccessibleInterface *child) const
{
if ((child->role() == QAccessible::Link) && (child->parent() == this)) {
return static_cast<const AccessibleLink *>(child)->index();
}
return -1;
}
void AccessibleRichTextLabel::selection(int selectionIndex, int *startOffset, int *endOffset) const
{
*startOffset = *endOffset = 0;
if (selectionIndex != 0)
return;
*startOffset = label()->selectionStart();
*endOffset = *startOffset + label()->selectedText().size();
}
int AccessibleRichTextLabel::selectionCount() const
{
return label()->hasSelectedText() ? 1 : 0;
}
void AccessibleRichTextLabel::addSelection(int startOffset, int endOffset)
{
setSelection(0, startOffset, endOffset);
}
void AccessibleRichTextLabel::removeSelection(int selectionIndex)
{
if (selectionIndex != 0)
return;
label()->setSelection(-1, -1);
}
void AccessibleRichTextLabel::setSelection(int selectionIndex, int startOffset, int endOffset)
{
if (selectionIndex != 0)
return;
label()->setSelection(startOffset, endOffset - startOffset);
}
int AccessibleRichTextLabel::cursorPosition() const
{
return label()->hasSelectedText() ? label()->selectionStart() + label()->selectedText().size() : 0;
}
void AccessibleRichTextLabel::setCursorPosition(int position)
{
Q_UNUSED(position)
}
QString AccessibleRichTextLabel::text(int startOffset, int endOffset) const
{
if (startOffset > endOffset)
return {};
// most likely the client is asking for the selected text, so return it
// instead of a slice of displayText() if the offsets match the selection
- if (startOffset == label()->selectionStart()
+ if (startOffset == label()->selectionStart() //
&& endOffset == startOffset + label()->selectedText().size()) {
return label()->selectedText();
}
return displayText().mid(startOffset, endOffset - startOffset);
}
int AccessibleRichTextLabel::characterCount() const
{
return displayText().size();
}
QRect AccessibleRichTextLabel::characterRect(int offset) const
{
Q_UNUSED(offset)
return {};
}
int AccessibleRichTextLabel::offsetAtPoint(const QPoint &point) const
{
Q_UNUSED(point)
return -1;
}
QString AccessibleRichTextLabel::attributes(int offset, int *startOffset, int *endOffset) const
{
*startOffset = *endOffset = offset;
return {};
}
void AccessibleRichTextLabel::scrollToSubstring(int startIndex, int endIndex)
{
Q_UNUSED(startIndex)
Q_UNUSED(endIndex)
}
QLabel *AccessibleRichTextLabel::label() const
{
return qobject_cast<QLabel*>(object());
}
AnchorProvider *AccessibleRichTextLabel::anchorProvider() const
{
return dynamic_cast<AnchorProvider *>(object());
}
QString AccessibleRichTextLabel::displayText() const
{
// calculate an approximation of the displayed text without using private
// information of QLabel
QString str = label()->text();
- if (label()->textFormat() == Qt::RichText
+ if (label()->textFormat() == Qt::RichText //
|| (label()->textFormat() == Qt::AutoText && Qt::mightBeRichText(str))) {
QTextDocument doc;
doc.setHtml(str);
str = doc.toPlainText();
}
return str;
}
std::vector<AccessibleRichTextLabel::ChildData> &AccessibleRichTextLabel::childCache() const
{
const auto *const ap = anchorProvider();
if (!ap || static_cast<int>(mChildCache.size()) == ap->numberOfAnchors()) {
return mChildCache;
}
clearChildCache();
// fill the cache with default-initialized child data
mChildCache.resize(ap->numberOfAnchors());
return mChildCache;
}
void AccessibleRichTextLabel::clearChildCache() const
{
std::for_each(std::cbegin(mChildCache), std::cend(mChildCache), [](const auto &child) {
if (child.id != 0) {
QAccessible::deleteAccessibleInterface(child.id);
}
});
mChildCache.clear();
}
diff --git a/src/commands/adduseridcommand.cpp b/src/commands/adduseridcommand.cpp
index 6ecd77a5c..9b77d7bd2 100644
--- a/src/commands/adduseridcommand.cpp
+++ b/src/commands/adduseridcommand.cpp
@@ -1,219 +1,219 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/adduseridcommand.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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "adduseridcommand.h"
#include "command_p.h"
#include "dialogs/adduseriddialog.h"
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/QuickJob>
#include <QObjectCleanupHandler>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
class AddUserIDCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::AddUserIDCommand;
AddUserIDCommand *q_func() const
{
return static_cast<AddUserIDCommand *>(q);
}
public:
explicit Private(AddUserIDCommand *qq, KeyListController *c);
~Private() override;
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated();
void createJob();
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::Key key;
QObjectCleanupHandler cleaner;
QPointer<AddUserIDDialog> dialog;
QPointer<QGpgME::QuickJob> job;
};
AddUserIDCommand::Private *AddUserIDCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const AddUserIDCommand::Private *AddUserIDCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
AddUserIDCommand::Private::Private(AddUserIDCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
AddUserIDCommand::Private::~Private() = default;
AddUserIDCommand::AddUserIDCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
AddUserIDCommand::AddUserIDCommand(const GpgME::Key &key)
: Command{key, new Private{this, nullptr}}
{
}
AddUserIDCommand::~AddUserIDCommand()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
}
void AddUserIDCommand::doStart()
{
const std::vector<Key> keys = d->keys();
if (keys.size() != 1) {
d->finished();
return;
}
d->key = keys.front();
- if (d->key.protocol() != GpgME::OpenPGP
+ if (d->key.protocol() != GpgME::OpenPGP //
|| !d->key.hasSecret()) {
d->finished();
return;
}
d->ensureDialogCreated();
const UserID uid = d->key.userID(0);
d->dialog->setName(QString::fromUtf8(uid.name()));
d->dialog->setEmail(Formatting::prettyEMail(uid.email(), uid.id()));
d->dialog->show();
}
void AddUserIDCommand::Private::slotDialogAccepted()
{
Q_ASSERT(dialog);
createJob();
if (!job) {
finished();
return;
}
job->startAddUid(key, dialog->userID());
}
void AddUserIDCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void AddUserIDCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled()) {
} else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void AddUserIDCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG).nospace() << this << "::" << __func__;
if (d->job) {
d->job->slotCancel();
}
}
void AddUserIDCommand::Private::ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new AddUserIDDialog;
cleaner.add(dialog);
applyWindowID(dialog);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void AddUserIDCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = QGpgME::openpgp();
if (!backend) {
return;
}
const auto j = backend->quickJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &QGpgME::QuickJob::result,
q, [this](const GpgME::Error &err) { slotResult(err); });
job = j;
}
void AddUserIDCommand::Private::showErrorDialog(const Error &err)
{
error(xi18nc("@info",
"<para>An error occurred while trying to add the user ID: "
"<message>%1</message></para>",
Formatting::errorAsString(err)),
i18nc("@title:window", "Add User ID Error"));
}
void AddUserIDCommand::Private::showSuccessDialog()
{
- information(i18nc("@info", "User ID successfully added."),
+ information(i18nc("@info", "User ID successfully added."), //
i18nc("@title:window", "Add User ID Succeeded"));
}
#undef d
#undef q
#include "moc_adduseridcommand.cpp"
diff --git a/src/commands/changeexpirycommand.cpp b/src/commands/changeexpirycommand.cpp
index 957a8a967..b0a993a60 100644
--- a/src/commands/changeexpirycommand.cpp
+++ b/src/commands/changeexpirycommand.cpp
@@ -1,318 +1,318 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/changeexpirycommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 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 "changeexpirycommand.h"
#include "command_p.h"
#include "dialogs/expirydialog.h"
#include "utils/expiration.h"
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <QGpgME/Protocol>
#include <QGpgME/ChangeExpiryJob>
#include <QDateTime>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
using namespace QGpgME;
namespace
{
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
bool subkeyHasSameExpirationAsPrimaryKey(const Subkey &subkey)
{
// we allow for a difference in expiration of up to 10 seconds
static const auto maxExpirationDifference = 10;
Q_ASSERT(!subkey.isNull());
const auto key = subkey.parent();
const auto primaryKey = key.subkey(0);
const auto primaryExpiration = quint32(primaryKey.expirationTime());
const auto subkeyExpiration = quint32(subkey.expirationTime());
if (primaryExpiration != 0 && subkeyExpiration != 0) {
return (primaryExpiration == subkeyExpiration) //
|| ((primaryExpiration > subkeyExpiration) && (primaryExpiration - subkeyExpiration <= maxExpirationDifference)) //
|| ((primaryExpiration < subkeyExpiration) && (subkeyExpiration - primaryExpiration <= maxExpirationDifference));
}
return primaryKey.neverExpires() && subkey.neverExpires();
}
bool allNotRevokedSubkeysHaveSameExpirationAsPrimaryKey(const Key &key)
{
Q_ASSERT(!key.isNull() && key.numSubkeys() > 0);
const auto subkeys = key.subkeys();
return std::all_of(std::begin(subkeys), std::end(subkeys), [](const auto &subkey) {
// revoked subkeys are ignored by gpg --quick-set-expire when updating the expiration of all subkeys;
// check if expiration of subkey is (more or less) the same as the expiration of the primary key
return subkey.isRevoked() || subkeyHasSameExpirationAsPrimaryKey(subkey);
});
}
#endif
}
class ChangeExpiryCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::ChangeExpiryCommand;
ChangeExpiryCommand *q_func() const
{
return static_cast<ChangeExpiryCommand *>(q);
}
public:
explicit Private(ChangeExpiryCommand *qq, KeyListController *c);
~Private() override;
private:
void slotDialogAccepted();
void slotDialogRejected();
void slotResult(const Error &err);
private:
void ensureDialogCreated(ExpiryDialog::Mode mode);
void createJob();
void showErrorDialog(const Error &error);
void showSuccessDialog();
private:
GpgME::Key key;
GpgME::Subkey subkey;
QPointer<ExpiryDialog> dialog;
QPointer<ChangeExpiryJob> job;
};
ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ChangeExpiryCommand::Private *ChangeExpiryCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ChangeExpiryCommand::Private::Private(ChangeExpiryCommand *qq, KeyListController *c)
: Command::Private{qq, c}
{
}
ChangeExpiryCommand::Private::~Private() = default;
void ChangeExpiryCommand::Private::slotDialogAccepted()
{
Q_ASSERT(dialog);
static const QTime END_OF_DAY{23, 59, 00};
const QDateTime expiry{dialog->dateOfExpiry(), END_OF_DAY};
qCDebug(KLEOPATRA_LOG) << "expiry" << expiry;
createJob();
Q_ASSERT(job);
std::vector<Subkey> subkeysToUpdate;
if (!subkey.isNull()) {
// change expiration of a single subkey
if (subkey.keyID() != key.keyID()) { // ignore the primary subkey
subkeysToUpdate.push_back(subkey);
}
} else {
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
// change expiration of the (primary) key and, optionally, of some subkeys
job->setOptions(ChangeExpiryJob::UpdatePrimaryKey);
if (dialog->updateExpirationOfAllSubkeys() && key.numSubkeys() > 1) {
// explicitly list the subkeys for which the expiration should be changed
// together with the expiration of the (primary) key, so that already expired
// subkeys are also updated
const auto subkeys = key.subkeys();
std::copy_if(std::next(subkeys.begin()), subkeys.end(), std::back_inserter(subkeysToUpdate), [](const auto &subkey) {
// skip revoked subkeys which would anyway be ignored by gpg;
// also skip subkeys without explicit expiration because they inherit the primary key's expiration;
// include all subkeys that are not yet expired or that expired around the same time as the primary key
return !subkey.isRevoked() //
&& !subkey.neverExpires() //
&& (!subkey.isExpired() || subkeyHasSameExpirationAsPrimaryKey(subkey));
});
}
#else
// nothing to do
#endif
}
if (const Error err = job->start(key, expiry, subkeysToUpdate)) {
showErrorDialog(err);
finished();
}
}
void ChangeExpiryCommand::Private::slotDialogRejected()
{
Q_EMIT q->canceled();
finished();
}
void ChangeExpiryCommand::Private::slotResult(const Error &err)
{
if (err.isCanceled())
;
else if (err) {
showErrorDialog(err);
} else {
showSuccessDialog();
}
finished();
}
void ChangeExpiryCommand::Private::ensureDialogCreated(ExpiryDialog::Mode mode)
{
if (dialog) {
return;
}
dialog = new ExpiryDialog{mode};
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); });
}
void ChangeExpiryCommand::Private::createJob()
{
Q_ASSERT(!job);
const auto backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
if (!backend) {
return;
}
ChangeExpiryJob *const j = backend->changeExpiryJob();
if (!j) {
return;
}
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(j, &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(j, &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
connect(j, &ChangeExpiryJob::result,
q, [this] (const auto &err) { slotResult(err); });
job = j;
}
void ChangeExpiryCommand::Private::showErrorDialog(const Error &err)
{
error(i18n("<p>An error occurred while trying to change "
"the end of the validity period for <b>%1</b>:</p><p>%2</p>",
Formatting::formatForComboBox(key),
Formatting::errorAsString(err)));
}
void ChangeExpiryCommand::Private::showSuccessDialog()
{
success(i18n("End of validity period changed successfully."));
}
ChangeExpiryCommand::ChangeExpiryCommand(KeyListController *c)
: Command{new Private{this, c}}
{
}
ChangeExpiryCommand::ChangeExpiryCommand(QAbstractItemView *v, KeyListController *c)
: Command{v, new Private{this, c}}
{
}
ChangeExpiryCommand::ChangeExpiryCommand(const GpgME::Key &key)
: Command{key, new Private{this, nullptr}}
{
}
ChangeExpiryCommand::~ChangeExpiryCommand() = default;
void ChangeExpiryCommand::setSubkey(const GpgME::Subkey &subkey)
{
d->subkey = subkey;
}
void ChangeExpiryCommand::doStart()
{
const std::vector<Key> keys = d->keys();
- if (keys.size() != 1 ||
- keys.front().protocol() != GpgME::OpenPGP ||
- !keys.front().hasSecret() ||
- keys.front().subkey(0).isNull()) {
+ if (keys.size() != 1 //
+ || keys.front().protocol() != GpgME::OpenPGP //
+ || !keys.front().hasSecret() //
+ || keys.front().subkey(0).isNull()) {
d->finished();
return;
}
d->key = keys.front();
if (!d->subkey.isNull() &&
d->subkey.parent().primaryFingerprint() != d->key.primaryFingerprint()) {
qDebug() << "Invalid subkey" << d->subkey.fingerprint()
<< ": Not a subkey of key" << d->key.primaryFingerprint();
d->finished();
return;
}
ExpiryDialog::Mode mode;
if (!d->subkey.isNull()) {
mode = ExpiryDialog::Mode::UpdateIndividualSubkey;
} else if (d->key.numSubkeys() == 1) {
mode = ExpiryDialog::Mode::UpdateCertificateWithoutSubkeys;
} else {
mode = ExpiryDialog::Mode::UpdateCertificateWithSubkeys;
}
d->ensureDialogCreated(mode);
Q_ASSERT(d->dialog);
const Subkey subkey = !d->subkey.isNull() ? d->subkey : d->key.subkey(0);
d->dialog->setDateOfExpiry((subkey.neverExpires() //
? QDate{} //
: defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration)));
#if QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY
if (mode == ExpiryDialog::Mode::UpdateCertificateWithSubkeys) {
d->dialog->setUpdateExpirationOfAllSubkeys(allNotRevokedSubkeysHaveSameExpirationAsPrimaryKey(d->key));
}
#endif
d->dialog->show();
}
void ChangeExpiryCommand::doCancel()
{
if (d->job) {
d->job->slotCancel();
}
}
#undef d
#undef q
#include "moc_changeexpirycommand.cpp"
diff --git a/src/commands/changepincommand.h b/src/commands/changepincommand.h
index 928642485..c8305df5b 100644
--- a/src/commands/changepincommand.h
+++ b/src/commands/changepincommand.h
@@ -1,47 +1,47 @@
/* commands/changepincommand.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "cardcommand.h"
namespace Kleo
{
namespace Commands
{
class ChangePinCommand : public CardCommand
{
Q_OBJECT
public:
enum ChangePinMode {
NormalMode = 0,
ResetMode = 1,
- NullPinMode = 2
+ NullPinMode = 2,
};
explicit ChangePinCommand(const std::string &serialNumber, const std::string &appName, QWidget *parent);
~ChangePinCommand() override;
void setKeyRef(const std::string &keyRef);
void setMode(ChangePinMode mode = NormalMode);
private:
void doStart() override;
void doCancel() override;
private:
class Private;
inline Private *d_func();
inline const Private *d_func() const;
};
} // namespace Commands
} // namespace Kleo
diff --git a/src/commands/command.h b/src/commands/command.h
index 135dcff18..7d0decfbc 100644
--- a/src/commands/command.h
+++ b/src/commands/command.h
@@ -1,137 +1,139 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/command.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 <QObject>
#include <qwindowdefs.h> // for WId
#include <utils/pimpl_ptr.h>
#include <utils/types.h> // for ExecutionContext
#include <vector>
class QModelIndex;
template <typename T> class QList;
class QAbstractItemView;
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KeyListController;
class AbstractKeyListSortFilterProxyModel;
class Command : public QObject, public ExecutionContext
{
Q_OBJECT
public:
explicit Command(KeyListController *parent);
explicit Command(QAbstractItemView *view, KeyListController *parent);
explicit Command(const GpgME::Key &key);
explicit Command(const std::vector<GpgME::Key> &keys);
~Command() override;
enum Restriction {
+ // clang-format off
NoRestriction = 0x0000,
NeedSelection = 0x0001,
OnlyOneKey = 0x0002,
NeedSecretKey = 0x0004, //< command performs secret key operations
NeedSecretKeyData = 0x0008, //< command needs access to the secret key data
MustBeOpenPGP = 0x0010,
MustBeCMS = 0x0020,
// esoteric:
MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate = 0x0040, // for set-owner-trust
AnyCardHasNullPin = 0x0080,
AnyCardCanLearnKeys = 0x0100,
MustBeRoot = 0x0200,
MustBeTrustedRoot = 0x0400 | MustBeRoot,
MustBeUntrustedRoot = 0x0800 | MustBeRoot,
MustBeValid = 0x1000, //< key is neither revoked nor expired nor otherwise "bad"
_AllRestrictions_Helper,
- AllRestrictions = 2 * (_AllRestrictions_Helper - 1) - 1
+ AllRestrictions = 2 * (_AllRestrictions_Helper - 1) - 1,
+ // clang-format on
};
Q_DECLARE_FLAGS(Restrictions, Restriction)
static Restrictions restrictions()
{
return NoRestriction;
}
/** Classify the files and return the most appropriate commands.
*
* @param files: A list of files.
*
* @returns null QString on success. Error message otherwise.
*/
static QVector<Command *>commandsForFiles(const QStringList &files);
/** Get a command for a query.
*
* @param query: A keyid / fingerprint or any string to use in the search.
*/
static Command *commandForQuery(const QString &query);
void setParentWidget(QWidget *widget);
void setParentWId(WId wid);
void setView(QAbstractItemView *view);
void setKey(const GpgME::Key &key);
void setKeys(const std::vector<GpgME::Key> &keys);
void setAutoDelete(bool on);
bool autoDelete() const;
void setWarnWhenRunningAtShutdown(bool warn);
bool warnWhenRunningAtShutdown() const;
public Q_SLOTS:
void start();
void cancel();
Q_SIGNALS:
void info(const QString &message, int timeout = 0);
void progress(int current, int total);
void finished();
void canceled();
private:
virtual void doStart() = 0;
virtual void doCancel() = 0;
private:
void applyWindowID(QWidget *wid) const override;
protected:
void addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &tabToolTip = QString());
protected:
class Private;
kdtools::pimpl_ptr<Private> d;
protected:
explicit Command(Private *pp);
explicit Command(QAbstractItemView *view, Private *pp);
explicit Command(const std::vector<GpgME::Key> &keys, Private *pp);
explicit Command(const GpgME::Key &key, Private *pp);
};
}
Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::Command::Restrictions)
diff --git a/src/commands/deletecertificatescommand.cpp b/src/commands/deletecertificatescommand.cpp
index 56cdb7f14..cce389b4b 100644
--- a/src/commands/deletecertificatescommand.cpp
+++ b/src/commands/deletecertificatescommand.cpp
@@ -1,396 +1,398 @@
/* -*- mode: c++; c-basic-offset:4 -*-
deleteCertificatescommand.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 <config-kleopatra.h>
#include "deletecertificatescommand.h"
#include "command_p.h"
#include <dialogs/deletecertificatesdialog.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <Libkleo/Stl_Util>
#include <QGpgME/Protocol>
#include <QGpgME/MultiDeleteJob>
#include <QGpgME/DeleteJob>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QPointer>
#include <QAbstractItemView>
#include <algorithm>
#include <vector>
#include <KLazyLocalizedString>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace QGpgME;
class DeleteCertificatesCommand::Private : public Command::Private
{
friend class ::Kleo::DeleteCertificatesCommand;
DeleteCertificatesCommand *q_func() const
{
return static_cast<DeleteCertificatesCommand *>(q);
}
public:
explicit Private(DeleteCertificatesCommand *qq, KeyListController *c);
~Private() override;
void startDeleteJob(GpgME::Protocol protocol);
void cancelJobs();
void pgpDeleteResult(const GpgME::Error &);
void cmsDeleteResult(const GpgME::Error &);
void showErrorsAndFinish();
bool canDelete(GpgME::Protocol proto) const
{
if (const auto cbp = (proto == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime()))
if (DeleteJob *const job = cbp->deleteJob()) {
job->slotCancel();
return true;
}
return false;
}
void ensureDialogCreated()
{
if (dialog) {
return;
}
dialog = new DeleteCertificatesDialog;
applyWindowID(dialog);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setWindowTitle(i18nc("@title:window", "Delete Certificates"));
connect(dialog, &QDialog::accepted, q_func(), [this]() { slotDialogAccepted(); });
connect(dialog, &QDialog::rejected, q_func(), [this]() { slotDialogRejected(); });
}
void ensureDialogShown()
{
if (dialog) {
dialog->show();
}
}
void slotDialogAccepted();
void slotDialogRejected()
{
canceled();
}
private:
QPointer<DeleteCertificatesDialog> dialog;
QPointer<MultiDeleteJob> cmsJob, pgpJob;
GpgME::Error cmsError, pgpError;
std::vector<Key> cmsKeys, pgpKeys;
};
DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
DeleteCertificatesCommand::Private::Private(DeleteCertificatesCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
DeleteCertificatesCommand::Private::~Private() {}
DeleteCertificatesCommand::DeleteCertificatesCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
DeleteCertificatesCommand::DeleteCertificatesCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
DeleteCertificatesCommand::~DeleteCertificatesCommand() {}
namespace
{
enum Action { Nothing = 0, Failure = 1, ClearCMS = 2, ClearPGP = 4 };
// const unsigned int errorCase =
// openpgp.empty() << 3U | d->canDelete( OpenPGP ) << 2U |
// cms.empty() << 1U | d->canDelete( CMS ) << 0U ;
static const struct {
const KLazyLocalizedString text;
Action actions;
} deletionErrorCases[16] = {
// if havePGP
// if cantPGP
// if haveCMS
{kli18n("Neither the OpenPGP nor the CMS "
"backends support certificate deletion.\n"
"Check your installation."),
Failure}, // cantCMS
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation.\n"
"Only the selected CMS certificates "
"will be deleted."),
ClearPGP}, // canCMS
// if !haveCMS
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure},
{kli18n("The OpenPGP backend does not support "
"certificate deletion.\n"
"Check your installation."),
Failure},
// if canPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation.\n"
"Only the selected OpenPGP certificates "
"will be deleted."),
- ClearCMS}, // cantCMS
+ ClearCMS}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !havePGP
// if cantPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation."),
- Failure}, // cantCMS
+ Failure}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if canPGP
// if haveCMS
{kli18n("The CMS backend does not support "
"certificate deletion.\n"
"Check your installation."),
- Failure}, // cantCMS
+ Failure}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
// if !haveCMS
{KLazyLocalizedString(), Nothing}, // cantCMS
{KLazyLocalizedString(), Nothing}, // canCMS
};
} // anon namespace
void DeleteCertificatesCommand::doStart()
{
std::vector<Key> selected = d->keys();
if (selected.empty()) {
d->finished();
return;
}
std::sort(selected.begin(), selected.end(), _detail::ByFingerprint<std::less>());
// Calculate the closure of the selected keys (those that need to
// be deleted with them, though not selected themselves):
std::vector<Key> toBeDeleted = KeyCache::instance()->findSubjects(selected);
std::sort(toBeDeleted.begin(), toBeDeleted.end(), _detail::ByFingerprint<std::less>());
std::vector<Key> unselected;
unselected.reserve(toBeDeleted.size());
std::set_difference(toBeDeleted.begin(), toBeDeleted.end(),
selected.begin(), selected.end(),
std::back_inserter(unselected),
_detail::ByFingerprint<std::less>());
d->ensureDialogCreated();
d->dialog->setSelectedKeys(selected);
d->dialog->setUnselectedKeys(unselected);
d->ensureDialogShown();
}
void DeleteCertificatesCommand::Private::slotDialogAccepted()
{
std::vector<Key> keys = dialog->keys();
Q_ASSERT(!keys.empty());
auto pgpBegin = keys.begin();
auto pgpEnd = std::stable_partition(pgpBegin, keys.end(),
[](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
auto cmsBegin = pgpEnd;
auto cmsEnd = keys.end();
std::vector<Key> openpgp(pgpBegin, pgpEnd);
std::vector<Key> cms(cmsBegin, cmsEnd);
- const unsigned int errorCase =
- openpgp.empty() << 3U | canDelete(OpenPGP) << 2U |
- cms.empty() << 1U | canDelete(CMS) << 0U;
+ const unsigned int errorCase = //
+ openpgp.empty() << 3U //
+ | canDelete(OpenPGP) << 2U //
+ | cms.empty() << 1U //
+ | canDelete(CMS) << 0U;
if (const unsigned int actions = deletionErrorCases[errorCase].actions) {
information(KLocalizedString(deletionErrorCases[errorCase].text).toString(),
(actions & Failure)
? i18n("Certificate Deletion Failed")
: i18n("Certificate Deletion Problem"));
if (actions & ClearCMS) {
cms.clear();
}
if (actions & ClearPGP) {
openpgp.clear();
}
if (actions & Failure) {
canceled();
return;
}
}
Q_ASSERT(!openpgp.empty() || !cms.empty());
pgpKeys.swap(openpgp);
cmsKeys.swap(cms);
if (!pgpKeys.empty()) {
startDeleteJob(GpgME::OpenPGP);
}
if (!cmsKeys.empty()) {
startDeleteJob(GpgME::CMS);
}
if ((pgpKeys.empty() || pgpError.code()) &&
(cmsKeys.empty() || cmsError.code())) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::startDeleteJob(GpgME::Protocol protocol)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const std::vector<Key> &keys = protocol == CMS ? cmsKeys : pgpKeys;
const auto backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<MultiDeleteJob> job(new MultiDeleteJob(backend));
connect(job.get(), &QGpgME::MultiDeleteJob::result, q_func(), [this, protocol](const GpgME::Error &result) {
if (protocol == CMS) {
cmsDeleteResult(result);
} else {
pgpDeleteResult(result);
}
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
if (const Error err = job->start(keys, true /*allowSecretKeyDeletion*/)) {
(protocol == CMS ? cmsError : pgpError) = err;
} else {
(protocol == CMS ? cmsJob : pgpJob) = job.release();
}
}
void DeleteCertificatesCommand::Private::showErrorsAndFinish()
{
Q_ASSERT(!pgpJob); Q_ASSERT(!cmsJob);
if (pgpError || cmsError) {
QString pgpErrorString;
if (pgpError) {
pgpErrorString = i18n("OpenPGP backend: %1", Formatting::errorAsString(pgpError));
}
QString cmsErrorString;
if (cmsError) {
cmsErrorString = i18n("CMS backend: %1", Formatting::errorAsString(cmsError));
}
const QString msg = i18n("<qt><p>An error occurred while trying to delete "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
pgpError ? cmsError ? pgpErrorString + QLatin1String("</br>") + cmsErrorString : pgpErrorString : cmsErrorString);
error(msg, i18n("Certificate Deletion Failed"));
} else if (!pgpError.isCanceled() && !cmsError.isCanceled()) {
std::vector<Key> keys = pgpKeys;
keys.insert(keys.end(), cmsKeys.begin(), cmsKeys.end());
KeyCache::mutableInstance()->remove(keys);
}
finished();
}
void DeleteCertificatesCommand::doCancel()
{
d->cancelJobs();
}
void DeleteCertificatesCommand::Private::pgpDeleteResult(const Error &err)
{
pgpError = err;
pgpJob = nullptr;
if (!cmsJob) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::cmsDeleteResult(const Error &err)
{
cmsError = err;
cmsJob = nullptr;
if (!pgpJob) {
showErrorsAndFinish();
}
}
void DeleteCertificatesCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_deletecertificatescommand.cpp"
diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp
index ca6af841d..040a35573 100644
--- a/src/commands/exportcertificatecommand.cpp
+++ b/src/commands/exportcertificatecommand.cpp
@@ -1,369 +1,368 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportcertificatecommand.h"
#include "fileoperationspreferences.h"
#include "command_p.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <QGpgME/Protocol>
#include <QGpgME/ExportJob>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QSaveFile>
#include <QMap>
#include <QPointer>
#include <QFileInfo>
#include <algorithm>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
class ExportCertificateCommand::Private : public Command::Private
{
friend class ::ExportCertificateCommand;
ExportCertificateCommand *q_func() const
{
return static_cast<ExportCertificateCommand *>(q);
}
public:
explicit Private(ExportCertificateCommand *qq, KeyListController *c);
~Private() override;
void startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void cancelJobs();
void exportResult(const GpgME::Error &, const QByteArray &);
void showError(const GpgME::Error &error);
bool requestFileNames(GpgME::Protocol prot);
void finishedIfLastJob();
private:
QMap<GpgME::Protocol, QString> fileNames;
uint jobsPending = 0;
QMap<QObject *, QString> outFileForSender;
QPointer<ExportJob> cmsJob;
QPointer<ExportJob> pgpJob;
};
ExportCertificateCommand::Private *ExportCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
ExportCertificateCommand::Private::~Private() {}
ExportCertificateCommand::ExportCertificateCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(const Key &key)
: Command(key, new Private(this, nullptr))
{
}
ExportCertificateCommand::~ExportCertificateCommand() {}
void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[OpenPGP] = fileName;
}
}
QString ExportCertificateCommand::openPGPFileName() const
{
return d->fileNames[OpenPGP];
}
void ExportCertificateCommand::setX509FileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[CMS] = fileName;
}
}
QString ExportCertificateCommand::x509FileName() const
{
return d->fileNames[CMS];
}
void ExportCertificateCommand::doStart()
{
std::vector<Key> keys = d->keys();
if (keys.empty()) {
return;
}
const auto firstCms = std::partition(keys.begin(), keys.end(),
[](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
std::vector<Key> openpgp, cms;
std::copy(keys.begin(), firstCms, std::back_inserter(openpgp));
std::copy(firstCms, keys.end(), std::back_inserter(cms));
Q_ASSERT(!openpgp.empty() || !cms.empty());
const bool haveBoth = !cms.empty() && !openpgp.empty();
const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!cms.empty() ? CMS : OpenPGP);
if (!d->requestFileNames(prot)) {
Q_EMIT canceled();
d->finished();
} else {
if (!openpgp.empty()) {
d->startExportJob(GpgME::OpenPGP, openpgp);
}
if (!cms.empty()) {
d->startExportJob(GpgME::CMS, cms);
}
}
}
bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol)
{
if (protocol == UnknownProtocol) {
if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) {
return true;
}
/* Unknown protocol ask for first PGP Export file name */
if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) {
return false;
}
/* And then for CMS */
return requestFileNames(GpgME::CMS);
}
if (!fileNames[protocol].isEmpty()) {
return true;
}
const auto lastDir = ApplicationState::lastUsedExportDirectory();
QString proposedFileName = lastDir + QLatin1Char('/');
if (keys().size() == 1) {
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
const auto key = keys().front();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
+ const auto asciiArmoredCertificateClass = (protocol == OpenPGP ? Class::OpenPGP : Class::CMS) | Class::Ascii | Class::Certificate;
/* Not translated so it's better to use in tutorials etc. */
proposedFileName += QStringLiteral("%1_%2_public.%3").arg(name).arg(
Formatting::prettyKeyID(key.shortKeyID())).arg(
- QString::fromLatin1(outputFileExtension(protocol == OpenPGP
- ? Class::OpenPGP | Class::Ascii | Class::Certificate
- : Class::CMS | Class::Ascii | Class::Certificate, usePGPFileExt)));
+ QString::fromLatin1(outputFileExtension(asciiArmoredCertificateClass, usePGPFileExt)));
}
if (protocol == GpgME::CMS) {
if (!fileNames[GpgME::OpenPGP].isEmpty()) {
/* If the user has already selected a PGP file name then use that as basis
* for a proposal for the S/MIME file. */
proposedFileName = fileNames[GpgME::OpenPGP];
const int idx = proposedFileName.size() - 4;
if (proposedFileName.endsWith(QLatin1String(".asc"))) {
proposedFileName.replace(idx, 4, QLatin1String(".pem"));
}
if (proposedFileName.endsWith(QLatin1String(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) {
proposedFileName.replace(idx, 4, QLatin1String(".der"));
}
}
}
if (proposedFileName.isEmpty()) {
proposedFileName = lastDir;
proposedFileName += i18nc("A generic filename for exported certificates", "certificates");
proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(),
i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)),
QStringLiteral("imp"),
proposedFileName,
protocol == GpgME::OpenPGP
? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)")
: i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)"));
if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) {
KMessageBox::error(parentWidgetOrView(),
i18n("You have to select different filenames for different protocols."),
i18n("Export Error"));
return false;
}
const QFileInfo fi(fname);
if (fi.suffix().isEmpty()) {
fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
fileNames[protocol] = fname;
ApplicationState::setLastUsedExportDirectory(fi.absolutePath());
return !fname.isEmpty();
}
void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
const QString fileName = fileNames[protocol];
const bool binary = protocol == GpgME::OpenPGP
? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive)
: fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive);
std::unique_ptr<ExportJob> job(backend->publicKeyExportJob(!binary));
Q_ASSERT(job.get());
connect(job.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &result, const QByteArray &keyData) {
exportResult(result, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress,
q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress,
q, [this](const QString &, int current, int total) { Q_EMIT q->progress(current, total); });
#endif
QStringList fingerprints;
fingerprints.reserve(keys.size());
for (const Key &i : keys) {
fingerprints << QLatin1String(i.primaryFingerprint());
}
const GpgME::Error err = job->start(fingerprints);
if (err) {
showError(err);
finished();
return;
}
Q_EMIT q->info(i18n("Exporting certificates..."));
++jobsPending;
const QPointer<ExportJob> exportJob(job.release());
outFileForSender[exportJob.data()] = fileName;
(protocol == CMS ? cmsJob : pgpJob) = exportJob;
}
void ExportCertificateCommand::Private::showError(const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n("<qt><p>An error occurred while trying to export "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
Formatting::errorAsString(err));
error(msg, i18n("Certificate Export Failed"));
}
void ExportCertificateCommand::doCancel()
{
d->cancelJobs();
}
void ExportCertificateCommand::Private::finishedIfLastJob()
{
if (jobsPending <= 0) {
finished();
}
}
static bool write_complete(QIODevice &iod, const QByteArray &data)
{
qint64 total = 0;
qint64 toWrite = data.size();
while (total < toWrite) {
const qint64 written = iod.write(data.data() + total, toWrite);
if (written < 0) {
return false;
}
total += written;
toWrite -= written;
}
return true;
}
void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data)
{
Q_ASSERT(jobsPending > 0);
--jobsPending;
Q_ASSERT(outFileForSender.contains(q->sender()));
const QString outFile = outFileForSender[q->sender()];
if (err) {
showError(err);
finishedIfLastJob();
return;
}
QSaveFile savefile(outFile);
//TODO: use KIO
const QString writeErrorMsg = i18n("Could not write to file %1.", outFile);
const QString errorCaption = i18n("Certificate Export Failed");
if (!savefile.open(QIODevice::WriteOnly)) {
error(writeErrorMsg, errorCaption);
finishedIfLastJob();
return;
}
if (!write_complete(savefile, data) ||
!savefile.commit()) {
error(writeErrorMsg, errorCaption);
}
finishedIfLastJob();
}
void ExportCertificateCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_exportcertificatecommand.cpp"
diff --git a/src/commands/genrevokecommand.cpp b/src/commands/genrevokecommand.cpp
index de68c4ea9..8d21798b9 100644
--- a/src/commands/genrevokecommand.cpp
+++ b/src/commands/genrevokecommand.cpp
@@ -1,201 +1,201 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/genrevokecommand.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 <config-kleopatra.h>
#include "genrevokecommand.h"
#include <utils/applicationstate.h>
#include <Libkleo/GnuPG>
#include <Libkleo/Formatting>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <KMessageBox>
#include <QProcess>
#include <QTextStream>
#include <QFile>
#include <QFileDialog>
#include "command_p.h"
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
GenRevokeCommand::GenRevokeCommand(QAbstractItemView *v, KeyListController *c) :
GnuPGProcessCommand(v, c)
{
}
GenRevokeCommand::GenRevokeCommand(KeyListController *c)
: GnuPGProcessCommand(c)
{
}
GenRevokeCommand::GenRevokeCommand(const Key &key)
: GnuPGProcessCommand(key)
{
}
// Fixup the revocation certificate similar to GnuPG
void GenRevokeCommand::postSuccessHook(QWidget *parentWidget)
{
QFile f(mOutputFileName);
if (!f.open(QIODevice::ReadOnly)) {
// Should never happen because in this case we would not have had a success.
KMessageBox::error(parentWidget, QStringLiteral("Failed to access the created output file."), errorCaption());
return;
}
const QString revCert = QString::fromLocal8Bit(f.readAll());
f.close();
if (!f.open(QIODevice::WriteOnly)) {
KMessageBox::error(parentWidget, QStringLiteral("Failed to write to the created output file."), errorCaption());
return;
}
QTextStream s(&f);
- s << i18n("This is a revocation certificate for the OpenPGP key:")
- << "\n\n " << Formatting::prettyNameAndEMail(d->key())
- << "\n Fingerprint: " << d->key().primaryFingerprint() << "\n\n"
+ s << i18n("This is a revocation certificate for the OpenPGP key:") << "\n\n" //
+ << " " << Formatting::prettyNameAndEMail(d->key()) << "\n" //
+ << "Fingerprint: " << d->key().primaryFingerprint() << "\n\n"
<< i18n("A revocation certificate is a kind of \"kill switch\" to publicly\n"
"declare that a key shall not anymore be used. It is not possible\n"
"to retract such a revocation certificate once it has been published.")
<< "\n\n"
<< i18n("Use it to revoke this key in case of a compromise or loss of\n"
"the secret key.")
<< "\n\n"
<< i18n("To avoid an accidental use of this file, a colon has been inserted\n"
"before the 5 dashes below. Remove this colon with a text editor\n"
"before importing and publishing this revocation certificate.")
<< "\n\n:"
<< revCert;
s.flush();
qCDebug(KLEOPATRA_LOG) << "revocation certificate stored as:" << mOutputFileName;
f.close();
KMessageBox::information(d->parentWidgetOrView(),
i18nc("@info", "Certificate successfully created.<br><br>"
"Note:<br>To prevent accidental import of the revocation<br>"
"it is required to manually edit the certificate<br>"
"before it can be imported."),
i18n("Revocation certificate created"));
}
/* Well not much to do with GnuPGProcessCommand anymore I guess.. */
void GenRevokeCommand::doStart()
{
auto proposedFileName = ApplicationState::lastUsedExportDirectory() + u'/' + QString::fromLatin1(d->key().primaryFingerprint()) + QLatin1String{".rev"};
while (mOutputFileName.isEmpty()) {
mOutputFileName = QFileDialog::getSaveFileName(d->parentWidgetOrView(),
i18n("Generate revocation certificate"),
proposedFileName,
QStringLiteral("%1 (*.rev)").arg(i18n("Revocation Certificates ")),
{},
QFileDialog::DontConfirmOverwrite);
if (mOutputFileName.isEmpty()) {
d->finished();
return;
}
if (!mOutputFileName.endsWith(QLatin1String(".rev"))) {
mOutputFileName += QLatin1String(".rev");
}
const QFileInfo fi{mOutputFileName};
if (fi.exists()) {
auto sel = KMessageBox::questionTwoActions(d->parentWidgetOrView(),
xi18n("The file <filename>%1</filename> already exists. Do you wish to overwrite it?", fi.fileName()),
i18nc("@title:window", "Overwrite File?"),
KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::Dangerous);
if (sel == KMessageBox::ButtonCode::SecondaryAction) {
proposedFileName = mOutputFileName;
mOutputFileName.clear();
}
}
}
ApplicationState::setLastUsedExportDirectory(mOutputFileName);
auto proc = process();
// We do custom io
disconnect(m_procReadyReadStdErrConnection);
proc->setReadChannel(QProcess::StandardOutput);
GnuPGProcessCommand::doStart();
connect(proc, &QProcess::readyReadStandardOutput,
this, [proc] () {
while (proc->canReadLine()) {
const QString line = QString::fromUtf8(proc->readLine()).trimmed();
// Command-fd is a stable interface, while this is all kind of hacky we
// are on a deadline :-/
if (line == QLatin1String("[GNUPG:] GET_BOOL gen_revoke.okay")) {
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.code")) {
proc->write("0\n");
} else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.text")) {
proc->write("\n");
} else if (line == QLatin1String("[GNUPG:] GET_BOOL openfile.overwrite.okay")) {
// We asked before
proc->write("y\n");
} else if (line == QLatin1String("[GNUPG:] GET_BOOL ask_revocation_reason.okay")) {
proc->write("y\n");
}
}
});
}
QStringList GenRevokeCommand::arguments() const
{
const Key key = d->key();
return {
gpgPath(),
QStringLiteral("--command-fd"),
QStringLiteral("0"),
QStringLiteral("--status-fd"),
QStringLiteral("1"),
QStringLiteral("-o"),
mOutputFileName,
QStringLiteral("--gen-revoke"),
QLatin1String(key.primaryFingerprint()),
};
}
QString GenRevokeCommand::errorCaption() const
{
return i18nc("@title:window", "Error creating revocation certificate");
}
QString GenRevokeCommand::crashExitMessage(const QStringList &) const
{
// We show a success message so a failure is either the user aborted
// or a bug.
qCDebug(KLEOPATRA_LOG) << "Crash exit of GenRevokeCommand";
return QString();
}
QString GenRevokeCommand::errorExitMessage(const QStringList &) const
{
// We show a success message so a failure is either the user aborted
// or a bug.
qCDebug(KLEOPATRA_LOG) << "Error exit of GenRevokeCommand";
return QString();
}
diff --git a/src/commands/signencryptfilescommand.cpp b/src/commands/signencryptfilescommand.cpp
index c3a408699..5a9571e65 100644
--- a/src/commands/signencryptfilescommand.cpp
+++ b/src/commands/signencryptfilescommand.cpp
@@ -1,291 +1,291 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/signencryptfilescommand.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 "signencryptfilescommand.h"
#include "command_p.h"
#include <crypto/signencryptfilescontroller.h>
#include <utils/filedialog.h>
#include <Libkleo/Stl_Util>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <exception>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Crypto;
class SignEncryptFilesCommand::Private : public Command::Private
{
friend class ::Kleo::Commands::SignEncryptFilesCommand;
SignEncryptFilesCommand *q_func() const
{
return static_cast<SignEncryptFilesCommand *>(q);
}
public:
explicit Private(SignEncryptFilesCommand *qq, KeyListController *c);
~Private() override;
QStringList selectFiles() const;
void init();
private:
void slotControllerDone()
{
finished();
}
void slotControllerError(int, const QString &)
{
finished();
}
private:
QStringList files;
std::shared_ptr<const ExecutionContext> shared_qq;
SignEncryptFilesController controller;
};
SignEncryptFilesCommand::Private *SignEncryptFilesCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const SignEncryptFilesCommand::Private *SignEncryptFilesCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
SignEncryptFilesCommand::Private::Private(SignEncryptFilesCommand *qq, KeyListController *c)
: Command::Private(qq, c),
files(),
shared_qq(qq, [](SignEncryptFilesCommand*){}),
controller()
{
- controller.setOperationMode(SignEncryptFilesController::SignSelected |
- SignEncryptFilesController::EncryptSelected |
- SignEncryptFilesController::ArchiveAllowed);
+ controller.setOperationMode(SignEncryptFilesController::SignSelected //
+ | SignEncryptFilesController::EncryptSelected //
+ | SignEncryptFilesController::ArchiveAllowed);
}
SignEncryptFilesCommand::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
}
SignEncryptFilesCommand::SignEncryptFilesCommand(KeyListController *c)
: Command(new Private(this, c))
{
d->init();
}
SignEncryptFilesCommand::SignEncryptFilesCommand(QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
}
SignEncryptFilesCommand::SignEncryptFilesCommand(const QStringList &files, KeyListController *c)
: Command(new Private(this, c))
{
d->init();
d->files = files;
}
SignEncryptFilesCommand::SignEncryptFilesCommand(const QStringList &files, QAbstractItemView *v, KeyListController *c)
: Command(v, new Private(this, c))
{
d->init();
d->files = files;
}
void SignEncryptFilesCommand::Private::init()
{
controller.setExecutionContext(shared_qq);
connect(&controller, &Controller::done, q, [this]() { slotControllerDone(); });
connect(&controller, &Controller::error, q, [this](int err, const QString &details) { slotControllerError(err, details); });
}
SignEncryptFilesCommand::~SignEncryptFilesCommand()
{
qCDebug(KLEOPATRA_LOG);
}
void SignEncryptFilesCommand::setFiles(const QStringList &files)
{
d->files = files;
}
void SignEncryptFilesCommand::setSigningPolicy(Policy policy)
{
unsigned int mode = d->controller.operationMode();
mode &= ~SignEncryptFilesController::SignMask;
switch (policy) {
case NoPolicy:
case Allow:
mode |= SignEncryptFilesController::SignAllowed;
break;
case Deny:
mode |= SignEncryptFilesController::SignDisallowed;
break;
case Force:
mode |= SignEncryptFilesController::SignSelected;
break;
}
try {
d->controller.setOperationMode(mode);
} catch (...) {}
}
Policy SignEncryptFilesCommand::signingPolicy() const
{
const unsigned int mode = d->controller.operationMode();
switch (mode & SignEncryptFilesController::SignMask) {
default:
Q_ASSERT(!"This should not happen!");
return NoPolicy;
case SignEncryptFilesController::SignAllowed:
return Allow;
case SignEncryptFilesController::SignSelected:
return Force;
case SignEncryptFilesController::SignDisallowed:
return Deny;
}
}
void SignEncryptFilesCommand::setEncryptionPolicy(Policy policy)
{
unsigned int mode = d->controller.operationMode();
mode &= ~SignEncryptFilesController::EncryptMask;
switch (policy) {
case NoPolicy:
case Allow:
mode |= SignEncryptFilesController::EncryptAllowed;
break;
case Deny:
mode |= SignEncryptFilesController::EncryptDisallowed;
break;
case Force:
mode |= SignEncryptFilesController::EncryptSelected;
break;
}
try {
d->controller.setOperationMode(mode);
} catch (...) {}
}
Policy SignEncryptFilesCommand::encryptionPolicy() const
{
const unsigned int mode = d->controller.operationMode();
switch (mode & SignEncryptFilesController::EncryptMask) {
default:
Q_ASSERT(!"This should not happen!");
return NoPolicy;
case SignEncryptFilesController::EncryptAllowed:
return Allow;
case SignEncryptFilesController::EncryptSelected:
return Force;
case SignEncryptFilesController::EncryptDisallowed:
return Deny;
}
}
void SignEncryptFilesCommand::setArchivePolicy(Policy policy)
{
unsigned int mode = d->controller.operationMode();
mode &= ~SignEncryptFilesController::ArchiveMask;
switch (policy) {
case NoPolicy:
case Allow:
mode |= SignEncryptFilesController::ArchiveAllowed;
break;
case Deny:
mode |= SignEncryptFilesController::ArchiveDisallowed;
break;
case Force:
mode |= SignEncryptFilesController::ArchiveForced;
break;
}
d->controller.setOperationMode(mode);
}
Policy SignEncryptFilesCommand::archivePolicy() const
{
const unsigned int mode = d->controller.operationMode();
switch (mode & SignEncryptFilesController::ArchiveMask) {
case SignEncryptFilesController::ArchiveAllowed:
return Allow;
case SignEncryptFilesController::ArchiveForced:
return Force;
case SignEncryptFilesController::ArchiveDisallowed:
return Deny;
default:
Q_ASSERT(!"This should not happen!");
return NoPolicy;
}
}
void SignEncryptFilesCommand::setProtocol(GpgME::Protocol proto)
{
d->controller.setProtocol(proto);
}
GpgME::Protocol SignEncryptFilesCommand::protocol() const
{
return d->controller.protocol();
}
void SignEncryptFilesCommand::doStart()
{
try {
if (d->files.empty()) {
d->files = selectFiles();
}
if (d->files.empty()) {
d->finished();
return;
}
d->controller.setFiles(d->files);
d->controller.start();
} catch (const std::exception &e) {
d->information(i18n("An error occurred: %1",
QString::fromLocal8Bit(e.what())),
i18n("Sign/Encrypt Files Error"));
d->finished();
}
}
void SignEncryptFilesCommand::doCancel()
{
qCDebug(KLEOPATRA_LOG);
d->controller.cancel();
}
QStringList SignEncryptFilesCommand::selectFiles() const
{
return FileDialog::getOpenFileNames(d->parentWidgetOrView(), i18n("Select One or More Files to Sign and/or Encrypt"), QStringLiteral("enc"));
}
#undef d
#undef q
#include "moc_signencryptfilescommand.cpp"
diff --git a/src/conf/dirservconfigpage.cpp b/src/conf/dirservconfigpage.cpp
index 9925fc59d..6b4597d43 100644
--- a/src/conf/dirservconfigpage.cpp
+++ b/src/conf/dirservconfigpage.cpp
@@ -1,529 +1,529 @@
/* -*- mode: c++; c-basic-offset:4 -*-
conf/dirservconfigpage.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB
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 "dirservconfigpage.h"
#include "labelledwidget.h"
#include <settings.h>
#include <Libkleo/Compat>
#include <Libkleo/DirectoryServicesWidget>
#include <Libkleo/KeyserverConfig>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <KMessageBox>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <KConfig>
#include <QSpinBox>
#include <QLabel>
#include <QCheckBox>
#include <QGroupBox>
#include <QLayout>
#include <QLineEdit>
#include <QTimeEdit>
#include <QVBoxLayout>
#include <gpgme++/engineinfo.h>
#include <gpgme.h>
using namespace Kleo;
using namespace QGpgME;
// Option for configuring X.509 servers (available via gpgconf since GnuPG 2.3.5 and 2.2.34)
static const char s_x509services_componentName[] = "dirmngr";
static const char s_x509services_entryName[] = "ldapserver";
// Legacy option for configuring X.509 servers (deprecated with GnuPG 2.2.28 and 2.3.2)
static const char s_x509services_legacy_componentName[] = "gpgsm";
static const char s_x509services_legacy_entryName[] = "keyserver";
static const char s_pgpservice_componentName[] = "dirmngr";
static const char s_pgpservice_entryName[] = "keyserver";
// legacy config entry used until GnuPG 2.2
static const char s_pgpservice_legacy_componentName[] = "gpg";
static const char s_pgpservice_legacy_entryName[] = "keyserver";
static const char s_timeout_componentName[] = "dirmngr";
static const char s_timeout_entryName[] = "ldaptimeout";
static const char s_maxitems_componentName[] = "dirmngr";
static const char s_maxitems_entryName[] = "max-replies";
class DirectoryServicesConfigurationPage::Private
{
DirectoryServicesConfigurationPage *q = nullptr;
public:
Private(DirectoryServicesConfigurationPage *q);
void load();
void save();
void defaults();
private:
enum EntryMultiplicity {
SingleValue,
- ListValue
+ ListValue,
};
enum ShowError {
DoNotShowError,
- DoShowError
+ DoShowError,
};
void setX509ServerEntry(const std::vector<KeyserverConfig> &servers);
void load(const Kleo::Settings &settings);
QGpgME::CryptoConfigEntry *configEntry(const char *componentName,
const char *entryName,
QGpgME::CryptoConfigEntry::ArgType argType,
EntryMultiplicity multiplicity,
ShowError showError);
Kleo::LabelledWidget<QLineEdit> mOpenPGPKeyserverEdit;
Kleo::DirectoryServicesWidget *mDirectoryServices = nullptr;
Kleo::LabelledWidget<QTimeEdit> mTimeout;
Kleo::LabelledWidget<QSpinBox> mMaxItems;
QCheckBox *mFetchMissingSignerKeysCB = nullptr;
QGpgME::CryptoConfigEntry *mOpenPGPServiceEntry = nullptr;
QGpgME::CryptoConfigEntry *mTimeoutConfigEntry = nullptr;
QGpgME::CryptoConfigEntry *mMaxItemsConfigEntry = nullptr;
QGpgME::CryptoConfig *mConfig = nullptr;
};
DirectoryServicesConfigurationPage::Private::Private(DirectoryServicesConfigurationPage *q)
{
mConfig = QGpgME::cryptoConfig();
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
auto glay = new QGridLayout(q);
#else
auto glay = new QGridLayout(q->widget());
#endif
glay->setContentsMargins(0, 0, 0, 0);
// OpenPGP keyserver
int row = 0;
{
auto l = new QHBoxLayout{};
l->setContentsMargins(0, 0, 0, 0);
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
mOpenPGPKeyserverEdit.createWidgets(q);
#else
mOpenPGPKeyserverEdit.createWidgets(q->widget());
#endif
mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:"));
l->addWidget(mOpenPGPKeyserverEdit.label());
l->addWidget(mOpenPGPKeyserverEdit.widget());
glay->addLayout(l, row, 0, 1, 3);
connect(mOpenPGPKeyserverEdit.widget(), &QLineEdit::textEdited,
q, &DirectoryServicesConfigurationPage::markAsChanged);
}
// X.509 servers
if (Settings{}.cmsEnabled()) {
++row;
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q};
#else
auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q->widget()};
#endif
auto groupBoxLayout = new QVBoxLayout{groupBox};
if (gpgme_check_version("1.16.0")) {
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
mDirectoryServices = new Kleo::DirectoryServicesWidget(q);
#else
mDirectoryServices = new Kleo::DirectoryServicesWidget(q->widget());
#endif
if (QLayout *l = mDirectoryServices->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
groupBoxLayout->addWidget(mDirectoryServices);
connect(mDirectoryServices, &DirectoryServicesWidget::changed,
q, &DirectoryServicesConfigurationPage::markAsChanged);
} else {
// QGpgME does not properly support keyserver flags for X.509 keyservers (added in GnuPG 2.2.28);
// disable the configuration to prevent the configuration from being corrupted
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible "
"because the used gpgme libraries are too old."), q});
#else
groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible "
"because the used gpgme libraries are too old."),
q->widget()});
#endif
}
glay->addWidget(groupBox, row, 0, 1, 3);
}
// LDAP timeout
++row;
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
mTimeout.createWidgets(q);
#else
mTimeout.createWidgets(q->widget());
#endif
mTimeout.label()->setText(i18n("LDAP &timeout (minutes:seconds):"));
mTimeout.widget()->setDisplayFormat(QStringLiteral("mm:ss"));
connect(mTimeout.widget(), &QTimeEdit::timeChanged,
q, &DirectoryServicesConfigurationPage::markAsChanged);
glay->addWidget(mTimeout.label(), row, 0);
glay->addWidget(mTimeout.widget(), row, 1);
// Max number of items returned by queries
++row;
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
mMaxItems.createWidgets(q);
#else
mMaxItems.createWidgets(q->widget());
#endif
mMaxItems.label()->setText(i18n("&Maximum number of items returned by query:"));
mMaxItems.widget()->setMinimum(0);
#if QT_DEPRECATED_SINCE(5, 14)
connect(mMaxItems.widget(), qOverload<int>(&QSpinBox::valueChanged),
q, &DirectoryServicesConfigurationPage::markAsChanged);
#else
connect(mMaxItems.widget(), &QSpinBox::valueChanged,
q, &DirectoryServicesConfigurationPage::markAsChanged);
#endif
glay->addWidget(mMaxItems.label(), row, 0);
glay->addWidget(mMaxItems.widget(), row, 1);
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
++row;
mFetchMissingSignerKeysCB = new QCheckBox{q};
mFetchMissingSignerKeysCB->setText(i18nc("@option:check",
"Retrieve missing certification keys when importing new keys"));
mFetchMissingSignerKeysCB->setToolTip(
xi18nc("@info:tooltip",
"If enabled, then Kleopatra will automatically try to retrieve the keys "
"that were used to certify the user IDs of newly imported OpenPGP keys."));
connect(mFetchMissingSignerKeysCB, &QCheckBox::toggled,
q, &DirectoryServicesConfigurationPage::markAsChanged);
glay->addWidget(mFetchMissingSignerKeysCB, row, 0, 1, 3);
#endif
glay->setRowStretch(++row, 1);
glay->setColumnStretch(2, 1);
}
static auto readKeyserverConfigs(const CryptoConfigEntry *configEntry)
{
std::vector<KeyserverConfig> servers;
if (configEntry) {
const auto urls = configEntry->urlValueList();
servers.reserve(urls.size());
std::transform(std::begin(urls), std::end(urls),
std::back_inserter(servers),
&KeyserverConfig::fromUrl);
}
return servers;
}
void DirectoryServicesConfigurationPage::Private::load(const Kleo::Settings &settings)
{
if (mDirectoryServices) {
mDirectoryServices->clear();
// gpgsm uses the deprecated keyserver option in gpgsm.conf additionally to the ldapserver option in dirmngr.conf;
// we (try to) read servers from both entries, but always write to the newest existing entry
const auto *const newEntry = configEntry(s_x509services_componentName, s_x509services_entryName,
CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
const auto *const legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName,
CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
auto entry = newEntry ? newEntry : legacyEntry;
if (entry) {
const auto additionalServers = readKeyserverConfigs(legacyEntry);
auto servers = readKeyserverConfigs(newEntry);
std::copy(std::begin(additionalServers), std::end(additionalServers),
std::back_inserter(servers));
mDirectoryServices->setKeyservers(servers);
mDirectoryServices->setReadOnly(entry->isReadOnly());
} else {
qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries"
<< s_x509services_componentName << "/" << s_x509services_entryName
<< "and"
<< s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName;
mDirectoryServices->setDisabled(true);
}
}
{
// gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf;
// therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9)
auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName,
CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
auto const legacyEntry = configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName,
CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry;
if (!mOpenPGPServiceEntry) {
qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries"
<< s_pgpservice_componentName << "/" << s_pgpservice_entryName
<< "and"
<< s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
} else if (mOpenPGPServiceEntry == legacyEntry) {
qCDebug(KLEOPATRA_LOG) << "Using config entry"
<< s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
} else {
qCDebug(KLEOPATRA_LOG) << "Using config entry"
<< s_pgpservice_componentName << "/" << s_pgpservice_entryName;
}
mOpenPGPKeyserverEdit.widget()->setText(mOpenPGPServiceEntry && mOpenPGPServiceEntry->isSet() ? mOpenPGPServiceEntry->stringValue() : QString());
mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly());
#if QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE
if (newEntry && !newEntry->defaultValue().isNull()) {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(newEntry->defaultValue().toString());
} else
#endif
{
if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net"));
} else {
mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net"));
}
}
}
// read LDAP timeout
// first try to read the config entry as int (GnuPG 2.3)
mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
if (!mTimeoutConfigEntry) {
// if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
}
if (mTimeoutConfigEntry) {
const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ?
mTimeoutConfigEntry->intValue() :
static_cast<int>(mTimeoutConfigEntry->uintValue());
const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout);
//qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<" ->" << time;
mTimeout.widget()->setTime(time);
}
mTimeout.setEnabled(mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly());
// read max-replies config entry
// first try to read the config entry as int (GnuPG 2.3)
mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
if (!mMaxItemsConfigEntry) {
// if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
}
if (mMaxItemsConfigEntry) {
const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ?
mMaxItemsConfigEntry->intValue() :
static_cast<int>(mMaxItemsConfigEntry->uintValue());
mMaxItems.widget()->blockSignals(true); // KNumInput emits valueChanged from setValue!
mMaxItems.widget()->setValue(value);
mMaxItems.widget()->blockSignals(false);
}
mMaxItems.setEnabled(mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly());
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
mFetchMissingSignerKeysCB->setChecked(settings.retrieveSignerKeysAfterImport());
mFetchMissingSignerKeysCB->setEnabled(!settings.isImmutable(QStringLiteral("RetrieveSignerKeysAfterImport")));
#else
Q_UNUSED(settings)
#endif
}
void DirectoryServicesConfigurationPage::Private::load()
{
load(Settings{});
}
namespace
{
void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value) {
if (!configEntry) {
return;
}
if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) {
if (configEntry->intValue() != value) {
configEntry->setIntValue(value);
}
} else {
const auto newValue = static_cast<unsigned>(value);
if (configEntry->uintValue() != newValue) {
configEntry->setUIntValue(newValue);
}
}
}
}
void DirectoryServicesConfigurationPage::Private::setX509ServerEntry(const std::vector<KeyserverConfig> &servers)
{
const auto newEntry = configEntry(s_x509services_componentName, s_x509services_entryName,
CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
const auto legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName,
CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
if ((newEntry && newEntry->isReadOnly()) || (legacyEntry && legacyEntry->isReadOnly())) {
// do not change the config entries if either config entry is read-only
return;
}
QList<QUrl> urls;
urls.reserve(servers.size());
std::transform(std::begin(servers), std::end(servers),
std::back_inserter(urls),
std::mem_fn(&KeyserverConfig::toUrl));
if (newEntry) {
// write all servers to the new config entry
newEntry->setURLValueList(urls);
// and clear the legacy config entry
if (legacyEntry) {
legacyEntry->setURLValueList({});
}
} else if (legacyEntry) {
// write all servers to the legacy config entry if the new entry is not available
legacyEntry->setURLValueList(urls);
} else {
qCWarning(KLEOPATRA_LOG) << "Could not store the X.509 servers. Unknown or wrong typed config entries"
<< s_x509services_componentName << "/" << s_x509services_entryName
<< "and"
<< s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName;
}
}
void DirectoryServicesConfigurationPage::Private::save()
{
if (mDirectoryServices && mDirectoryServices->isEnabled()) {
setX509ServerEntry(mDirectoryServices->keyservers());
}
if (mOpenPGPServiceEntry) {
const auto keyserver = mOpenPGPKeyserverEdit.widget()->text().trimmed();
if (keyserver.isEmpty()) {
mOpenPGPServiceEntry->resetToDefault();
} else {
const auto keyserverUrl = keyserver.contains(QLatin1String{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver);
mOpenPGPServiceEntry->setStringValue(keyserverUrl);
}
}
const QTime time{mTimeout.widget()->time()};
updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second());
updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems.widget()->value());
mConfig->sync(true);
#if QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID
Settings settings;
settings.setRetrieveSignerKeysAfterImport(mFetchMissingSignerKeysCB->isChecked());
settings.save();
#endif
}
void DirectoryServicesConfigurationPage::Private::defaults()
{
// these guys don't have a default, to clear them:
if (mDirectoryServices && mDirectoryServices->isEnabled()) {
setX509ServerEntry({});
}
if (mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()) {
mOpenPGPServiceEntry->setStringValue(QString());
}
// these presumably have a default, use that one:
if (mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()) {
mTimeoutConfigEntry->resetToDefault();
}
if (mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()) {
mMaxItemsConfigEntry->resetToDefault();
}
Settings settings;
settings.setRetrieveSignerKeysAfterImport(settings.findItem(QStringLiteral("RetrieveSignerKeysAfterImport"))->getDefault().toBool());
load(settings);
}
// Find config entry for ldap servers. Implements runtime checks on the configuration option.
CryptoConfigEntry *DirectoryServicesConfigurationPage::Private::configEntry(const char *componentName,
const char *entryName,
CryptoConfigEntry::ArgType argType,
EntryMultiplicity multiplicity,
ShowError showError)
{
CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName);
if (!entry) {
if (showError == DoShowError) {
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
KMessageBox::error(q, i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName)));
#else
KMessageBox::error(
q->widget(),
i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName)));
#endif
}
return nullptr;
}
if (entry->argType() != argType || entry->isList() != bool(multiplicity)) {
if (showError == DoShowError) {
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
KMessageBox::error(q, i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", QLatin1String(componentName), QLatin1String(entryName), entry->argType(), entry->isList()));
#else
KMessageBox::error(q->widget(),
i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4",
QLatin1String(componentName),
QLatin1String(entryName),
entry->argType(),
entry->isList()));
#endif
}
return nullptr;
}
return entry;
}
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QWidget *parent, const QVariantList &args)
: KCModule{parent, args}
#else
DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
: KCModule(parent, data, args)
#endif
, d{new Private{this}}
{
}
DirectoryServicesConfigurationPage::~DirectoryServicesConfigurationPage() = default;
void DirectoryServicesConfigurationPage::load()
{
d->load();
}
void DirectoryServicesConfigurationPage::save()
{
d->save();
}
void DirectoryServicesConfigurationPage::defaults()
{
d->defaults();
}
diff --git a/src/conf/kleopageconfigdialog.cpp b/src/conf/kleopageconfigdialog.cpp
index 676ae0b1c..fd61a2d5c 100644
--- a/src/conf/kleopageconfigdialog.cpp
+++ b/src/conf/kleopageconfigdialog.cpp
@@ -1,254 +1,254 @@
/*
kleopageconfigdialog.cpp
This file is part of Kleopatra
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-only
It is derived from KCMultidialog which is:
SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
SPDX-FileCopyrightText: 2003 Daniel Molkentin <molkentin@kde.org>
SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz <kretz@kde.org>
SPDX-FileCopyrightText: 2004 Frans Englich <frans.englich@telia.com>
SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "kleopageconfigdialog.h"
#include <kcmutils_version.h>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QProcess>
#include <QPushButton>
#include <QStandardPaths>
#include <QUrl>
#include <KCModule>
#include <KStandardGuiItem>
#include <KMessageBox>
#include <KLocalizedString>
#include "kleopatra_debug.h"
KleoPageConfigDialog::KleoPageConfigDialog(QWidget *parent)
: KPageDialog(parent)
{
setModal(false);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
- buttonBox->setStandardButtons(QDialogButtonBox::Help
- | QDialogButtonBox::RestoreDefaults
- | QDialogButtonBox::Cancel
- | QDialogButtonBox::Apply
- | QDialogButtonBox::Ok
+ buttonBox->setStandardButtons(QDialogButtonBox::Help //
+ | QDialogButtonBox::RestoreDefaults //
+ | QDialogButtonBox::Cancel //
+ | QDialogButtonBox::Apply //
+ | QDialogButtonBox::Ok //
| QDialogButtonBox::Reset);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults),
KStandardGuiItem::defaults());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help());
buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false);
buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked,
this, &KleoPageConfigDialog::slotApplyClicked);
connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked,
this, &KleoPageConfigDialog::slotOkClicked);
connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked,
this, &KleoPageConfigDialog::slotDefaultClicked);
connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked,
this, &KleoPageConfigDialog::slotHelpClicked);
connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked,
this, &KleoPageConfigDialog::slotUser1Clicked);
setButtonBox(buttonBox);
connect(this, &KPageDialog::currentPageChanged,
this, &KleoPageConfigDialog::slotCurrentPageChanged);
}
void KleoPageConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous)
{
if (!previous) {
return;
}
blockSignals(true);
setCurrentPage(previous);
KCModule *previousModule = qobject_cast<KCModule*>(previous->widget());
bool canceled = false;
if (previousModule && mChangedModules.contains(previousModule)) {
const int queryUser = KMessageBox::warningTwoActionsCancel(
this,
i18n("The settings of the current module have changed.\n"
"Do you want to apply the changes or discard them?"),
i18n("Apply Settings"),
KStandardGuiItem::apply(),
KStandardGuiItem::discard(),
KStandardGuiItem::cancel());
if (queryUser == KMessageBox::ButtonCode::PrimaryAction) {
previousModule->save();
} else if (queryUser == KMessageBox::ButtonCode::SecondaryAction) {
previousModule->load();
}
canceled = queryUser == KMessageBox::Cancel;
}
if (!canceled) {
mChangedModules.removeAll(previousModule);
setCurrentPage(current);
}
blockSignals(false);
clientChanged();
}
void KleoPageConfigDialog::apply()
{
QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
applyButton->setFocus();
for (KCModule *module : mChangedModules) {
module->save();
}
mChangedModules.clear();
Q_EMIT configCommitted();
clientChanged();
}
void KleoPageConfigDialog::slotDefaultClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule*>(item->widget());
if (!module) {
return;
}
module->defaults();
clientChanged();
}
void KleoPageConfigDialog::slotUser1Clicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule*>(item->widget());
if (!module) {
return;
}
module->load();
mChangedModules.removeAll(module);
clientChanged();
}
void KleoPageConfigDialog::slotApplyClicked()
{
apply();
}
void KleoPageConfigDialog::slotOkClicked()
{
apply();
accept();
}
void KleoPageConfigDialog::slotHelpClicked()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
const QString docPath = mHelpUrls.value(item->name());
QUrl docUrl;
#ifdef Q_OS_WIN
docUrl = QUrl(QLatin1String("https://docs.kde.org/index.php?branch=stable5&language=")
+ QLocale().name() + QLatin1String("&application=kleopatra"));
#else
docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp
#endif
if (docUrl.scheme() == QLatin1String("help") || docUrl.scheme() == QLatin1String("man") || docUrl.scheme() == QLatin1String("info")) {
// Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process.
QProcess::startDetached(QStringLiteral("khelpcenter"), QStringList() << docUrl.toString());
} else {
QDesktopServices::openUrl(docUrl);
}
}
void KleoPageConfigDialog::addModule(const QString &name, const QString &docPath, const QString &icon, KCModule *module)
{
mModules << module;
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
KPageWidgetItem *item = addPage(module, name);
#else
KPageWidgetItem *item = addPage(module->widget(), name);
#endif
item->setIcon(QIcon::fromTheme(icon));
#if KCMUTILS_VERSION < QT_VERSION_CHECK(5, 240, 0)
connect(module, SIGNAL(changed(bool)), this, SLOT(moduleChanged(bool)));
#else
connect(module, &KCModule::needsSaveChanged, this, [this, module]() {
moduleChanged(module->needsSave());
});
#endif
mHelpUrls.insert(name, docPath);
}
void KleoPageConfigDialog::moduleChanged(bool state)
{
KCModule *module = qobject_cast<KCModule*>(sender());
qCDebug(KLEOPATRA_LOG) << "Module changed: " << state << " mod " << module;
if (mChangedModules.contains(module)) {
if (!state) {
mChangedModules.removeAll(module);
} else {
return;
}
}
if (state) {
mChangedModules << module;
}
clientChanged();
}
void KleoPageConfigDialog::clientChanged()
{
const KPageWidgetItem *item = currentPage();
if (!item) {
return;
}
KCModule *module = qobject_cast<KCModule*>(item->widget());
if (!module) {
return;
}
qCDebug(KLEOPATRA_LOG) << "Client changed: " << " mod " << module;
bool change = mChangedModules.contains(module);
QPushButton *resetButton = buttonBox()->button(QDialogButtonBox::Reset);
if (resetButton) {
resetButton->setEnabled(change);
}
QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply);
if (applyButton) {
applyButton->setEnabled(change);
}
}
diff --git a/src/conf/smimevalidationconfigurationwidget.cpp b/src/conf/smimevalidationconfigurationwidget.cpp
index 75cf00d7c..46b9cf959 100644
--- a/src/conf/smimevalidationconfigurationwidget.cpp
+++ b/src/conf/smimevalidationconfigurationwidget.cpp
@@ -1,405 +1,406 @@
/* -*- 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 <config-kleopatra.h>
#include "smimevalidationconfigurationwidget.h"
#include "ui_smimevalidationconfigurationwidget.h"
#include "labelledwidget.h"
#include "smimevalidationpreferences.h"
#include <Libkleo/Compat>
#include <QGpgME/CryptoConfig>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#if HAVE_QDBUS
# include <QDBusConnection>
#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<int>(&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<KleopatraClientCopy::Gui::CertificateRequester> labelledOCSPResponderSignature;
LabelledWidget<QLineEdit> 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
+ 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))
+ : 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/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp
index 86c59b37d..42f5c361c 100644
--- a/src/crypto/autodecryptverifyfilescontroller.cpp
+++ b/src/crypto/autodecryptverifyfilescontroller.cpp
@@ -1,624 +1,624 @@
/* -*- mode: c++; c-basic-offset:4 -*-
autodecryptverifyfilescontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 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
*/
#include <config-kleopatra.h>
#include "autodecryptverifyfilescontroller.h"
#include "fileoperationspreferences.h"
#include <crypto/gui/decryptverifyoperationwidget.h>
#include <crypto/gui/decryptverifyfilesdialog.h>
#include <crypto/decryptverifytask.h>
#include <crypto/taskcollection.h>
#include "commands/decryptverifyfilescommand.h"
#include <Libkleo/GnuPG>
#include <utils/path-helper.h>
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <utils/archivedefinition.h>
#include <Libkleo/Classify>
#ifndef Q_OS_WIN
#include <KIO/CopyJob>
#include <KIO/Global>
#endif
#include <KLocalizedString>
#include <KMessageBox>
#include "kleopatra_debug.h"
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/DecryptVerifyArchiveJob>
#endif
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QFileDialog>
#include <QTemporaryDir>
#include <gpgme++/decryptionresult.h>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
class AutoDecryptVerifyFilesController::Private
{
AutoDecryptVerifyFilesController *const q;
public:
explicit Private(AutoDecryptVerifyFilesController *qq);
void schedule();
QString getEmbeddedFileName(const QString &fileName) const;
void exec();
std::vector<std::shared_ptr<Task> > buildTasks(const QStringList &, QStringList &);
struct CryptoFile {
QString baseName;
QString fileName;
GpgME::Protocol protocol = GpgME::UnknownProtocol;
int classification = 0;
std::shared_ptr<Output> output;
};
QVector<CryptoFile> classifyAndSortFiles(const QStringList &files);
void reportError(int err, const QString &details)
{
q->setLastError(err, details);
q->emitDoneOrError();
}
void cancelAllTasks();
QStringList m_passedFiles, m_filesAfterPreparation;
std::vector<std::shared_ptr<const DecryptVerifyResult> > m_results;
std::vector<std::shared_ptr<Task> > m_runnableTasks, m_completedTasks;
std::shared_ptr<Task> m_runningTask;
bool m_errorDetected = false;
DecryptVerifyOperation m_operation = DecryptVerify;
QPointer<DecryptVerifyFilesDialog> m_dialog;
std::unique_ptr<QTemporaryDir> m_workDir;
};
AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq)
{
qRegisterMetaType<VerificationResult>();
}
void AutoDecryptVerifyFilesController::Private::schedule()
{
if (!m_runningTask && !m_runnableTasks.empty()) {
const std::shared_ptr<Task> t = m_runnableTasks.back();
m_runnableTasks.pop_back();
t->start();
m_runningTask = t;
}
if (!m_runningTask) {
kleo_assert(m_runnableTasks.empty());
for (const std::shared_ptr<const DecryptVerifyResult> &i : std::as_const(m_results)) {
Q_EMIT q->verificationResult(i->verificationResult());
}
}
}
QString AutoDecryptVerifyFilesController::Private::getEmbeddedFileName(const QString &fileName) const
{
auto it = std::find_if(m_results.cbegin(), m_results.cend(), [fileName](const auto &r) {
return r->fileName() == fileName;
});
if (it != m_results.cend()) {
const auto embeddedFilePath = QString::fromUtf8((*it)->decryptionResult().fileName());
if (embeddedFilePath.contains(QLatin1Char{'\\'})) {
// ignore embedded file names containing '\'
return {};
}
// strip the path from the embedded file name
return QFileInfo{embeddedFilePath}.fileName();
} else {
return {};
}
}
void AutoDecryptVerifyFilesController::Private::exec()
{
Q_ASSERT(!m_dialog);
QStringList undetected;
std::vector<std::shared_ptr<Task> > tasks = buildTasks(m_passedFiles, undetected);
if (!undetected.isEmpty()) {
// Since GpgME 1.7.0 Classification is supposed to be reliable
// so we really can't do anything with this data.
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to find encrypted or signed data in one or more files.<nl/>"
"You can manually select what to do with the files now.<nl/>"
"If they contain signed or encrypted data please report a bug (see Help->Report Bug)."));
auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true);
cmd->start();
}
if (tasks.empty()) {
q->emitDoneOrError();
return;
}
Q_ASSERT(m_runnableTasks.empty());
m_runnableTasks.swap(tasks);
std::shared_ptr<TaskCollection> coll(new TaskCollection);
for (const std::shared_ptr<Task> &i : std::as_const(m_runnableTasks)) {
q->connectTask(i);
}
coll->setTasks(m_runnableTasks);
DecryptVerifyFilesDialog dialog{coll};
m_dialog = &dialog;
m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles));
QTimer::singleShot(0, q, SLOT(schedule()));
const auto result = m_dialog->exec();
if (result == QDialog::Rejected) {
q->cancel();
} else if (result == QDialog::Accepted && m_workDir) {
// Without workdir there is nothing to move.
const QDir workdir(m_workDir->path());
const QDir outDir(m_dialog->outputLocation());
bool overWriteAll = false;
qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &fi: workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) {
const auto inpath = fi.absoluteFilePath();
if (fi.isDir()) {
// A directory. Assume that the input was an archive
// and avoid directory merges by trying to find a non
// existing directory.
auto candidate = fi.fileName();
if (candidate.startsWith(QLatin1Char('-'))) {
// Bug in GpgTar Extracts stdout passed archives to a dir named -
candidate = QFileInfo(m_passedFiles.first()).baseName();
}
QString suffix;
QFileInfo ofi;
int i = 0;
do {
ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix));
if (!ofi.exists()) {
break;
}
suffix = QStringLiteral("_%1").arg(++i);
} while (i < 1000);
const auto destPath = ofi.absoluteFilePath();
#ifndef Q_OS_WIN
auto job = KIO::moveAs(QUrl::fromLocalFile(inpath), QUrl::fromLocalFile(destPath));
qCDebug(KLEOPATRA_LOG) << "Moving" << job->srcUrls().front().toLocalFile() << "to" << job->destUrl().toLocalFile();
if (!job->exec()) {
if (job->error() == KIO::ERR_USER_CANCELED) {
break;
}
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18nc("@info", "<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>"
"<para><message>%3</message></para>",
inpath, destPath, job->errorString()));
}
#else
// On Windows, KIO::move does not work for folders when crossing partition boundaries
if (!moveDir(inpath, destPath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18nc("@info", "<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>",
inpath, destPath));
}
#endif
continue;
}
const auto embeddedFileName = getEmbeddedFileName(inpath);
QString outFileName = fi.fileName();
if (!embeddedFileName.isEmpty() && embeddedFileName != fi.fileName()) {
// we switch "Yes" and "No" because Yes is default, but saving with embedded file name could be dangerous
const auto answer = KMessageBox::questionTwoActionsCancel(
m_dialog,
xi18n("Shall the file be saved with the original file name <filename>%1</filename>?", embeddedFileName),
i18n("Use Original File Name?"),
KGuiItem(xi18n("No, Save As <filename>%1</filename>", fi.fileName())),
KGuiItem(xi18n("Yes, Save As <filename>%1</filename>", embeddedFileName)));
if (answer == KMessageBox::Cancel) {
qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath;
continue;
} else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
outFileName = embeddedFileName;
}
}
const auto outpath = outDir.absoluteFilePath(outFileName);
qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath;
const QFileInfo ofi(outpath);
if (ofi.exists()) {
int sel = KMessageBox::Cancel;
if (!overWriteAll) {
sel = KMessageBox::questionTwoActionsCancel(m_dialog,
i18n("The file <b>%1</b> already exists.\n"
"Overwrite?",
outpath),
i18n("Overwrite Existing File?"),
KStandardGuiItem::overwrite(),
KGuiItem(i18n("Overwrite All")),
KStandardGuiItem::cancel());
}
if (sel == KMessageBox::Cancel) {
qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath;
continue;
}
if (sel == KMessageBox::ButtonCode::SecondaryAction) { // Overwrite All
overWriteAll = true;
}
if (!QFile::remove(outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to delete <filename>%1</filename>.",
outpath));
continue;
}
}
if (!QFile::rename(inpath, outpath)) {
reportError(makeGnuPGError(GPG_ERR_GENERAL),
xi18n("Failed to move <filename>%1</filename> to <filename>%2</filename>.",
inpath, outpath));
}
}
}
q->emitDoneOrError();
}
QVector<AutoDecryptVerifyFilesController::Private::CryptoFile> AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files)
{
const auto isSignature = [](int classification) -> bool {
- return mayBeDetachedSignature(classification)
- || mayBeOpaqueSignature(classification)
- || (classification & Class::TypeMask) == Class::ClearsignedMessage;
+ return mayBeDetachedSignature(classification) //
+ || mayBeOpaqueSignature(classification) //
+ || (classification & Class::TypeMask) == Class::ClearsignedMessage;
};
QVector<CryptoFile> out;
for (const auto &file : files) {
CryptoFile cFile;
cFile.fileName = file;
cFile.baseName = stripSuffix(file);
cFile.classification = classify(file);
cFile.protocol = findProtocol(cFile.classification);
auto it = std::find_if(out.begin(), out.end(),
[&cFile](const CryptoFile &other) {
return other.protocol == cFile.protocol
&& other.baseName == cFile.baseName;
});
if (it != out.end()) {
// If we found a file with the same basename, make sure that encrypted
// file is before the signature file, so that we first decrypt and then
// verify
if (isSignature(cFile.classification) && isCipherText(it->classification)) {
out.insert(it + 1, cFile);
} else if (isCipherText(cFile.classification) && isSignature(it->classification)) {
out.insert(it, cFile);
} else {
// both are signatures or both are encrypted files, in which
// case order does not matter
out.insert(it, cFile);
}
} else {
out.push_back(cFile);
}
}
return out;
}
static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol)
{
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
#else
return false;
#endif
}
std::vector< std::shared_ptr<Task> > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected)
{
// sort files so that we make sure we first decrypt and then verify
QVector<CryptoFile> cryptoFiles = classifyAndSortFiles(fileNames);
std::vector<std::shared_ptr<Task> > tasks;
for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) {
auto &cFile = (*it);
QFileInfo fi(cFile.fileName);
qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification);
if (!fi.isReadable()) {
reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT),
xi18n("Cannot open <filename>%1</filename> for reading.", cFile.fileName));
continue;
}
if (mayBeAnyCertStoreType(cFile.classification)) {
// Trying to verify a certificate. Possible because extensions are often similar
// for PGP Keys.
reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT),
xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", cFile.fileName));
qCDebug(KLEOPATRA_LOG) << "reported error";
continue;
}
// We can't reliably detect CMS detached signatures, so we will try to do
// our best to use the current file as a detached signature and fallback to
// opaque signature otherwise.
if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) {
// First, see if previous task was a decryption task for the same file
// and "pipe" it's output into our input
std::shared_ptr<Input> input;
bool prepend = false;
if (it != cryptoFiles.begin()) {
const auto prev = it - 1;
if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) {
input = Input::createFromOutput(prev->output);
prepend = true;
}
}
if (!input) {
if (QFile::exists(cFile.baseName)) {
input = Input::createFromFile(cFile.baseName);
}
}
if (input) {
qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(cFile.fileName));
t->setSignedData(input);
t->setProtocol(cFile.protocol);
if (prepend) {
// Put the verify task BEFORE the decrypt task in the tasks queue,
// because the tasks are executed in reverse order!
tasks.insert(tasks.end() - 1, t);
} else {
tasks.push_back(t);
}
continue;
} else {
// No signed data, maybe not a detached signature
}
}
if (isDetachedSignature(cFile.classification)) {
// Detached signature, try to find data or ask the user.
QString signedDataFileName = cFile.baseName;
if (!QFile::exists(signedDataFileName)) {
signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with the signature <filename>%1</filename>", fi.fileName()),
fi.path());
}
if (signedDataFileName.isEmpty()) {
qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted.";
} else {
qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(cFile.fileName));
t->setSignedData(Input::createFromFile(signedDataFileName));
t->setProtocol(cFile.protocol);
tasks.push_back(t);
}
continue;
}
if (!mayBeAnyMessageType(cFile.classification)) {
// Not a Message? Maybe there is a signature for this file?
const auto signatures = findSignatures(cFile.fileName);
bool foundSig = false;
if (!signatures.empty()) {
for (const QString &sig : signatures) {
const auto classification = classify(sig);
qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName
<< "Classification: " << classification;
const auto proto = findProtocol(classification);
if (proto == GpgME::UnknownProtocol) {
qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess.";
continue;
}
foundSig = true;
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(sig));
t->setSignedData(Input::createFromFile(cFile.fileName));
t->setProtocol(proto);
tasks.push_back(t);
}
}
if (!foundSig) {
undetected << cFile.fileName;
qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected.";
}
} else {
const FileOperationsPreferences fileOpSettings;
// Any Message type so we have input and output.
const auto input = Input::createFromFile(cFile.fileName);
std::shared_ptr<ArchiveDefinition> ad;
if (fileOpSettings.autoExtractArchives()) {
const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions();
ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName);
}
if (fileOpSettings.dontUseTmpDir()) {
if (!m_workDir) {
m_workDir = std::make_unique<QTemporaryDir>(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX"));
}
if (!m_workDir->isValid()) {
qCDebug(KLEOPATRA_LOG) << heuristicBaseDirectory(fileNames) << "not a valid temporary directory.";
m_workDir.reset();
}
}
if (!m_workDir) {
m_workDir = std::make_unique<QTemporaryDir>();
}
qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory.";
const auto wd = QDir(m_workDir->path());
std::shared_ptr<Output> output;
if (ad) {
if ((ad->id() == QLatin1String{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) {
// we don't need an output
} else {
output = ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd);
}
} else {
output = Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false);
}
// If this might be opaque CMS signature, then try that. We already handled
// detached CMS signature above
const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification);
if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) {
qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask";
std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask);
t->setInput(input);
if (output) {
t->setOutput(output);
}
t->setProtocol(cFile.protocol);
if (ad) {
t->setExtractArchive(true);
t->setInputFile(cFile.fileName);
if (output) {
t->setOutputDirectory(m_workDir->path());
} else {
// make gpgtar extract to a subfolder of the work directory based on the input file name
const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
}
}
tasks.push_back(t);
} else {
// Any message. That is not an opaque signature needs to be
// decrypted. Verify we always do because we can't know if
// an encrypted message is also signed.
qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask";
std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask);
t->setInput(input);
if (output) {
t->setOutput(output);
}
t->setProtocol(cFile.protocol);
if (ad) {
t->setExtractArchive(true);
t->setInputFile(cFile.fileName);
if (output) {
t->setOutputDirectory(m_workDir->path());
} else {
// make gpgtar extract to a subfolder of the work directory based on the input file name
const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
}
}
cFile.output = output;
tasks.push_back(t);
}
}
}
return tasks;
}
void AutoDecryptVerifyFilesController::setFiles(const QStringList &files)
{
d->m_passedFiles = files;
}
AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent) :
DecryptVerifyFilesController(parent), d(new Private(this))
{
}
AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent) :
DecryptVerifyFilesController(ctx, parent), d(new Private(this))
{
}
AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController()
{
qCDebug(KLEOPATRA_LOG);
}
void AutoDecryptVerifyFilesController::start()
{
d->exec();
}
void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op)
{
d->m_operation = op;
}
DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const
{
return d->m_operation;
}
void AutoDecryptVerifyFilesController::Private::cancelAllTasks()
{
// we just kill all runnable tasks - this will not result in
// signal emissions.
m_runnableTasks.clear();
// a cancel() will result in a call to
if (m_runningTask) {
m_runningTask->cancel();
}
}
void AutoDecryptVerifyFilesController::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
try {
d->m_errorDetected = true;
if (d->m_dialog) {
d->m_dialog->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
{
Q_ASSERT(task);
Q_UNUSED(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
d->m_completedTasks.push_back(d->m_runningTask);
d->m_runningTask.reset();
if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) {
d->m_results.push_back(dvr);
}
QTimer::singleShot(0, this, SLOT(schedule()));
}
#include "moc_autodecryptverifyfilescontroller.cpp"
diff --git a/src/crypto/createchecksumscontroller.cpp b/src/crypto/createchecksumscontroller.cpp
index 17fa27bbd..1e29f960f 100644
--- a/src/crypto/createchecksumscontroller.cpp
+++ b/src/crypto/createchecksumscontroller.cpp
@@ -1,610 +1,610 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/createchecksumscontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "createchecksumscontroller.h"
#include "checksumsutils_p.h"
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <Libkleo/Stl_Util>
#include <Libkleo/ChecksumDefinition>
#include <Libkleo/Classify>
#include <KLocalizedString>
#include <QTemporaryFile>
#include <KConfigGroup>
#include <KSharedConfig>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QListWidget>
#include <QVBoxLayout>
#include <QPointer>
#include <QFileInfo>
#include <QThread>
#include <QMutex>
#include <QProgressDialog>
#include <QDir>
#include <QProcess>
#include <gpg-error.h>
#include <deque>
#include <map>
#include <limits>
#include <functional>
using namespace Kleo;
using namespace Kleo::Crypto;
namespace
{
class ResultDialog : public QDialog
{
Q_OBJECT
public:
ResultDialog(const QStringList &created, const QStringList &errors, QWidget *parent = nullptr, Qt::WindowFlags f = {})
: QDialog(parent, f),
createdLB(created.empty()
? i18nc("@info", "No checksum files have been created.")
: i18nc("@info", "These checksum files have been successfully created:"), this),
createdLW(this),
errorsLB(errors.empty()
- ? i18nc("@info", "There were no errors.")
+ ? i18nc("@info", "There were no errors.") //
: i18nc("@info", "The following errors were encountered:"), this),
errorsLW(this),
buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, this),
vlay(this)
{
KDAB_SET_OBJECT_NAME(createdLB);
KDAB_SET_OBJECT_NAME(createdLW);
KDAB_SET_OBJECT_NAME(errorsLB);
KDAB_SET_OBJECT_NAME(errorsLW);
KDAB_SET_OBJECT_NAME(buttonBox);
KDAB_SET_OBJECT_NAME(vlay);
createdLW.addItems(created);
QRect r;
for (int i = 0; i < created.size(); ++i) {
r = r.united(createdLW.visualRect(createdLW.model()->index(0, i)));
}
createdLW.setMinimumWidth(qMin(1024, r.width() + 4 * createdLW.frameWidth()));
errorsLW.addItems(errors);
vlay.addWidget(&createdLB);
vlay.addWidget(&createdLW, 1);
vlay.addWidget(&errorsLB);
vlay.addWidget(&errorsLW, 1);
vlay.addWidget(&buttonBox);
if (created.empty()) {
createdLW.hide();
}
if (errors.empty()) {
errorsLW.hide();
}
connect(&buttonBox, &QDialogButtonBox::accepted, this, &ResultDialog::accept);
connect(&buttonBox, &QDialogButtonBox::rejected, this, &ResultDialog::reject);
readConfig();
}
~ResultDialog() override
{
writeConfig();
}
void readConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "ResultDialog");
const QSize size = dialog.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
resize(size);
}
}
void writeConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), "ResultDialog");
dialog.writeEntry("Size", size());
dialog.sync();
}
private:
QLabel createdLB;
QListWidget createdLW;
QLabel errorsLB;
QListWidget errorsLW;
QDialogButtonBox buttonBox;
QVBoxLayout vlay;
};
}
static QStringList fs_sort(QStringList l)
{
std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) {
return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
});
return l;
}
static QStringList fs_intersect(QStringList l1, QStringList l2)
{
fs_sort(l1);
fs_sort(l2);
QStringList result;
std::set_intersection(l1.begin(), l1.end(),
l2.begin(), l2.end(),
std::back_inserter(result),
[](const QString &lhs, const QString &rhs) {
return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0;
});
return result;
}
class CreateChecksumsController::Private : public QThread
{
Q_OBJECT
friend class ::Kleo::Crypto::CreateChecksumsController;
CreateChecksumsController *const q;
public:
explicit Private(CreateChecksumsController *qq);
~Private() override;
Q_SIGNALS:
void progress(int, int, const QString &);
private:
void slotOperationFinished()
{
#ifndef QT_NO_PROGRESSDIALOG
if (progressDialog) {
progressDialog->setValue(progressDialog->maximum());
progressDialog->close();
}
#endif // QT_NO_PROGRESSDIALOG
auto const dlg = new ResultDialog(created, errors);
dlg->setAttribute(Qt::WA_DeleteOnClose);
q->bringToForeground(dlg);
if (!errors.empty())
q->setLastError(gpg_error(GPG_ERR_GENERAL),
errors.join(QLatin1Char('\n')));
q->emitDoneOrError();
}
void slotProgress(int current, int total, const QString &what)
{
qCDebug(KLEOPATRA_LOG) << "progress: " << current << "/" << total << ": " << qPrintable(what);
#ifndef QT_NO_PROGRESSDIALOG
if (!progressDialog) {
return;
}
progressDialog->setMaximum(total);
progressDialog->setValue(current);
progressDialog->setLabelText(what);
#endif // QT_NO_PROGRESSDIALOG
}
private:
void run() override;
private:
#ifndef QT_NO_PROGRESSDIALOG
QPointer<QProgressDialog> progressDialog;
#endif
mutable QMutex mutex;
const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions;
std::shared_ptr<ChecksumDefinition> checksumDefinition;
QStringList files;
QStringList errors, created;
bool allowAddition;
volatile bool canceled;
};
CreateChecksumsController::Private::Private(CreateChecksumsController *qq)
: q(qq),
#ifndef QT_NO_PROGRESSDIALOG
progressDialog(),
#endif
mutex(),
checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()),
checksumDefinition(ChecksumDefinition::getDefaultChecksumDefinition(checksumDefinitions)),
files(),
errors(),
created(),
allowAddition(false),
canceled(false)
{
connect(this, SIGNAL(progress(int,int,QString)),
q, SLOT(slotProgress(int,int,QString)));
connect(this, &Private::progress,
q, &Controller::progress);
connect(this, SIGNAL(finished()),
q, SLOT(slotOperationFinished()));
}
CreateChecksumsController::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
}
CreateChecksumsController::CreateChecksumsController(QObject *p)
: Controller(p), d(new Private(this))
{
}
CreateChecksumsController::CreateChecksumsController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p)
: Controller(ctx, p), d(new Private(this))
{
}
CreateChecksumsController::~CreateChecksumsController()
{
qCDebug(KLEOPATRA_LOG);
}
void CreateChecksumsController::setFiles(const QStringList &files)
{
kleo_assert(!d->isRunning());
kleo_assert(!files.empty());
const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(d->checksumDefinitions);
if (!std::all_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(patterns)) &&
!std::none_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(patterns))) {
throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Create Checksums: input files must be either all checksum files or all files to be checksummed, not a mixture of both."));
}
const QMutexLocker locker(&d->mutex);
d->files = files;
}
void CreateChecksumsController::setAllowAddition(bool allow)
{
kleo_assert(!d->isRunning());
const QMutexLocker locker(&d->mutex);
d->allowAddition = allow;
}
bool CreateChecksumsController::allowAddition() const
{
const QMutexLocker locker(&d->mutex);
return d->allowAddition;
}
void CreateChecksumsController::start()
{
{
const QMutexLocker locker(&d->mutex);
#ifndef QT_NO_PROGRESSDIALOG
d->progressDialog = new QProgressDialog(i18n("Initializing..."), i18n("Cancel"), 0, 0);
applyWindowID(d->progressDialog);
d->progressDialog->setAttribute(Qt::WA_DeleteOnClose);
d->progressDialog->setMinimumDuration(1000);
d->progressDialog->setWindowTitle(i18nc("@title:window", "Create Checksum Progress"));
connect(d->progressDialog.data(), &QProgressDialog::canceled, this, &CreateChecksumsController::cancel);
#endif // QT_NO_PROGRESSDIALOG
d->canceled = false;
d->errors.clear();
d->created.clear();
}
d->start();
}
void CreateChecksumsController::cancel()
{
qCDebug(KLEOPATRA_LOG);
const QMutexLocker locker(&d->mutex);
d->canceled = true;
}
namespace
{
struct Dir {
QDir dir;
QString sumFile;
QStringList inputFiles;
quint64 totalSize;
std::shared_ptr<ChecksumDefinition> checksumDefinition;
};
}
static QStringList remove_checksum_files(QStringList l, const std::vector<QRegularExpression> &rxs)
{
QStringList::iterator end = l.end();
for (const auto &rx : rxs) {
end = std::remove_if(l.begin(), end,
[rx](const QString &str) {
return rx.match(str).hasMatch();
});
}
l.erase(end, l.end());
return l;
}
static quint64 aggregate_size(const QDir &dir, const QStringList &files)
{
quint64 n = 0;
for (const QString &file : files) {
n += QFileInfo(dir.absoluteFilePath(file)).size();
}
return n;
}
static std::vector<Dir> find_dirs_by_sum_files(const QStringList &files, bool allowAddition,
const std::function<void(int)> &progress,
const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions)
{
const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(checksumDefinitions);
std::vector<Dir> dirs;
dirs.reserve(files.size());
int i = 0;
for (const QString &file : files) {
const QFileInfo fi(file);
const QDir dir = fi.dir();
const QStringList entries = remove_checksum_files(dir.entryList(QDir::Files), patterns);
QStringList inputFiles;
if (allowAddition) {
inputFiles = entries;
} else {
const std::vector<ChecksumsUtils::File> parsed = ChecksumsUtils::parse_sum_file(fi.absoluteFilePath());
QStringList oldInputFiles;
oldInputFiles.reserve(parsed.size());
std::transform(parsed.cbegin(), parsed.cend(), std::back_inserter(oldInputFiles),
std::mem_fn(&ChecksumsUtils::File::name));
inputFiles = fs_intersect(oldInputFiles, entries);
}
const Dir item = {
dir,
fi.fileName(),
inputFiles,
aggregate_size(dir, inputFiles),
- ChecksumsUtils::filename2definition(fi.fileName(), checksumDefinitions)
+ ChecksumsUtils::filename2definition(fi.fileName(), checksumDefinitions),
};
dirs.push_back(item);
if (progress) {
progress(++i);
}
}
return dirs;
}
namespace
{
struct less_dir {
bool operator()(const QDir &lhs, const QDir &rhs) const
{
return QString::compare(lhs.absolutePath(), rhs.absolutePath(), ChecksumsUtils::fs_cs) < 0;
}
};
}
static std::vector<Dir> find_dirs_by_input_files(const QStringList &files, const std::shared_ptr<ChecksumDefinition> &checksumDefinition, bool allowAddition,
const std::function<void(int)> &progress,
const std::vector< std::shared_ptr<ChecksumDefinition> > &checksumDefinitions)
{
Q_UNUSED(allowAddition)
if (!checksumDefinition) {
return std::vector<Dir>();
}
const std::vector<QRegularExpression> patterns = ChecksumsUtils::get_patterns(checksumDefinitions);
std::map<QDir, QStringList, less_dir> dirs2files;
// Step 1: sort files by the dir they're contained in:
std::deque<QString> inputs(files.begin(), files.end());
int i = 0;
while (!inputs.empty()) {
const QString file = inputs.front();
inputs.pop_front();
const QFileInfo fi(file);
if (fi.isDir()) {
QDir dir(file);
dirs2files[ dir ] = remove_checksum_files(dir.entryList(QDir::Files), patterns);
const auto entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
std::transform(entryList.cbegin(), entryList.cend(),
std::inserter(inputs, inputs.begin()),
[&dir](const QString &entry) {
return dir.absoluteFilePath(entry);
});
} else {
dirs2files[fi.dir()].push_back(file);
}
if (progress) {
progress(++i);
}
}
// Step 2: convert into vector<Dir>:
std::vector<Dir> dirs;
dirs.reserve(dirs2files.size());
for (auto it = dirs2files.begin(), end = dirs2files.end(); it != end; ++it) {
const QStringList inputFiles = remove_checksum_files(it->second, patterns);
if (inputFiles.empty()) {
continue;
}
const Dir dir = {
it->first,
checksumDefinition->outputFileName(),
inputFiles,
aggregate_size(it->first, inputFiles),
- checksumDefinition
+ checksumDefinition,
};
dirs.push_back(dir);
if (progress) {
progress(++i);
}
}
return dirs;
}
static QString process(const Dir &dir, bool *fatal)
{
const QString absFilePath = dir.dir.absoluteFilePath(dir.sumFile);
QTemporaryFile out;
QProcess p;
if (!out.open()) {
return QStringLiteral("Failed to open Temporary file.");
}
p.setWorkingDirectory(dir.dir.absolutePath());
p.setStandardOutputFile(out.fileName());
const QString program = dir.checksumDefinition->createCommand();
dir.checksumDefinition->startCreateCommand(&p, dir.inputFiles);
p.waitForFinished(-1);
qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode();
if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) {
if (fatal && p.error() == QProcess::FailedToStart) {
*fatal = true;
}
if (p.error() == QProcess::UnknownError)
return i18n("Error while running %1: %2", program,
QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData()));
else {
return i18n("Failed to execute %1: %2", program, p.errorString());
}
}
QFileInfo fi(absFilePath);
if (!(fi.exists() && !QFile::remove(absFilePath)) && QFile::copy(out.fileName(), absFilePath)) {
return QString();
}
return xi18n("Failed to overwrite <filename>%1</filename>.", dir.sumFile);
}
namespace
{
static QDebug operator<<(QDebug s, const Dir &dir)
{
return s << "Dir(" << dir.dir << "->" << dir.sumFile << "<-(" << dir.totalSize << ')' << dir.inputFiles << ")\n";
}
}
void CreateChecksumsController::Private::run()
{
QMutexLocker locker(&mutex);
const QStringList files = this->files;
const std::vector< std::shared_ptr<ChecksumDefinition> > checksumDefinitions = this->checksumDefinitions;
const std::shared_ptr<ChecksumDefinition> checksumDefinition = this->checksumDefinition;
const bool allowAddition = this->allowAddition;
locker.unlock();
QStringList errors;
QStringList created;
if (!checksumDefinition) {
errors.push_back(i18n("No checksum programs defined."));
locker.relock();
this->errors = errors;
return;
} else {
qCDebug(KLEOPATRA_LOG) << "using checksum-definition" << checksumDefinition->id();
}
//
// Step 1: build a list of work to do (no progress):
//
const QString scanning = i18n("Scanning directories...");
Q_EMIT progress(0, 0, scanning);
const bool haveSumFiles = std::all_of(files.cbegin(), files.cend(), ChecksumsUtils::matches_any(ChecksumsUtils::get_patterns(checksumDefinitions)));
const auto progressCb = [this, &scanning](int c) { Q_EMIT progress(c, 0, scanning); };
const std::vector<Dir> dirs = haveSumFiles
? find_dirs_by_sum_files(files, allowAddition, progressCb, checksumDefinitions)
: find_dirs_by_input_files(files, checksumDefinition, allowAddition, progressCb, checksumDefinitions);
for (const Dir &dir : dirs) {
qCDebug(KLEOPATRA_LOG) << dir;
}
if (!canceled) {
Q_EMIT progress(0, 0, i18n("Calculating total size..."));
const quint64 total = kdtools::accumulate_transform(dirs.cbegin(), dirs.cend(),
std::mem_fn(&Dir::totalSize),
Q_UINT64_C(0));
if (!canceled) {
//
// Step 2: perform work (with progress reporting):
//
// re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...)
const quint64 factor = total / std::numeric_limits<int>::max() + 1;
quint64 done = 0;
for (const Dir &dir : dirs) {
Q_EMIT progress(done / factor, total / factor,
i18n("Checksumming (%2) in %1", dir.checksumDefinition->label(), dir.dir.path()));
bool fatal = false;
const QString error = process(dir, &fatal);
if (!error.isEmpty()) {
errors.push_back(error);
} else {
created.push_back(dir.dir.absoluteFilePath(dir.sumFile));
}
done += dir.totalSize;
if (fatal || canceled) {
break;
}
}
Q_EMIT progress(done / factor, total / factor, i18n("Done."));
}
}
locker.relock();
this->errors = errors;
this->created = created;
// mutex unlocked by QMutexLocker
}
#include "moc_createchecksumscontroller.cpp"
#include "createchecksumscontroller.moc"
diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp
index b18b416bc..53ddc29a1 100644
--- a/src/crypto/decryptverifyfilescontroller.cpp
+++ b/src/crypto/decryptverifyfilescontroller.cpp
@@ -1,445 +1,444 @@
/* -*- mode: c++; c-basic-offset:4 -*-
decryptverifyfilescontroller.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 "decryptverifyfilescontroller.h"
#include <crypto/gui/decryptverifyoperationwidget.h>
#include <crypto/gui/decryptverifyfileswizard.h>
#include <crypto/decryptverifytask.h>
#include <crypto/taskcollection.h>
#include <Libkleo/GnuPG>
#include <utils/path-helper.h>
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <utils/archivedefinition.h>
#include <Libkleo/Classify>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QPointer>
#include <QTimer>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
class DecryptVerifyFilesController::Private
{
DecryptVerifyFilesController *const q;
public:
static std::shared_ptr<AbstractDecryptVerifyTask> taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr<OverwritePolicy> &overwritePolicy);
explicit Private(DecryptVerifyFilesController *qq);
void slotWizardOperationPrepared();
void slotWizardCanceled();
void schedule();
void prepareWizardFromPassedFiles();
std::vector<std::shared_ptr<Task> > buildTasks(const QStringList &, const std::shared_ptr<OverwritePolicy> &);
void ensureWizardCreated();
void ensureWizardVisible();
void reportError(int err, const QString &details)
{
q->setLastError(err, details);
q->emitDoneOrError();
}
void cancelAllTasks();
QStringList m_passedFiles, m_filesAfterPreparation;
QPointer<DecryptVerifyFilesWizard> m_wizard;
std::vector<std::shared_ptr<const DecryptVerifyResult> > m_results;
std::vector<std::shared_ptr<Task> > m_runnableTasks, m_completedTasks;
std::shared_ptr<Task> m_runningTask;
bool m_errorDetected;
DecryptVerifyOperation m_operation;
};
// static
std::shared_ptr<AbstractDecryptVerifyTask> DecryptVerifyFilesController::Private::taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr<OverwritePolicy> &overwritePolicy)
{
kleo_assert(w);
std::shared_ptr<AbstractDecryptVerifyTask> task;
switch (w->mode()) {
case DecryptVerifyOperationWidget::VerifyDetachedWithSignature: {
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(fileName));
t->setSignedData(Input::createFromFile(w->signedDataFileName()));
task = t;
kleo_assert(fileName == w->inputFileName());
}
break;
case DecryptVerifyOperationWidget::VerifyDetachedWithSignedData: {
std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
t->setInput(Input::createFromFile(w->inputFileName()));
t->setSignedData(Input::createFromFile(fileName));
task = t;
kleo_assert(fileName == w->signedDataFileName());
}
break;
case DecryptVerifyOperationWidget::DecryptVerifyOpaque: {
const unsigned int classification = classify(fileName);
qCDebug(KLEOPATRA_LOG) << "classified" << fileName << "as" << printableClassification(classification);
const std::shared_ptr<ArchiveDefinition> ad = w->selectedArchiveDefinition();
- const Protocol proto =
- isOpenPGP(classification) ? OpenPGP :
- isCMS(classification) ? CMS :
- ad /* _needs_ the info */ ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS")) :
- /* else we don't care */ UnknownProtocol;
+ const Protocol proto = isOpenPGP(classification) ? OpenPGP
+ : isCMS(classification) ? CMS
+ : ad ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS"))
+ : UnknownProtocol;
const std::shared_ptr<Input> input = Input::createFromFile(fileName);
- const std::shared_ptr<Output> output =
- ad ? ad->createOutputFromUnpackCommand(proto, fileName, outDir) :
- /*else*/ Output::createFromFile(outDir.absoluteFilePath(outputFileName(QFileInfo(fileName).fileName())), overwritePolicy);
+ const std::shared_ptr<Output> output = ad
+ ? ad->createOutputFromUnpackCommand(proto, fileName, outDir)
+ : Output::createFromFile(outDir.absoluteFilePath(outputFileName(QFileInfo(fileName).fileName())), overwritePolicy);
if (mayBeCipherText(classification)) {
qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask";
std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask);
t->setInput(input);
t->setOutput(output);
task = t;
} else {
qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask";
std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask);
t->setInput(input);
t->setOutput(output);
task = t;
}
kleo_assert(fileName == w->inputFileName());
}
break;
}
task->autodetectProtocolFromInput();
return task;
}
DecryptVerifyFilesController::Private::Private(DecryptVerifyFilesController *qq) : q(qq), m_errorDetected(false), m_operation(DecryptVerify)
{
qRegisterMetaType<VerificationResult>();
}
void DecryptVerifyFilesController::Private::slotWizardOperationPrepared()
{
ensureWizardCreated();
std::vector<std::shared_ptr<Task> > tasks = buildTasks(m_filesAfterPreparation, std::make_shared<OverwritePolicy>(m_wizard, OverwritePolicy::MultipleFiles));
if (tasks.empty()) {
reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), i18n("No usable inputs found"));
}
kleo_assert(m_runnableTasks.empty());
m_runnableTasks.swap(tasks);
std::shared_ptr<TaskCollection> coll(new TaskCollection);
for (const auto &i: m_runnableTasks) {
q->connectTask(i);
}
coll->setTasks(m_runnableTasks);
m_wizard->setTaskCollection(coll);
QTimer::singleShot(0, q, SLOT(schedule()));
}
void DecryptVerifyFilesController::Private::slotWizardCanceled()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
q->cancel();
q->emitDoneOrError();
}
void DecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
{
Q_ASSERT(task);
Q_UNUSED(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
d->m_completedTasks.push_back(d->m_runningTask);
d->m_runningTask.reset();
if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) {
d->m_results.push_back(dvr);
}
QTimer::singleShot(0, this, SLOT(schedule()));
}
void DecryptVerifyFilesController::Private::schedule()
{
if (!m_runningTask && !m_runnableTasks.empty()) {
const std::shared_ptr<Task> t = m_runnableTasks.back();
m_runnableTasks.pop_back();
t->start();
m_runningTask = t;
}
if (!m_runningTask) {
kleo_assert(m_runnableTasks.empty());
for (const auto &i: m_results) {
Q_EMIT q->verificationResult(i->verificationResult());
}
q->emitDoneOrError();
}
}
void DecryptVerifyFilesController::Private::ensureWizardCreated()
{
if (m_wizard) {
return;
}
std::unique_ptr<DecryptVerifyFilesWizard> w(new DecryptVerifyFilesWizard);
w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files"));
w->setAttribute(Qt::WA_DeleteOnClose);
connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection);
connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection);
m_wizard = w.release();
}
namespace
{
struct FindExtension {
const QString ext;
const Protocol proto;
FindExtension(const QString &ext, Protocol proto) : ext(ext), proto(proto) {}
bool operator()(const std::shared_ptr<ArchiveDefinition> &ad) const
{
qCDebug(KLEOPATRA_LOG) << " considering" << (ad ? ad->label() : QStringLiteral("<null>")) << "for" << ext;
bool result;
if (proto == UnknownProtocol) {
result = ad && (ad->extensions(OpenPGP).contains(ext, Qt::CaseInsensitive) || ad->extensions(CMS).contains(ext, Qt::CaseInsensitive));
} else {
result = ad && ad->extensions(proto).contains(ext, Qt::CaseInsensitive);
}
qCDebug(KLEOPATRA_LOG) << (result ? " -> matches" : " -> doesn't match");
return result;
}
};
}
std::shared_ptr<ArchiveDefinition> DecryptVerifyFilesController::pick_archive_definition(GpgME::Protocol proto, const std::vector< std::shared_ptr<ArchiveDefinition> > &ads, const QString &filename)
{
const QFileInfo fi(outputFileName(filename));
QString extension = fi.completeSuffix();
if (extension == QLatin1String("out")) { // added by outputFileName() -> useless
return std::shared_ptr<ArchiveDefinition>();
}
if (extension.endsWith(QLatin1String(".out"))) { // added by outputFileName() -> remove
extension.chop(4);
}
for (;;) {
const auto it
= std::find_if(ads.begin(), ads.end(), FindExtension(extension, proto));
if (it != ads.end()) {
return *it;
}
const int idx = extension.indexOf(QLatin1Char('.'));
if (idx < 0) {
return std::shared_ptr<ArchiveDefinition>();
}
extension = extension.mid(idx + 1);
}
}
void DecryptVerifyFilesController::Private::prepareWizardFromPassedFiles()
{
ensureWizardCreated();
const std::vector< std::shared_ptr<ArchiveDefinition> > archiveDefinitions = ArchiveDefinition::getArchiveDefinitions();
unsigned int counter = 0;
for (const auto &fname: std::as_const(m_passedFiles)) {
kleo_assert(!fname.isEmpty());
const unsigned int classification = classify(fname);
const Protocol proto = findProtocol(classification);
if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification) || mayBeDetachedSignature(classification)) {
DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++);
kleo_assert(op != nullptr);
op->setArchiveDefinitions(archiveDefinitions);
const QString signedDataFileName = findSignedData(fname);
// this breaks opaque signatures whose source files still
// happen to exist in the same directory. Until we have
// content-based classification, this is the most unlikely
// case, so that's the case we break. ### FIXME remove when content-classify is done
if (mayBeDetachedSignature(classification) && !signedDataFileName.isEmpty()) {
op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature);
}
// ### end FIXME
else if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification)) {
op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname));
} else {
op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature);
}
op->setInputFileName(fname);
op->setSignedDataFileName(signedDataFileName);
m_filesAfterPreparation << fname;
} else {
// probably the signed data file was selected:
const QStringList signatures = findSignatures(fname);
if (signatures.empty()) {
// We are assuming this is a detached signature file, but
// there were no signature files for it. Let's guess it's encrypted after all.
// ### FIXME once we have a proper heuristic for this, this should move into
// classify() and/or classifyContent()
DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++);
kleo_assert(op != nullptr);
op->setArchiveDefinitions(archiveDefinitions);
op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname));
op->setInputFileName(fname);
m_filesAfterPreparation << fname;
} else {
for (const auto &s: signatures) {
DecryptVerifyOperationWidget *op = m_wizard->operationWidget(counter++);
kleo_assert(op != nullptr);
op->setArchiveDefinitions(archiveDefinitions);
op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignedData);
op->setInputFileName(s);
op->setSignedDataFileName(fname);
m_filesAfterPreparation << fname;
}
}
}
}
m_wizard->setOutputDirectory(heuristicBaseDirectory(m_passedFiles));
return;
}
std::vector< std::shared_ptr<Task> > DecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, const std::shared_ptr<OverwritePolicy> &overwritePolicy)
{
const bool useOutDir = m_wizard->useOutputDirectory();
const QFileInfo outDirInfo(m_wizard->outputDirectory());
kleo_assert(!useOutDir || outDirInfo.isDir());
const QDir outDir(outDirInfo.absoluteFilePath());
kleo_assert(!useOutDir || outDir.exists());
std::vector<std::shared_ptr<Task> > tasks;
for (int i = 0, end = fileNames.size(); i != end; ++i)
try {
const QDir fileDir = QFileInfo(fileNames[i]).absoluteDir();
kleo_assert(fileDir.exists());
tasks.push_back(taskFromOperationWidget(m_wizard->operationWidget(static_cast<unsigned int>(i)), fileNames[i], useOutDir ? outDir : fileDir, overwritePolicy));
} catch (const GpgME::Exception &e) {
tasks.push_back(Task::makeErrorTask(e.error(), QString::fromLocal8Bit(e.what()), fileNames[i]));
}
return tasks;
}
void DecryptVerifyFilesController::setFiles(const QStringList &files)
{
d->m_passedFiles = files;
}
void DecryptVerifyFilesController::Private::ensureWizardVisible()
{
ensureWizardCreated();
q->bringToForeground(m_wizard);
}
DecryptVerifyFilesController::DecryptVerifyFilesController(QObject *parent) : Controller(parent), d(new Private(this))
{
}
DecryptVerifyFilesController::DecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent) : Controller(ctx, parent), d(new Private(this))
{
}
DecryptVerifyFilesController::~DecryptVerifyFilesController()
{
qCDebug(KLEOPATRA_LOG);
}
void DecryptVerifyFilesController::start()
{
d->prepareWizardFromPassedFiles();
d->ensureWizardVisible();
}
void DecryptVerifyFilesController::setOperation(DecryptVerifyOperation op)
{
d->m_operation = op;
}
DecryptVerifyOperation DecryptVerifyFilesController::operation() const
{
return d->m_operation;
}
void DecryptVerifyFilesController::Private::cancelAllTasks()
{
// we just kill all runnable tasks - this will not result in
// signal emissions.
m_runnableTasks.clear();
// a cancel() will result in a call to
if (m_runningTask) {
m_runningTask->cancel();
}
}
void DecryptVerifyFilesController::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
try {
d->m_errorDetected = true;
if (d->m_wizard) {
d->m_wizard->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
#include "moc_decryptverifyfilescontroller.cpp"
diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp
index 8fc925169..f757cc449 100644
--- a/src/crypto/decryptverifytask.cpp
+++ b/src/crypto/decryptverifytask.cpp
@@ -1,1788 +1,1788 @@
/* -*- mode: c++; c-basic-offset:4 -*-
decryptverifytask.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 "decryptverifytask.h"
#include <QGpgME/Protocol>
#include <QGpgME/VerifyOpaqueJob>
#include <QGpgME/VerifyDetachedJob>
#include <QGpgME/DecryptJob>
#include <QGpgME/DecryptVerifyJob>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/DecryptVerifyArchiveJob>
#endif
#include <Libkleo/AuditLogEntry>
#include <Libkleo/Compliance>
#include <Libkleo/Dn>
#include <Libkleo/KleoException>
#include <Libkleo/Stl_Util>
#include <Libkleo/KeyCache>
#include <Libkleo/Predicates>
#include <Libkleo/Formatting>
#include <Libkleo/Classify>
#include <utils/detail_p.h>
#include <utils/input.h>
#include <utils/output.h>
#include <utils/kleo_assert.h>
#include <Libkleo/GnuPG>
#include <KMime/HeaderParsing>
#include <gpgme++/error.h>
#include <gpgme++/key.h>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/context.h>
#include <gpg-error.h>
#include "kleopatra_debug.h"
#include <KFileUtils>
#include <KLocalizedString>
#include <QLocale>
#include <QByteArray>
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QIODevice>
#include <QStringList>
#include <QTextDocument> // Qt::escape
#include <algorithm>
#include <sstream>
using namespace Kleo::Crypto;
using namespace Kleo;
using namespace GpgME;
using namespace KMime::Types;
namespace
{
static AuditLogEntry auditLogFromSender(QObject *sender)
{
return AuditLogEntry::fromJob(qobject_cast<const QGpgME::Job *>(sender));
}
static bool addrspec_equal(const AddrSpec &lhs, const AddrSpec &rhs, Qt::CaseSensitivity cs)
{
return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0;
}
static bool mailbox_equal(const Mailbox &lhs, const Mailbox &rhs, Qt::CaseSensitivity cs)
{
return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs);
}
static std::string stripAngleBrackets(const std::string &str)
{
if (str.empty()) {
return str;
}
if (str[0] == '<' && str[str.size() - 1] == '>') {
return str.substr(1, str.size() - 2);
}
return str;
}
static std::string email(const UserID &uid)
{
if (uid.parent().protocol() == OpenPGP) {
if (const char *const email = uid.email()) {
return stripAngleBrackets(email);
} else {
return std::string();
}
}
Q_ASSERT(uid.parent().protocol() == CMS);
if (const char *const id = uid.id())
if (*id == '<') {
return stripAngleBrackets(id);
} else {
return DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData();
}
else {
return std::string();
}
}
static Mailbox mailbox(const UserID &uid)
{
const std::string e = email(uid);
Mailbox mbox;
if (!e.empty()) {
mbox.setAddress(e.c_str());
}
return mbox;
}
static std::vector<Mailbox> extractMailboxes(const Key &key)
{
std::vector<Mailbox> res;
const auto userIDs{key.userIDs()};
for (const UserID &id : userIDs) {
const Mailbox mbox = mailbox(id);
if (!mbox.addrSpec().isEmpty()) {
res.push_back(mbox);
}
}
return res;
}
static std::vector<Mailbox> extractMailboxes(const std::vector<Key> &signers)
{
std::vector<Mailbox> res;
for (const Key &i : signers) {
const std::vector<Mailbox> bxs = extractMailboxes(i);
res.insert(res.end(), bxs.begin(), bxs.end());
}
return res;
}
static bool keyContainsMailbox(const Key &key, const Mailbox &mbox)
{
const std::vector<Mailbox> mbxs = extractMailboxes(key);
return std::find_if(mbxs.cbegin(), mbxs.cend(),
[mbox](const Mailbox &m) {
return mailbox_equal(mbox, m, Qt::CaseInsensitive);
}) != mbxs.cend();
}
static bool keysContainMailbox(const std::vector<Key> &keys, const Mailbox &mbox)
{
return std::find_if(keys.cbegin(), keys.cend(),
[mbox](const Key &key) {
return keyContainsMailbox(key, mbox);
}) != keys.cend();
}
static bool relevantInDecryptVerifyContext(const VerificationResult &r)
{
// for D/V operations, we ignore verification results which are not errors and contain
// no signatures (which means that the data was just not signed)
return (r.error() && r.error().code() != GPG_ERR_DECRYPT_FAILED) || r.numSignatures() > 0;
}
static QString signatureSummaryToString(int summary)
{
if (summary & Signature::None) {
return i18n("Error: Signature not verified");
} else if (summary & Signature::Valid || summary & Signature::Green) {
return i18n("Good signature");
} else if (summary & Signature::KeyRevoked) {
return i18n("Signing certificate was revoked");
} else if (summary & Signature::KeyExpired) {
return i18n("Signing certificate is expired");
} else if (summary & Signature::KeyMissing) {
return i18n("Certificate is not available");
} else if (summary & Signature::SigExpired) {
return i18n("Signature expired");
} else if (summary & Signature::CrlMissing) {
return i18n("CRL missing");
} else if (summary & Signature::CrlTooOld) {
return i18n("CRL too old");
} else if (summary & Signature::BadPolicy) {
return i18n("Bad policy");
} else if (summary & Signature::SysError) {
return i18n("System error"); //### retrieve system error details?
} else if (summary & Signature::Red) {
return i18n("Bad signature");
}return QString();
}
static QString formatValidSignatureWithTrustLevel(const UserID &id)
{
if (id.isNull()) {
return QString();
}
switch (id.validity()) {
case UserID::Marginal:
return i18n("The signature is valid but the trust in the certificate's validity is only marginal.");
case UserID::Full:
return i18n("The signature is valid and the certificate's validity is fully trusted.");
case UserID::Ultimate:
return i18n("The signature is valid and the certificate's validity is ultimately trusted.");
case UserID::Never:
return i18n("The signature is valid but the certificate's validity is <em>not trusted</em>.");
case UserID::Unknown:
return i18n("The signature is valid but the certificate's validity is unknown.");
case UserID::Undefined:
default:
return i18n("The signature is valid but the certificate's validity is undefined.");
}
}
static QString renderKeyLink(const QString &fpr, const QString &text)
{
return QStringLiteral("<a href=\"key:%1\">%2</a>").arg(fpr, text);
}
static QString renderKey(const Key &key)
{
if (key.isNull()) {
return i18n("Unknown certificate");
}
if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) {
const QString text = QStringLiteral("%1 (%2)").arg(Formatting::prettyNameAndEMail(key).toHtmlEscaped()).arg(
Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData()));
return renderKeyLink(QLatin1String(key.primaryFingerprint()), text);
}
return renderKeyLink(QLatin1String(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint()));
}
static QString renderKeyEMailOnlyNameAsFallback(const Key &key)
{
if (key.isNull()) {
return i18n("Unknown certificate");
}
const QString email = Formatting::prettyEMail(key);
const QString user = !email.isEmpty() ? email : Formatting::prettyName(key);
return renderKeyLink(QLatin1String(key.primaryFingerprint()), user);
}
static QString formatDate(const QDateTime &dt)
{
return QLocale().toString(dt);
}
static QString formatSigningInformation(const Signature &sig)
{
if (sig.isNull()) {
return QString();
}
const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime();
QString text;
Key key = sig.key();
if (dt.isValid()) {
text = i18nc("1 is a date", "Signature created on %1", formatDate(dt)) + QStringLiteral("<br>");
}
if (key.isNull()) {
return text += i18n("With unavailable certificate:") + QStringLiteral("<br>ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper());
}
text += i18n("With certificate:") + QStringLiteral("<br>") + renderKey(key);
if (DeVSCompliance::isCompliant()) {
text +=
(QStringLiteral("<br/>")
+ (sig.isDeVs()
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The signature is %1", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The signature <b>is not</b> %1.", DeVSCompliance::name(true))));
}
return text;
}
static QString strikeOut(const QString &str, bool strike)
{
return QString(strike ? QStringLiteral("<s>%1</s>") : QStringLiteral("%1")).arg(str.toHtmlEscaped());
}
static QString formatInputOutputLabel(const QString &input, const QString &output, bool inputDeleted, bool outputDeleted)
{
if (output.isEmpty()) {
return strikeOut(input, inputDeleted);
}
return i18nc("Input file --> Output file (rarr is arrow", "%1 &rarr; %2",
strikeOut(input, inputDeleted),
strikeOut(output, outputDeleted));
}
static bool IsErrorOrCanceled(const GpgME::Error &err)
{
return err || err.isCanceled();
}
static bool IsErrorOrCanceled(const Result &res)
{
return IsErrorOrCanceled(res.error());
}
static bool IsBad(const Signature &sig)
{
return sig.summary() & Signature::Red;
}
static bool IsGoodOrValid(const Signature &sig)
{
return (sig.summary() & Signature::Valid) || (sig.summary() & Signature::Green);
}
static UserID findUserIDByMailbox(const Key &key, const Mailbox &mbox)
{
const auto userIDs{key.userIDs()};
for (const UserID &id : userIDs)
if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) {
return id;
}
return UserID();
}
static void updateKeys(const VerificationResult &result) {
// This little hack works around the problem that GnuPG / GpgME does not
// provide Key information in a verification result. The Key object is
// a dummy just holding the KeyID. This hack ensures that all available
// keys are fetched from the backend and are populated
for (const auto &sig: result.signatures()) {
// Update key information
sig.key(true, true);
}
}
static QString ensureUniqueDirectory(const QString &path)
{
// make sure that we don't use an existing directory
QString uniquePath = path;
const QFileInfo outputInfo{path};
if (outputInfo.exists()) {
const auto uniqueName = KFileUtils::suggestName(QUrl::fromLocalFile(outputInfo.absolutePath()), outputInfo.fileName());
uniquePath = outputInfo.dir().filePath(uniqueName);
}
if (!QDir{}.mkpath(uniquePath)) {
return {};
}
return uniquePath;
}
}
class DecryptVerifyResult::SenderInfo
{
public:
explicit SenderInfo(const Mailbox &infSender, const std::vector<Key> &signers_) : informativeSender(infSender), signers(signers_) {}
const Mailbox informativeSender;
const std::vector<Key> signers;
bool hasInformativeSender() const
{
return !informativeSender.addrSpec().isEmpty();
}
bool conflicts() const
{
return hasInformativeSender() && hasKeys() && !keysContainMailbox(signers, informativeSender);
}
bool hasKeys() const
{
return std::any_of(signers.cbegin(), signers.cend(), [](const Key &key) { return !key.isNull(); });
}
std::vector<Mailbox> signerMailboxes() const
{
return extractMailboxes(signers);
}
};
namespace
{
static Task::Result::VisualCode codeForVerificationResult(const VerificationResult &res)
{
if (res.isNull()) {
return Task::Result::NeutralSuccess;
}
const std::vector<Signature> sigs = res.signatures();
if (sigs.empty()) {
return Task::Result::Warning;
}
if (std::find_if(sigs.begin(), sigs.end(), IsBad) != sigs.end()) {
return Task::Result::Danger;
}
if ((size_t)std::count_if(sigs.begin(), sigs.end(), IsGoodOrValid) == sigs.size()) {
return Task::Result::AllGood;
}
return Task::Result::Warning;
}
static QString formatVerificationResultOverview(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info)
{
if (res.isNull()) {
return QString();
}
const Error err = res.error();
if (err.isCanceled()) {
return i18n("<b>Verification canceled.</b>");
} else if (err) {
return i18n("<b>Verification failed: %1.</b>", Formatting::errorAsString(err).toHtmlEscaped());
}
const std::vector<Signature> sigs = res.signatures();
if (sigs.empty()) {
return i18n("<b>No signatures found.</b>");
}
const uint bad = std::count_if(sigs.cbegin(), sigs.cend(), IsBad);
if (bad > 0) {
return i18np("<b>Invalid signature.</b>", "<b>%1 invalid signatures.</b>", bad);
}
const uint warn = std::count_if(sigs.cbegin(), sigs.cend(), [](const Signature &sig) { return !IsGoodOrValid(sig); });
if (warn == sigs.size()) {
return i18np("<b>The data could not be verified.</b>", "<b>%1 signatures could not be verified.</b>", warn);
}
//Good signature:
QString text;
if (sigs.size() == 1) {
text = i18n("<b>Valid signature by %1</b>", renderKeyEMailOnlyNameAsFallback(sigs[0].key()));
if (info.conflicts())
text += i18n("<br/><b>Warning:</b> The sender's mail address is not stored in the %1 used for signing.",
renderKeyLink(QLatin1String(sigs[0].key().primaryFingerprint()), i18n("certificate")));
} else {
text = i18np("<b>Valid signature.</b>", "<b>%1 valid signatures.</b>", sigs.size());
if (info.conflicts()) {
text += i18n("<br/><b>Warning:</b> The sender's mail address is not stored in the certificates used for signing.");
}
}
return text;
}
static QString formatDecryptionResultOverview(const DecryptionResult &result, const QString &errorString = QString())
{
const Error err = result.error();
if (err.isCanceled()) {
return i18n("<b>Decryption canceled.</b>");
}
else if (result.isLegacyCipherNoMDC()) {
return i18n("<b>Decryption failed: %1.</b>", i18n("No integrity protection (MDC)."));
}
else if (!errorString.isEmpty()) {
return i18n("<b>Decryption failed: %1.</b>", errorString.toHtmlEscaped());
} else if (err) {
return i18n("<b>Decryption failed: %1.</b>", Formatting::errorAsString(err).toHtmlEscaped());
}
return i18n("<b>Decryption succeeded.</b>");
}
static QString formatSignature(const Signature &sig, const DecryptVerifyResult::SenderInfo &info)
{
if (sig.isNull()) {
return QString();
}
const QString text = formatSigningInformation(sig) + QLatin1String("<br/>");
const Key key = sig.key();
// Green
if (sig.summary() & Signature::Valid) {
const UserID id = findUserIDByMailbox(key, info.informativeSender);
return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0));
}
// Red
if ((sig.summary() & Signature::Red)) {
const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
if (sig.summary() & Signature::SysError) {
return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status()));
}
return ret;
}
// Key missing
if ((sig.summary() & Signature::KeyMissing)) {
return text + i18n("You can search the certificate on a keyserver or import it from a file.");
}
// Yellow
- if ((sig.validity() & Signature::Validity::Undefined) ||
- (sig.validity() & Signature::Validity::Unknown) ||
- (sig.summary() == Signature::Summary::None)) {
+ if ((sig.validity() & Signature::Validity::Undefined) //
+ || (sig.validity() & Signature::Validity::Unknown) //
+ || (sig.summary() == Signature::Summary::None)) {
return text + (key.protocol() == OpenPGP ? i18n("The used key is not certified by you or any trusted person.") :
i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."));
}
// Catch all fall through
const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary()));
if (sig.summary() & Signature::SysError) {
return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status()));
}
return ret;
}
static QStringList format(const std::vector<Mailbox> &mbxs)
{
QStringList res;
std::transform(mbxs.cbegin(), mbxs.cend(), std::back_inserter(res),
[](const Mailbox &mbox) {
return mbox.prettyAddress();
});
return res;
}
static QString formatVerificationResultDetails(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info, const QString &errorString)
{
if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) {
return i18n("Input error: %1", errorString);
}
const std::vector<Signature> sigs = res.signatures();
QString details;
for (const Signature &sig : sigs) {
details += formatSignature(sig, info) + QLatin1Char('\n');
}
details = details.trimmed();
details.replace(QLatin1Char('\n'), QStringLiteral("<br/><br/>"));
if (info.conflicts()) {
details += i18n("<p>The sender's address %1 is not stored in the certificate. Stored: %2</p>", info.informativeSender.prettyAddress(), format(info.signerMailboxes()).join(i18nc("separator for a list of e-mail addresses", ", ")));
}
return details;
}
static QString formatRecipientsDetails(const std::vector<Key> &knownRecipients, unsigned int numRecipients)
{
if (numRecipients == 0) {
return {};
}
if (knownRecipients.empty()) {
return QLatin1String("<i>") +
i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) +
QLatin1String("</i>");
}
QString details = i18np("Recipient:", "Recipients:", numRecipients);
if (numRecipients == 1) {
details += QLatin1Char(' ') + renderKey(knownRecipients.front());
} else {
details += QLatin1String("<ul>");
for (const Key &key : knownRecipients) {
details += QLatin1String("<li>") + renderKey(key) + QLatin1String("</li>");
}
if (knownRecipients.size() < numRecipients) {
details += QLatin1String("<li><i>") +
i18np("One unknown recipient", "%1 unknown recipients",
numRecipients - knownRecipients.size()) +
QLatin1String("</i></li>");
}
details += QLatin1String("</ul>");
}
return details;
}
static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector<Key> &recipients,
const QString &errorString, bool isSigned, const QPointer<Task> &task)
{
if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) {
return i18n("Input error: %1", errorString);
}
if (res.isNull() || res.error() || res.error().isCanceled()) {
return formatRecipientsDetails(recipients, res.numRecipients());
}
QString details;
if (DeVSCompliance::isCompliant()) {
details += ((res.isDeVs()
? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption is %1.", DeVSCompliance::name(true))
: i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"The decryption <b>is not</b> %1.", DeVSCompliance::name(true)))
+ QStringLiteral("<br/>"));
}
if (res.fileName()) {
const auto decVerifyTask = qobject_cast<AbstractDecryptVerifyTask*> (task.data());
if (decVerifyTask) {
const auto embedFileName = QString::fromUtf8(res.fileName()).toHtmlEscaped();
if (embedFileName != decVerifyTask->outputLabel()) {
details += i18n("Embedded file name: '%1'", embedFileName);
details += QStringLiteral("<br/>");
}
}
}
if (!isSigned) {
details += i18n("<b>Note:</b> You cannot be sure who encrypted this message as it is not signed.")
+ QLatin1String("<br/>");
}
if (res.isLegacyCipherNoMDC()) {
details += i18nc("Integrity protection was missing because an old cipher was used.",
"<b>Hint:</b> If this file was encrypted before the year 2003 it is "
"likely that the file is legitimate. This is because back "
"then integrity protection was not widely used.") + QStringLiteral("<br/><br/>") +
i18nc("The user is offered to force decrypt a non integrity protected message. With the strong advice to re-encrypt it.",
"If you are confident that the file was not manipulated you should re-encrypt it after you have forced the decryption.") +
QStringLiteral("<br/><br/>");
}
details += formatRecipientsDetails(recipients, res.numRecipients());
return details;
}
static QString formatDecryptVerifyResultOverview(const DecryptionResult &dr, const VerificationResult &vr, const DecryptVerifyResult::SenderInfo &info)
{
if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) {
return formatDecryptionResultOverview(dr);
}
return formatVerificationResultOverview(vr, info);
}
static QString formatDecryptVerifyResultDetails(const DecryptionResult &dr,
const VerificationResult &vr,
const std::vector<Key> &recipients,
const DecryptVerifyResult::SenderInfo &info,
const QString &errorString,
const QPointer<Task> &task)
{
const QString drDetails = formatDecryptionResultDetails(dr, recipients, errorString, relevantInDecryptVerifyContext(vr), task);
if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) {
return drDetails;
}
return drDetails + (drDetails.isEmpty() ? QString() : QStringLiteral("<br/>")) + formatVerificationResultDetails(vr, info, errorString);
}
} // anon namespace
class DecryptVerifyResult::Private
{
DecryptVerifyResult *const q;
public:
Private(DecryptVerifyOperation type,
const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &input,
const QString &output,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender,
DecryptVerifyResult *qq) :
q(qq),
m_type(type),
m_verificationResult(vr),
m_decryptionResult(dr),
m_stuff(stuff),
m_fileName(fileName),
m_error(error),
m_errorString(errString),
m_inputLabel(input),
m_outputLabel(output),
m_auditLog(auditLog),
m_parentTask(QPointer<Task>(parentTask)),
m_informativeSender(informativeSender)
{
}
QString label() const
{
return formatInputOutputLabel(m_inputLabel, m_outputLabel, false, q->hasError());
}
DecryptVerifyResult::SenderInfo makeSenderInfo() const;
bool isDecryptOnly() const
{
return m_type == Decrypt;
}
bool isVerifyOnly() const
{
return m_type == Verify;
}
bool isDecryptVerify() const
{
return m_type == DecryptVerify;
}
DecryptVerifyOperation m_type;
VerificationResult m_verificationResult;
DecryptionResult m_decryptionResult;
QByteArray m_stuff;
QString m_fileName;
GpgME::Error m_error;
QString m_errorString;
QString m_inputLabel;
QString m_outputLabel;
const AuditLogEntry m_auditLog;
QPointer <Task> m_parentTask;
const Mailbox m_informativeSender;
};
DecryptVerifyResult::SenderInfo DecryptVerifyResult::Private::makeSenderInfo() const
{
return SenderInfo(m_informativeSender, KeyCache::instance()->findSigners(m_verificationResult));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Decrypt,
+ Decrypt, //
VerificationResult(),
dr,
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Decrypt,
+ Decrypt, //
VerificationResult(),
DecryptionResult(err),
QByteArray(),
{},
err,
what,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptVerifyResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plaintext, const QString &fileName, const AuditLogEntry &auditLog)
{
const auto err = dr.error().code() ? dr.error() : vr.error();
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- DecryptVerify,
+ DecryptVerify, //
vr,
dr,
plaintext,
fileName,
err,
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- DecryptVerify,
+ DecryptVerify, //
VerificationResult(),
DecryptionResult(err),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Verify,
+ Verify, //
vr,
DecryptionResult(),
plaintext,
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Verify,
+ Verify, //
VerificationResult(err),
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Verify,
+ Verify, //
vr,
DecryptionResult(),
QByteArray(),
{},
{},
QString(),
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
std::shared_ptr<DecryptVerifyResult> AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog)
{
return std::shared_ptr<DecryptVerifyResult>(new DecryptVerifyResult(
- Verify,
+ Verify, //
VerificationResult(err),
DecryptionResult(),
QByteArray(),
{},
err,
details,
inputLabel(),
outputLabel(),
auditLog,
this,
informativeSender()));
}
DecryptVerifyResult::DecryptVerifyResult(DecryptVerifyOperation type,
const VerificationResult &vr,
const DecryptionResult &dr,
const QByteArray &stuff,
const QString &fileName,
const GpgME::Error &error,
const QString &errString,
const QString &inputLabel,
const QString &outputLabel,
const AuditLogEntry &auditLog,
Task *parentTask,
const Mailbox &informativeSender)
: Task::Result(), d(new Private(type, vr, dr, stuff, fileName, error, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, this))
{
}
QString DecryptVerifyResult::overview() const
{
QString ov;
if (d->isDecryptOnly()) {
ov += formatDecryptionResultOverview(d->m_decryptionResult);
} else if (d->isVerifyOnly()) {
ov += formatVerificationResultOverview(d->m_verificationResult, d->makeSenderInfo());
} else {
ov += formatDecryptVerifyResultOverview(d->m_decryptionResult, d->m_verificationResult, d->makeSenderInfo());
}
if (ov.size() + d->label().size() > 120) {
// Avoid ugly breaks
ov = QStringLiteral("<br>") + ov;
}
return i18nc("label: result example: foo.sig: Verification failed. ", "%1: %2", d->label(), ov);
}
QString DecryptVerifyResult::details() const
{
if (d->isDecryptOnly()) {
return formatDecryptionResultDetails(d->m_decryptionResult,
KeyCache::instance()->findRecipients(d->m_decryptionResult),
errorString(), false, d->m_parentTask);
}
if (d->isVerifyOnly()) {
return formatVerificationResultDetails(d->m_verificationResult, d->makeSenderInfo(), errorString());
}
return formatDecryptVerifyResultDetails(d->m_decryptionResult,
d->m_verificationResult, KeyCache::instance()->findRecipients(
d->m_decryptionResult), d->makeSenderInfo(), errorString(),
d->m_parentTask);
}
GpgME::Error DecryptVerifyResult::error() const
{
return d->m_error;
}
QString DecryptVerifyResult::errorString() const
{
return d->m_errorString;
}
AuditLogEntry DecryptVerifyResult::auditLog() const
{
return d->m_auditLog;
}
QPointer<Task> DecryptVerifyResult::parentTask() const
{
return d->m_parentTask;
}
Task::Result::VisualCode DecryptVerifyResult::code() const
{
if ((d->m_type == DecryptVerify || d->m_type == Verify) && relevantInDecryptVerifyContext(verificationResult())) {
return codeForVerificationResult(verificationResult());
}
return hasError() ? NeutralError : NeutralSuccess;
}
GpgME::VerificationResult DecryptVerifyResult::verificationResult() const
{
return d->m_verificationResult;
}
GpgME::DecryptionResult DecryptVerifyResult::decryptionResult() const
{
return d->m_decryptionResult;
}
QString DecryptVerifyResult::fileName() const
{
return d->m_fileName;
}
class AbstractDecryptVerifyTask::Private
{
public:
Mailbox informativeSender;
QPointer<QGpgME::Job> job;
};
AbstractDecryptVerifyTask::AbstractDecryptVerifyTask(QObject *parent) : Task(parent), d(new Private) {}
AbstractDecryptVerifyTask::~AbstractDecryptVerifyTask() {}
void AbstractDecryptVerifyTask::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
Mailbox AbstractDecryptVerifyTask::informativeSender() const
{
return d->informativeSender;
}
void AbstractDecryptVerifyTask::setInformativeSender(const Mailbox &sender)
{
d->informativeSender = sender;
}
QGpgME::Job *AbstractDecryptVerifyTask::job() const
{
return d->job;
}
void AbstractDecryptVerifyTask::setJob(QGpgME::Job *job)
{
d->job = job;
}
class DecryptVerifyTask::Private
{
DecryptVerifyTask *const q;
public:
explicit Private(DecryptVerifyTask *qq)
: q{qq}
{
}
void startDecryptVerifyJob();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startDecryptVerifyArchiveJob();
#endif
void slotResult(const DecryptionResult &, const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_ignoreMDCError = false;
bool m_extractArchive = false;
QString m_inputFilePath;
QString m_outputDirectory;
};
void DecryptVerifyTask::Private::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText)
{
updateKeys(vr);
{
std::stringstream ss;
ss << dr << '\n' << vr;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (dr.error().code() || vr.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!dr.isNull() || !vr.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = dr.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
(m_output && m_output->failed())) {
q->emitResult(q->fromDecryptResult(drErr ? dr.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptVerifyResult(dr, vr, plainText, m_output ? m_output->fileName() : QString{}, auditLog));
}
DecryptVerifyTask::DecryptVerifyTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
DecryptVerifyTask::~DecryptVerifyTask()
{
}
void DecryptVerifyTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptVerifyTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptVerifyTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = prot == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptVerifyTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature/ciphertext - maybe it is neither ciphertext nor a signature?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptVerifyTask::label() const
{
return i18n("Decrypting: %1...", d->m_input->label());
}
unsigned long long DecryptVerifyTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptVerifyTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString DecryptVerifyTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : d->m_outputDirectory;
}
Protocol DecryptVerifyTask::protocol() const
{
return d->m_protocol;
}
static void ensureIOOpen(QIODevice *input, QIODevice *output)
{
if (input && !input->isOpen()) {
input->open(QIODevice::ReadOnly);
}
if (output && !output->isOpen()) {
output->open(QIODevice::WriteOnly);
}
}
void DecryptVerifyTask::setIgnoreMDCError(bool value)
{
d->m_ignoreMDCError = value;
}
void DecryptVerifyTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void DecryptVerifyTask::setInputFile(const QString &path)
{
d->m_inputFilePath = path;
}
void DecryptVerifyTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
}
#endif
void DecryptVerifyTask::doStart()
{
kleo_assert(d->m_backend);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else
#endif
{
d->startDecryptVerifyJob();
}
}
static void setIgnoreMDCErrorFlag(QGpgME::Job *job, bool ignoreMDCError)
{
if (ignoreMDCError) {
qCDebug(KLEOPATRA_LOG) << "Modifying job to ignore MDC errors.";
auto ctx = QGpgME::Job::context(job);
if (!ctx) {
qCWarning(KLEOPATRA_LOG) << "Failed to get context for job";
} else {
const auto err = ctx->setFlag("ignore-mdc-error", "1");
if (err) {
qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << Formatting::errorAsString(err);
}
}
}
}
void DecryptVerifyTask::Private::startDecryptVerifyJob()
{
try {
std::unique_ptr<QGpgME::DecryptVerifyJob> job{m_backend->decryptVerifyJob()};
kleo_assert(job);
setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError);
QObject::connect(job.get(), &QGpgME::DecryptVerifyJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult, const QByteArray &plainText) {
slotResult(decryptResult, verifyResult, plainText);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
#else
QObject::connect(job.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get());
job->start(m_input->ioDevice(), m_output->ioDevice());
q->setJob(job.release());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptVerifyResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void DecryptVerifyTask::Private::startDecryptVerifyArchiveJob()
{
std::unique_ptr<QGpgME::DecryptVerifyArchiveJob> job{m_backend->decryptVerifyArchiveJob()};
kleo_assert(job);
setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError);
connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult) {
slotResult(decryptResult, verifyResult);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress);
#else
connect(job.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
#if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME
// make sure that we don't use an existing output directory
const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory);
if (outputDirectory.isEmpty()) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {}));
return;
}
m_outputDirectory = outputDirectory;
job->setInputFile(m_inputFilePath);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->startIt();
#else
ensureIOOpen(m_input->ioDevice().get(), nullptr);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->start(m_input->ioDevice());
#endif
q->setJob(job.release());
if (err) {
q->emitResult(q->fromDecryptVerifyResult(err, {}, {}));
}
}
#endif
class DecryptTask::Private
{
DecryptTask *const q;
public:
explicit Private(DecryptTask *qq)
: q{qq}
{
}
void slotResult(const DecryptionResult &, const QByteArray &);
void registerJob(QGpgME::DecryptJob *job)
{
q->connect(job, SIGNAL(result(GpgME::DecryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::DecryptionResult,QByteArray)));
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
q->connect(job, &QGpgME::Job::jobProgress, q, &DecryptTask::setProgress);
#else
q->connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
}
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
};
void DecryptTask::Private::slotResult(const DecryptionResult &result, const QByteArray &plainText)
{
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
const int drErr = result.error().code();
const QString errorString = m_output->errorString();
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
m_output->failed()) {
q->emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromDecryptResult(result, plainText, auditLog));
}
DecryptTask::DecryptTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
DecryptTask::~DecryptTask()
{
}
void DecryptTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void DecryptTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void DecryptTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void DecryptTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this was S/MIME- or OpenPGP-encrypted - maybe it is not ciphertext at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString DecryptTask::label() const
{
return i18n("Decrypting: %1...", d->m_input->label());
}
unsigned long long DecryptTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString DecryptTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString DecryptTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : QString();
}
Protocol DecryptTask::protocol() const
{
return d->m_protocol;
}
void DecryptTask::doStart()
{
kleo_assert(d->m_backend);
try {
std::unique_ptr<QGpgME::DecryptJob> job{d->m_backend->decryptJob()};
kleo_assert(job);
d->registerJob(job.get());
ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get());
job->start(d->m_input->ioDevice(), d->m_output->ioDevice());
setJob(job.release());
} catch (const GpgME::Exception &e) {
emitResult(fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
class VerifyOpaqueTask::Private
{
VerifyOpaqueTask *const q;
public:
explicit Private(VerifyOpaqueTask *qq)
: q{qq}
{
}
void startVerifyOpaqueJob();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startDecryptVerifyArchiveJob();
#endif
void slotResult(const VerificationResult &, const QByteArray & = {});
std::shared_ptr<Input> m_input;
std::shared_ptr<Output> m_output;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
bool m_extractArchive = false;
QString m_inputFilePath;
QString m_outputDirectory;
};
void VerifyOpaqueTask::Private::slotResult(const VerificationResult &result, const QByteArray &plainText)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
if (m_output) {
if (result.error().code()) {
m_output->cancel();
} else {
try {
kleo_assert(!result.isNull());
m_output->finalize();
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
return;
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
return;
}
}
}
const int drErr = result.error().code();
const QString errorString = m_output ? m_output->errorString() : QString{};
if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) ||
(m_output && m_output->failed())) {
q->emitResult(q->fromVerifyOpaqueResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO),
errorString, auditLog));
return;
}
q->emitResult(q->fromVerifyOpaqueResult(result, plainText, auditLog));
}
VerifyOpaqueTask::VerifyOpaqueTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
VerifyOpaqueTask::~VerifyOpaqueTask()
{
}
void VerifyOpaqueTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyOpaqueTask::setOutput(const std::shared_ptr<Output> &output)
{
d->m_output = output;
kleo_assert(d->m_output && d->m_output->ioDevice());
}
void VerifyOpaqueTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyOpaqueTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
QString VerifyOpaqueTask::label() const
{
return i18n("Verifying: %1...", d->m_input->label());
}
unsigned long long VerifyOpaqueTask::inputSize() const
{
return d->m_input ? d->m_input->size() : 0;
}
QString VerifyOpaqueTask::inputLabel() const
{
return d->m_input ? d->m_input->label() : QString();
}
QString VerifyOpaqueTask::outputLabel() const
{
return d->m_output ? d->m_output->label() : d->m_outputDirectory;
}
Protocol VerifyOpaqueTask::protocol() const
{
return d->m_protocol;
}
void VerifyOpaqueTask::setExtractArchive(bool extract)
{
d->m_extractArchive = extract;
}
void VerifyOpaqueTask::setInputFile(const QString &path)
{
d->m_inputFilePath = path;
}
void VerifyOpaqueTask::setOutputDirectory(const QString &directory)
{
d->m_outputDirectory = directory;
}
void VerifyOpaqueTask::doStart()
{
kleo_assert(d->m_backend);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) {
d->startDecryptVerifyArchiveJob();
} else
#endif
{
d->startVerifyOpaqueJob();
}
}
void VerifyOpaqueTask::Private::startVerifyOpaqueJob()
{
try {
std::unique_ptr<QGpgME::VerifyOpaqueJob> job{m_backend->verifyOpaqueJob()};
kleo_assert(job);
connect(job.get(), &QGpgME::VerifyOpaqueJob::result, q, [this](const GpgME::VerificationResult &result, const QByteArray &plainText) {
slotResult(result, plainText);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress, q, &VerifyOpaqueTask::setProgress);
#else
connect(job.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr);
job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr<QIODevice>());
q->setJob(job.release());
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void VerifyOpaqueTask::Private::startDecryptVerifyArchiveJob()
{
std::unique_ptr<QGpgME::DecryptVerifyArchiveJob> job{m_backend->decryptVerifyArchiveJob()};
kleo_assert(job);
connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const DecryptionResult &, const VerificationResult &verifyResult) {
slotResult(verifyResult);
});
connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::dataProgress, q, &VerifyOpaqueTask::setProgress);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME
// make sure that we don't use an existing output directory
const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory);
if (outputDirectory.isEmpty()) {
q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {}));
return;
}
m_outputDirectory = outputDirectory;
job->setInputFile(m_inputFilePath);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->startIt();
#else
ensureIOOpen(m_input->ioDevice().get(), nullptr);
job->setOutputDirectory(m_outputDirectory);
const auto err = job->start(m_input->ioDevice());
#endif
q->setJob(job.release());
if (err) {
q->emitResult(q->fromVerifyOpaqueResult(err, {}, {}));
}
}
#endif
class VerifyDetachedTask::Private
{
VerifyDetachedTask *const q;
public:
explicit Private(VerifyDetachedTask *qq)
: q{qq}
{
}
void slotResult(const VerificationResult &);
void registerJob(QGpgME::VerifyDetachedJob *job)
{
q->connect(job, SIGNAL(result(GpgME::VerificationResult)),
q, SLOT(slotResult(GpgME::VerificationResult)));
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
q->connect(job, &QGpgME::Job::jobProgress, q, &VerifyDetachedTask::setProgress);
#else
q->connect(job, &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
}
std::shared_ptr<Input> m_input, m_signedData;
const QGpgME::Protocol *m_backend = nullptr;
Protocol m_protocol = UnknownProtocol;
};
void VerifyDetachedTask::Private::slotResult(const VerificationResult &result)
{
updateKeys(result);
{
std::stringstream ss;
ss << result;
qCDebug(KLEOPATRA_LOG) << ss.str().c_str();
}
const AuditLogEntry auditLog = auditLogFromSender(q->sender());
try {
kleo_assert(!result.isNull());
q->emitResult(q->fromVerifyDetachedResult(result, auditLog));
} catch (const GpgME::Exception &e) {
q->emitResult(q->fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
} catch (const std::exception &e) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog));
} catch (...) {
q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog));
}
}
VerifyDetachedTask::VerifyDetachedTask(QObject *parent) : AbstractDecryptVerifyTask(parent), d(new Private(this))
{
}
VerifyDetachedTask::~VerifyDetachedTask()
{
}
void VerifyDetachedTask::setInput(const std::shared_ptr<Input> &input)
{
d->m_input = input;
kleo_assert(d->m_input && d->m_input->ioDevice());
}
void VerifyDetachedTask::setSignedData(const std::shared_ptr<Input> &signedData)
{
d->m_signedData = signedData;
kleo_assert(d->m_signedData && d->m_signedData->ioDevice());
}
void VerifyDetachedTask::setProtocol(Protocol prot)
{
kleo_assert(prot != UnknownProtocol);
d->m_protocol = prot;
d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(d->m_backend);
}
void VerifyDetachedTask::autodetectProtocolFromInput()
{
if (!d->m_input) {
return;
}
const Protocol p = findProtocol(d->m_input->classification());
if (p == UnknownProtocol) {
throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly);
}
setProtocol(p);
}
unsigned long long VerifyDetachedTask::inputSize() const
{
return d->m_signedData ? d->m_signedData->size() : 0;
}
QString VerifyDetachedTask::label() const
{
if (d->m_signedData) {
return xi18nc("Verification of a detached signature in progress. The first file contains the data."
"The second file is the signature file.",
"Verifying: <filename>%1</filename> with <filename>%2</filename>...",
d->m_signedData->label(),
d->m_input->label());
}
return i18n("Verifying signature: %1...", d->m_input->label());
}
QString VerifyDetachedTask::inputLabel() const
{
if (d->m_signedData && d->m_input) {
return xi18nc("Verification of a detached signature summary. The first file contains the data."
"The second file is signature.",
"Verified <filename>%1</filename> with <filename>%2</filename>",
d->m_signedData->label(),
d->m_input->label());
}
return d->m_input ? d->m_input->label() : QString();
}
QString VerifyDetachedTask::outputLabel() const
{
return QString();
}
Protocol VerifyDetachedTask::protocol() const
{
return d->m_protocol;
}
void VerifyDetachedTask::doStart()
{
kleo_assert(d->m_backend);
try {
std::unique_ptr<QGpgME::VerifyDetachedJob> job{d->m_backend->verifyDetachedJob()};
kleo_assert(job);
d->registerJob(job.get());
ensureIOOpen(d->m_input->ioDevice().get(), nullptr);
ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr);
job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice());
setJob(job.release());
} catch (const GpgME::Exception &e) {
emitResult(fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry()));
} catch (const std::exception &e) {
emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry()));
} catch (...) {
emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry()));
}
}
#include "moc_decryptverifytask.cpp"
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index 2cf87e30e..3c83e4b81 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,798 +1,798 @@
/* 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 <dev@ingo-kloecker.de>
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 <QAccessible>
#include <QCompleter>
#include <QPushButton>
#include <QAction>
#include <QSignalBlocker>
#include "kleopatra_debug.h"
#include <Libkleo/Debug>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QMenu>
#include <QToolButton>
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<GpgME::Key>();
if (!key.isNull()) {
return Kleo::Formatting::iconForUid(key.userID(0));
}
const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value<KeyGroup>();
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);
}
}
private:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
const auto leftGroup = leftKey.isNull() ? sourceModel()->data(left, KeyList::GroupRole).value<KeyGroup>() : KeyGroup{};
const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
const auto rightGroup = rightKey.isNull() ? sourceModel()->data(right, KeyList::GroupRole).value<KeyGroup>() : KeyGroup{};
// shouldn't happen, but still put null entries at the end
if (leftKey.isNull() && leftGroup.isNull()) {
return false;
}
if (rightKey.isNull() && rightGroup.isNull()) {
return true;
}
// first sort by the displayed name and/or email address
const auto leftNameAndOrEmail = leftGroup.isNull() ? Formatting::nameAndEmailForSummaryLine(leftKey) : leftGroup.name();
const auto rightNameAndOrEmail = rightGroup.isNull() ? Formatting::nameAndEmailForSummaryLine(rightKey) : rightGroup.name();
const int cmp = QString::localeAwareCompare(leftNameAndOrEmail, rightNameAndOrEmail);
if (cmp) {
return cmp < 0;
}
// then sort groups before certificates
if (!leftGroup.isNull() && !rightKey.isNull()) {
return true; // left is group, right is certificate
}
if (!leftKey.isNull() && !rightGroup.isNull()) {
return false; // left is certificate, right is group
}
// if both are groups (with identical names) sort them by their ID
if (!leftGroup.isNull() && !rightGroup.isNull()) {
return leftGroup.id() < rightGroup.id();
}
// sort certificates with same name/email by validity and creation time
const auto lUid = leftKey.userID(0);
const auto rUid = rightKey.userID(0);
if (lUid.validity() != rUid.validity()) {
return lUid.validity() > rUid.validity();
}
/* Both have the same validity, check which one is newer. */
time_t leftTime = 0;
for (const GpgME::Subkey &s : leftKey.subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > leftTime) {
leftTime = s.creationTime();
}
}
time_t rightTime = 0;
for (const GpgME::Subkey &s : rightKey.subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > rightTime) {
rightTime = s.creationTime();
}
}
if (rightTime != leftTime) {
return leftTime > rightTime;
}
// as final resort we compare the fingerprints
return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0;
}
};
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<KeyFilter> &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<GpgME::Key> &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;
bool mEditingInProgress = false;
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;
CompletionProxyModel *const mCompleterFilterModel;
QCompleter *mCompleter = nullptr;
std::shared_ptr<KeyFilter> mFilter;
QAction *const mStatusAction;
QAction *const mShowDetailsAction;
QPointer<QGpgME::Job> 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<KeyFilter>{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);
// initialize dynamic sorting
mCompleterFilterModel->sort(0);
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<const QModelIndex &>(&QCompleter::activated),
q, [this] (const QModelIndex &index) {
Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value<Key>();
auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value<KeyGroup>();
if (!key.isNull()) {
q->setKey(key);
} else if (!group.isNull()) {
q->setGroup(group);
} else {
qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group";
}
// queue the call of editFinished() to ensure that QLineEdit finished its own work
QMetaObject::invokeMethod(q, [this]() { editFinished(); }, Qt::QueuedConnection);
});
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();
}
if (q->isEmpty()) {
Q_EMIT q->cleared();
}
}
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<GpgME::Key> &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" << Formatting::errorAsString(err);
} 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<GpgME::Key> &keys)
{
if (mLocateJob != job) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Ignoring outdated finished" << job << "for" << email;
return;
}
qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << Formatting::errorAsString(result.error()) << "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<std::equal_to> 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<Key>();
newGroup = mFilterModel->data(index, KeyList::GroupRole).value<KeyGroup>();
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<int>(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<int>(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<int>(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()
+ 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::isEditingInProgress() const
{
return d->mEditingInProgress;
}
bool CertificateLineEdit::hasAcceptableInput() const
{
- return d->mStatus == Private::Status::Empty
+ return d->mStatus == Private::Status::Empty //
|| d->mStatus == Private::Status::Success;
}
void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
mFilter = filter;
mFilterModel->setKeyFilter(filter);
mCompleterFilterModel->setKeyFilter(mFilter);
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
d->setKeyFilter(filter);
}
void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name)
{
d->setAccessibleName(name);
}
#include "certificatelineedit.moc"
diff --git a/src/crypto/gui/decryptverifyfileswizard.h b/src/crypto/gui/decryptverifyfileswizard.h
index 79e01246e..bcf860388 100644
--- a/src/crypto/gui/decryptverifyfileswizard.h
+++ b/src/crypto/gui/decryptverifyfileswizard.h
@@ -1,63 +1,63 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/decryptverifyfileswizard.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 <crypto/gui/wizard.h>
#include <utils/pimpl_ptr.h>
#include <memory>
namespace Kleo
{
namespace Crypto
{
class Task;
class TaskCollection;
namespace Gui
{
class DecryptVerifyOperationWidget;
class DecryptVerifyFilesWizard : public Wizard
{
Q_OBJECT
public:
enum Page {
OperationsPage = 0,
- ResultPage
+ ResultPage,
};
explicit DecryptVerifyFilesWizard(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~DecryptVerifyFilesWizard() override;
void setOutputDirectory(const QString &dir);
QString outputDirectory() const;
bool useOutputDirectory() const;
void setTaskCollection(const std::shared_ptr<TaskCollection> &coll);
DecryptVerifyOperationWidget *operationWidget(unsigned int idx);
Q_SIGNALS:
void operationPrepared();
private:
void onNext(int id) override;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
};
}
}
}
diff --git a/src/crypto/gui/decryptverifyoperationwidget.h b/src/crypto/gui/decryptverifyoperationwidget.h
index 697d62750..15c5b6869 100644
--- a/src/crypto/gui/decryptverifyoperationwidget.h
+++ b/src/crypto/gui/decryptverifyoperationwidget.h
@@ -1,72 +1,72 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/decryptverifyoperationwidget.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 <QWidget>
#include <utils/pimpl_ptr.h>
#include <memory>
#include <vector>
namespace Kleo
{
class ArchiveDefinition;
}
namespace Kleo
{
namespace Crypto
{
namespace Gui
{
class DecryptVerifyOperationWidget : public QWidget
{
Q_OBJECT
Q_ENUMS(Mode)
Q_PROPERTY(Mode mode READ mode WRITE setMode)
Q_PROPERTY(QString inputFileName READ inputFileName WRITE setInputFileName)
Q_PROPERTY(QString signedDataFileName READ signedDataFileName WRITE setSignedDataFileName)
public:
explicit DecryptVerifyOperationWidget(QWidget *parent = nullptr);
~DecryptVerifyOperationWidget() override;
enum Mode {
VerifyDetachedWithSignature,
VerifyDetachedWithSignedData,
- DecryptVerifyOpaque
+ DecryptVerifyOpaque,
};
void setMode(Mode mode, const std::shared_ptr<ArchiveDefinition> &ad);
void setMode(Mode mode);
Mode mode() const;
void setInputFileName(const QString &name);
QString inputFileName() const;
void setSignedDataFileName(const QString &name);
QString signedDataFileName() const;
void setArchiveDefinitions(const std::vector< std::shared_ptr<ArchiveDefinition> > &ads);
std::shared_ptr<ArchiveDefinition> selectedArchiveDefinition() const;
Q_SIGNALS:
void changed();
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_PRIVATE_SLOT(d, void enableDisableWidgets())
};
}
}
}
diff --git a/src/crypto/gui/objectspage.cpp b/src/crypto/gui/objectspage.cpp
index ae3144cbd..c4be48fb5 100644
--- a/src/crypto/gui/objectspage.cpp
+++ b/src/crypto/gui/objectspage.cpp
@@ -1,143 +1,143 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/objectspage.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 <config-kleopatra.h>
#include "objectspage.h"
#include <utils/filedialog.h>
#include <QIcon>
#include <KLocalizedString>
#include <QFileInfo>
#include <QListWidget>
#include <QHBoxLayout>
#include <QPushButton>
#include <QVBoxLayout>
using namespace Kleo;
using namespace Kleo::Crypto::Gui;
class ObjectsPage::Private
{
friend class ::Kleo::Crypto::Gui::ObjectsPage;
ObjectsPage *const q;
public:
explicit Private(ObjectsPage *qq);
~Private();
void add();
void addFile(const QFileInfo &i);
void remove();
void listSelectionChanged();
enum Role {
- AbsoluteFilePathRole = Qt::UserRole
+ AbsoluteFilePathRole = Qt::UserRole,
};
private:
QListWidget *fileListWidget;
QPushButton *removeButton;
};
ObjectsPage::Private::Private(ObjectsPage *qq)
: q(qq)
{
q->setTitle(i18n("<b>Objects</b>"));
auto const top = new QVBoxLayout(q);
fileListWidget = new QListWidget;
fileListWidget->setSelectionMode(QAbstractItemView::MultiSelection);
connect(fileListWidget, SIGNAL(itemSelectionChanged()), q, SLOT(listSelectionChanged()));
top->addWidget(fileListWidget);
auto const buttonWidget = new QWidget;
auto const buttonLayout = new QHBoxLayout(buttonWidget);
removeButton = new QPushButton;
removeButton->setText(i18n("Remove Selected"));
connect(removeButton, SIGNAL(clicked()), q, SLOT(remove()));
buttonLayout->addWidget(removeButton);
buttonLayout->addStretch();
top->addWidget(buttonWidget);
listSelectionChanged();
}
ObjectsPage::Private::~Private() {}
void ObjectsPage::Private::add()
{
const QString fname = FileDialog::getOpenFileName(q, i18n("Select File"), QStringLiteral("enc"));
if (fname.isEmpty()) {
return;
}
addFile(QFileInfo(fname));
Q_EMIT q->completeChanged();
}
void ObjectsPage::Private::remove()
{
const QList<QListWidgetItem *> selected = fileListWidget->selectedItems();
Q_ASSERT(!selected.isEmpty());
for (QListWidgetItem *const i : selected) {
delete i;
}
Q_EMIT q->completeChanged();
}
void ObjectsPage::Private::listSelectionChanged()
{
removeButton->setEnabled(!fileListWidget->selectedItems().isEmpty());
}
ObjectsPage::ObjectsPage(QWidget *parent, Qt::WindowFlags f)
: WizardPage(parent, f), d(new Private(this))
{
}
ObjectsPage::~ObjectsPage()
{
}
void ObjectsPage::setFiles(const QStringList &list)
{
d->fileListWidget->clear();
for (const QString &i : list) {
d->addFile(QFileInfo(i));
}
Q_EMIT completeChanged();
}
void ObjectsPage::Private::addFile(const QFileInfo &info)
{
auto const item = new QListWidgetItem;
if (info.isDir()) {
item->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
}
item->setText(info.fileName());
item->setData(AbsoluteFilePathRole, info.absoluteFilePath());
fileListWidget->addItem(item);
}
QStringList ObjectsPage::files() const
{
QStringList list;
for (int i = 0; i < d->fileListWidget->count(); ++i) {
const QListWidgetItem *const item = d->fileListWidget->item(i);
list.push_back(item->data(Private::AbsoluteFilePathRole).toString());
}
return list;
}
bool ObjectsPage::isComplete() const
{
return d->fileListWidget->count() > 0;
}
#include "moc_objectspage.cpp"
diff --git a/src/crypto/gui/resolverecipientspage.cpp b/src/crypto/gui/resolverecipientspage.cpp
index c2968f062..a14759c7f 100644
--- a/src/crypto/gui/resolverecipientspage.cpp
+++ b/src/crypto/gui/resolverecipientspage.cpp
@@ -1,697 +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 <config-kleopatra.h>
#include "resolverecipientspage.h"
#include "resolverecipientspage_p.h"
#include <dialogs/certificateselectiondialog.h>
#include <crypto/certificateresolver.h>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QButtonGroup>
#include <QComboBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPointer>
#include <QPushButton>
#include <QRadioButton>
#include <QToolButton>
#include <QStringList>
#include <QVBoxLayout>
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<Key> &pgp, const std::vector<Key> &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<QListWidgetItem *> 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("<i>No certificate selected</i>"));
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<Key>(), std::vector<Key>());
}
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 |
+ const CertificateSelectionDialog::Options options = //
+ CertificateSelectionDialog::SingleSelection | //
+ CertificateSelectionDialog::EncryptOnly | //
+ CertificateSelectionDialog::MultiSelection | //
CertificateSelectionDialog::optionsFromProtocol(prot);
dlg->setOptions(options);
return dlg;
}
void ResolveRecipientsPage::ItemWidget::showSelectionDialog()
{
QPointer<CertificateSelectionDialog> 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<Key> 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<Key> &pgp, const std::vector<Key> &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<Key> ResolveRecipientsPage::ItemWidget::certificates() const
{
std::vector<Key> 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<RecipientPreferences> 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("<b>Recipients</b>"));
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<Key> 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("<b>Warning:</b> 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<Protocol>(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<CertificateSelectionDialog> dlg = createCertificateSelectionDialog(q, q->selectedProtocol());
if (dlg->exec() != QDialog::Accepted || !dlg /*q already deleted*/) {
return;
}
const std::vector<Key> 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<Key> pgp = key.protocol() == OpenPGP ? std::vector<Key>(1, key) : std::vector<Key>();
const std::vector<Key> cms = key.protocol() == CMS ? std::vector<Key>(1, key) : std::vector<Key>();
m_listWidget->setCertificates(rec, pgp, cms);
}
Q_EMIT q->completeChanged();
}
namespace
{
std::vector<Key> makeSuggestions(const std::shared_ptr<RecipientPreferences> &prefs, const Mailbox &mb, GpgME::Protocol prot)
{
std::vector<Key> 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<Key> &keys)
{
QStringList list;
std::transform(keys.begin(), keys.end(), list.begin(), &Formatting::formatKeyLink);
return list.join(QLatin1String("<br/>"));
}
void ResolveRecipientsPage::setAdditionalRecipientsInfo(const std::vector<Key> &recipients)
{
d->m_additionalRecipientsLabel->setVisible(!recipients.empty());
if (recipients.empty()) {
return;
}
d->m_additionalRecipientsLabel->setText(
i18n("<qt><p>Recipients predefined via GnuPG settings:</p>%1</qt>",
listKeysForInfo(recipients)));
}
void ResolveRecipientsPage::setRecipients(const std::vector<Mailbox> &recipients, const std::vector<Mailbox> &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<Key> pgp = makeSuggestions(d->m_recipientPreferences, mb, OpenPGP);
const std::vector<Key> 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<Key> pgp = makeSuggestions(d->m_recipientPreferences, i, OpenPGP);
const std::vector<Key> 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<Key> ResolveRecipientsPage::resolvedCertificates() const
{
std::vector<Key> 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<RecipientPreferences> ResolveRecipientsPage::recipientPreferences() const
{
return d->m_recipientPreferences;
}
void ResolveRecipientsPage::setRecipientPreferences(const std::shared_ptr<RecipientPreferences> &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/resolverecipientspage_p.h b/src/crypto/gui/resolverecipientspage_p.h
index 6773bb247..da2f758aa 100644
--- a/src/crypto/gui/resolverecipientspage_p.h
+++ b/src/crypto/gui/resolverecipientspage_p.h
@@ -1,107 +1,107 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/resolverecipientspage_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 <crypto/gui/resolverecipientspage.h>
#include <KMime/HeaderParsing>
#include <QHash>
class QComboBox;
class QLabel;
class QListWidget;
class QListWidgetItem;
#include <QStringList>
class QToolButton;
class Kleo::Crypto::Gui::ResolveRecipientsPage::ListWidget : public QWidget
{
Q_OBJECT
public:
explicit ListWidget(QWidget *parent = nullptr, Qt::WindowFlags flags = {});
~ListWidget() override;
void addEntry(const QString &id, const QString &name);
void addEntry(const KMime::Types::Mailbox &mbox);
void addEntry(const QString &id, const QString &name, const KMime::Types::Mailbox &mbox);
void removeEntry(const QString &id);
QStringList selectedEntries() const;
void setCertificates(const QString &id, const std::vector<GpgME::Key> &pgpCerts, const std::vector<GpgME::Key> &cmsCerts);
GpgME::Key selectedCertificate(const QString &id) const;
GpgME::Key selectedCertificate(const QString &id, GpgME::Protocol prot) const;
KMime::Types::Mailbox mailbox(const QString &id) const;
QStringList identifiers() const;
void setProtocol(GpgME::Protocol prot);
void showSelectionDialog(const QString &id);
enum Role {
- IdRole = Qt::UserRole
+ IdRole = Qt::UserRole,
};
Q_SIGNALS:
void selectionChanged();
void completeChanged();
private Q_SLOTS:
void onSelectionChange();
private:
QListWidget *m_listWidget;
QHash<QString, ItemWidget *> widgets;
QHash<QString, QListWidgetItem *> items;
GpgME::Protocol m_protocol;
};
class Kleo::Crypto::Gui::ResolveRecipientsPage::ItemWidget : public QWidget
{
Q_OBJECT
public:
explicit ItemWidget(const QString &id, const QString &name, const KMime::Types::Mailbox &mbox, QWidget *parent = nullptr, Qt::WindowFlags flags = {});
~ItemWidget() override;
QString id() const;
KMime::Types::Mailbox mailbox() const;
void setCertificates(const std::vector<GpgME::Key> &pgp,
const std::vector<GpgME::Key> &cms);
GpgME::Key selectedCertificate() const;
GpgME::Key selectedCertificate(GpgME::Protocol prot) const;
std::vector<GpgME::Key> certificates() const;
void setProtocol(GpgME::Protocol protocol);
void setSelected(bool selected);
bool isSelected() const;
public Q_SLOTS:
void showSelectionDialog();
Q_SIGNALS:
void changed();
private:
void addCertificateToComboBox(const GpgME::Key &key);
void resetCertificates();
void selectCertificateInComboBox(const GpgME::Key &key);
void updateVisibility();
private:
QString m_id;
KMime::Types::Mailbox m_mailbox;
QLabel *m_nameLabel;
QLabel *m_certLabel;
QComboBox *m_certCombo;
QToolButton *m_selectButton;
GpgME::Protocol m_protocol;
QHash<GpgME::Protocol, GpgME::Key> m_selectedCertificates;
std::vector<GpgME::Key> m_pgp, m_cms;
bool m_selected;
};
diff --git a/src/crypto/gui/resultitemwidget.cpp b/src/crypto/gui/resultitemwidget.cpp
index 65f189437..3a6a8532b 100644
--- a/src/crypto/gui/resultitemwidget.cpp
+++ b/src/crypto/gui/resultitemwidget.cpp
@@ -1,360 +1,360 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/resultitemwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 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
*/
#include <config-kleopatra.h>
#include "resultitemwidget.h"
#include "commands/command.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/lookupcertificatescommand.h"
#include "crypto/decryptverifytask.h"
#include "view/htmllabel.h"
#include "view/urllabel.h"
#include <Libkleo/AuditLogEntry>
#include <Libkleo/AuditLogViewer>
#include <Libkleo/Classify>
#include <Libkleo/SystemInfo>
#include <gpgme++/key.h>
#include <gpgme++/decryptionresult.h>
#include <KLocalizedString>
#include <QPushButton>
#include <KStandardGuiItem>
#include "kleopatra_debug.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QUrl>
#include <QVBoxLayout>
#include <KGuiItem>
#include <KColorScheme>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
namespace
{
// TODO move out of here
static QColor colorForVisualCode(Task::Result::VisualCode code)
{
switch (code) {
case Task::Result::AllGood:
return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color();
case Task::Result::NeutralError:
case Task::Result::Warning:
return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color();
case Task::Result::Danger:
return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color();
case Task::Result::NeutralSuccess:
default:
return QColor(0x00, 0x80, 0xFF); // light blue
}
}
static QColor txtColorForVisualCode(Task::Result::VisualCode code)
{
switch (code) {
case Task::Result::AllGood:
case Task::Result::NeutralError:
case Task::Result::Warning:
return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NormalText).color();
case Task::Result::Danger:
return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color();
case Task::Result::NeutralSuccess:
default:
return QColor(0xFF, 0xFF, 0xFF); // white
}
}
}
class ResultItemWidget::Private
{
ResultItemWidget *const q;
public:
explicit Private(const std::shared_ptr<const Task::Result> &result, ResultItemWidget *qq) : q(qq), m_result(result)
{
Q_ASSERT(m_result);
}
void slotLinkActivated(const QString &);
void updateShowDetailsLabel();
void addKeyImportButton(QBoxLayout *lay, bool search);
void addIgnoreMDCButton(QBoxLayout *lay);
void oneImportFinished();
const std::shared_ptr<const Task::Result> m_result;
UrlLabel *m_auditLogLabel = nullptr;
QPushButton *m_closeButton = nullptr;
bool m_importCanceled = false;
};
void ResultItemWidget::Private::oneImportFinished()
{
if (m_importCanceled) {
return;
}
if (m_result->parentTask()) {
m_result->parentTask()->start();
}
q->setVisible(false);
}
void ResultItemWidget::Private::addIgnoreMDCButton(QBoxLayout *lay)
{
if (!m_result || !lay) {
return;
}
const auto dvResult = dynamic_cast<const DecryptVerifyResult *>(m_result.get());
if (!dvResult) {
return;
}
const auto decResult = dvResult->decryptionResult();
if (decResult.isNull() || !decResult.error() || !decResult.isLegacyCipherNoMDC())
{
return;
}
auto btn = new QPushButton(i18n("Force decryption"));
btn->setFixedSize(btn->sizeHint());
connect (btn, &QPushButton::clicked, q, [this] () {
if (m_result->parentTask()) {
const auto dvTask = dynamic_cast<DecryptVerifyTask*>(m_result->parentTask().data());
dvTask->setIgnoreMDCError(true);
dvTask->start();
q->setVisible(false);
} else {
qCWarning(KLEOPATRA_LOG) << "Failed to get parent task";
}
});
lay->addWidget(btn);
}
void ResultItemWidget::Private::addKeyImportButton(QBoxLayout *lay, bool search)
{
if (!m_result || !lay) {
return;
}
const auto dvResult = dynamic_cast<const DecryptVerifyResult *>(m_result.get());
if (!dvResult) {
return;
}
const auto verifyResult = dvResult->verificationResult();
if (verifyResult.isNull()) {
return;
}
for (const auto &sig: verifyResult.signatures()) {
if (!(sig.summary() & GpgME::Signature::KeyMissing)) {
continue;
}
auto btn = new QPushButton;
QString suffix;
const auto keyid = QLatin1String(sig.fingerprint());
if (verifyResult.numSignatures() > 1) {
suffix = QLatin1Char(' ') + keyid;
}
btn = new QPushButton(search ? i18nc("1 is optional keyid. No space is intended as it can be empty.",
"Search%1", suffix)
: i18nc("1 is optional keyid. No space is intended as it can be empty.",
"Import%1", suffix));
if (search) {
btn->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
connect (btn, &QPushButton::clicked, q, [this, btn, keyid] () {
btn->setEnabled(false);
m_importCanceled = false;
auto cmd = new Kleo::Commands::LookupCertificatesCommand(keyid, nullptr);
connect(cmd, &Kleo::Commands::LookupCertificatesCommand::canceled,
q, [this]() { m_importCanceled = true; });
connect(cmd, &Kleo::Commands::LookupCertificatesCommand::finished,
q, [this, btn]() {
btn->setEnabled(true);
oneImportFinished();
});
cmd->setParentWidget(q);
cmd->start();
});
} else {
btn->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import")));
connect (btn, &QPushButton::clicked, q, [this, btn] () {
btn->setEnabled(false);
m_importCanceled = false;
auto cmd = new Kleo::ImportCertificateFromFileCommand();
connect(cmd, &Kleo::ImportCertificateFromFileCommand::canceled,
q, [this]() { m_importCanceled = true; });
connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished,
q, [this, btn]() {
btn->setEnabled(true);
oneImportFinished();
});
cmd->setParentWidget(q);
cmd->start();
});
}
btn->setFixedSize(btn->sizeHint());
lay->addWidget(btn);
}
}
static QUrl auditlog_url_template()
{
QUrl url(QStringLiteral("kleoresultitem://showauditlog"));
return url;
}
void ResultItemWidget::Private::updateShowDetailsLabel()
{
const auto auditLogUrl = m_result->auditLog().asUrl(auditlog_url_template());
const auto auditLogLinkText =
- m_result->hasError() ? i18n("Diagnostics")
+ m_result->hasError() ? i18n("Diagnostics") //
: i18nc("The Audit Log is a detailed error log from the gnupg backend",
"Show Audit Log");
m_auditLogLabel->setUrl(auditLogUrl, auditLogLinkText);
m_auditLogLabel->setVisible(!auditLogUrl.isEmpty());
}
ResultItemWidget::ResultItemWidget(const std::shared_ptr<const Task::Result> &result, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags), d(new Private(result, this))
{
const QColor color = colorForVisualCode(d->m_result->code());
const QColor txtColor = txtColorForVisualCode(d->m_result->code());
const QColor linkColor = txtColor;
const QString styleSheet = SystemInfo::isHighContrastModeActive()
? QStringLiteral(
"QFrame,QLabel { margin: 0px; }"
"QFrame#resultFrame{ border-style: solid; border-radius: 3px; border-width: 1px }"
"QLabel { padding: 5px; border-radius: 3px }")
: QStringLiteral(
"QFrame,QLabel { background-color: %1; margin: 0px; }"
"QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }"
"QLabel { color: %3; padding: 5px; border-radius: 3px }")
.arg(color.name())
.arg(color.darker(150).name())
.arg(txtColor.name());
auto topLayout = new QVBoxLayout(this);
auto frame = new QFrame;
frame->setObjectName(QStringLiteral("resultFrame"));
frame->setStyleSheet(styleSheet);
topLayout->addWidget(frame);
auto layout = new QHBoxLayout(frame);
auto vlay = new QVBoxLayout();
auto overview = new HtmlLabel;
overview->setWordWrap(true);
overview->setHtml(d->m_result->overview());
overview->setStyleSheet(styleSheet);
overview->setLinkColor(linkColor);
setFocusPolicy(overview->focusPolicy());
setFocusProxy(overview);
connect(overview, &QLabel::linkActivated,
this, [this](const auto &link) { d->slotLinkActivated(link); });
vlay->addWidget(overview);
layout->addLayout(vlay);
auto actionLayout = new QVBoxLayout;
layout->addLayout(actionLayout);
d->addKeyImportButton(actionLayout, false);
// TODO: Only show if auto-key-retrieve is not set.
d->addKeyImportButton(actionLayout, true);
d->addIgnoreMDCButton(actionLayout);
d->m_auditLogLabel = new UrlLabel;
connect(d->m_auditLogLabel, &QLabel::linkActivated,
this, [this](const auto &link) { d->slotLinkActivated(link); });
actionLayout->addWidget(d->m_auditLogLabel);
d->m_auditLogLabel->setStyleSheet(styleSheet);
d->m_auditLogLabel->setLinkColor(linkColor);
auto detailsLabel = new HtmlLabel;
detailsLabel->setWordWrap(true);
detailsLabel->setHtml(d->m_result->details());
detailsLabel->setStyleSheet(styleSheet);
detailsLabel->setLinkColor(linkColor);
connect(detailsLabel, &QLabel::linkActivated,
this, [this](const auto &link) { d->slotLinkActivated(link); });
vlay->addWidget(detailsLabel);
d->m_closeButton = new QPushButton;
KGuiItem::assign(d->m_closeButton, KStandardGuiItem::close());
d->m_closeButton->setFixedSize(d->m_closeButton->sizeHint());
connect(d->m_closeButton, &QAbstractButton::clicked, this, &ResultItemWidget::closeButtonClicked);
actionLayout->addWidget(d->m_closeButton);
d->m_closeButton->setVisible(false);
layout->setStretch(0, 1);
actionLayout->addStretch(-1);
vlay->addStretch(-1);
d->updateShowDetailsLabel();
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum);
}
ResultItemWidget::~ResultItemWidget()
{
}
void ResultItemWidget::showCloseButton(bool show)
{
d->m_closeButton->setVisible(show);
}
bool ResultItemWidget::hasErrorResult() const
{
return d->m_result->hasError();
}
void ResultItemWidget::Private::slotLinkActivated(const QString &link)
{
Q_ASSERT(m_result);
qCDebug(KLEOPATRA_LOG) << "Link activated: " << link;
if (link.startsWith(QLatin1String("key:"))) {
auto split = link.split(QLatin1Char(':'));
auto fpr = split.value(1);
if (split.size() == 2 && isFingerprint(fpr)) {
/* There might be a security consideration here if somehow
* a short keyid is used in a link and it collides with another.
* So we additionally check that it really is a fingerprint. */
auto cmd = Command::commandForQuery(fpr);
cmd->setParentWId(q->effectiveWinId());
cmd->start();
} else {
qCWarning(KLEOPATRA_LOG) << "key link invalid " << link;
}
return;
}
const QUrl url(link);
if (url.host() == QLatin1String("showauditlog")) {
q->showAuditLog();
return;
}
qCWarning(KLEOPATRA_LOG) << "Unexpected link scheme: " << link;
}
void ResultItemWidget::showAuditLog()
{
AuditLogViewer::showAuditLog(parentWidget(), d->m_result->auditLog());
}
#include "moc_resultitemwidget.cpp"
diff --git a/src/crypto/gui/signencryptemailconflictdialog.cpp b/src/crypto/gui/signencryptemailconflictdialog.cpp
index caa50e81b..f4459b919 100644
--- a/src/crypto/gui/signencryptemailconflictdialog.cpp
+++ b/src/crypto/gui/signencryptemailconflictdialog.cpp
@@ -1,638 +1,640 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/signencryptemailconflictdialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signencryptemailconflictdialog.h"
#include <crypto/sender.h>
#include <crypto/recipient.h>
#include "dialogs/certificateselectiondialog.h"
#include "certificateselectionline.h"
#include <Libkleo/GnuPG>
#include "utils/gui-helper.h"
#include "utils/kleo_assert.h"
#include <Libkleo/Compliance>
#include <Libkleo/Stl_Util>
#include <Libkleo/Formatting>
#include <Libkleo/SystemInfo>
#include <gpgme++/key.h>
#include <KMime/HeaderParsing>
#include <KLocalizedString>
#include <KColorScheme>
#include <QLabel>
#include <QLayout>
#include <QGroupBox>
#include <QDialogButtonBox>
#include <QCheckBox>
#include <QRadioButton>
#include <QPushButton>
#include <QPointer>
#include <QSignalBlocker>
#include <QToolButton>
#include <algorithm>
#include <iterator>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace Kleo::Crypto::Gui;
using namespace Kleo::Dialogs;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(GpgME::UserID)
static CertificateSelectionDialog *
create_certificate_selection_dialog(QWidget *parent, Protocol proto)
{
auto const dlg = new CertificateSelectionDialog(parent);
dlg->setOptions(proto == OpenPGP ? CertificateSelectionDialog::OpenPGPFormat :
proto == CMS ? CertificateSelectionDialog::CMSFormat : CertificateSelectionDialog::AnyFormat);
return dlg;
}
static CertificateSelectionDialog *
create_encryption_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox)
{
CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto);
dlg->setCustomLabelText(i18n("Please select an encryption certificate for recipient \"%1\"", mailbox));
- dlg->setOptions(CertificateSelectionDialog::SingleSelection |
- CertificateSelectionDialog::EncryptOnly |
+ dlg->setOptions(CertificateSelectionDialog::SingleSelection | //
+ CertificateSelectionDialog::EncryptOnly | //
dlg->options());
return dlg;
}
static CertificateSelectionDialog *
create_signing_certificate_selection_dialog(QWidget *parent, Protocol proto, const QString &mailbox)
{
CertificateSelectionDialog *const dlg = create_certificate_selection_dialog(parent, proto);
dlg->setCustomLabelText(i18n("Please select a signing certificate for sender \"%1\"", mailbox));
- dlg->setOptions(CertificateSelectionDialog::SingleSelection |
- CertificateSelectionDialog::SignOnly |
- CertificateSelectionDialog::SecretKeys |
+ dlg->setOptions(CertificateSelectionDialog::SingleSelection | //
+ CertificateSelectionDialog::SignOnly | //
+ CertificateSelectionDialog::SecretKeys | //
dlg->options());
return dlg;
}
static QString make_top_label_conflict_text(bool sign, bool enc)
{
return
sign && enc ? i18n("Kleopatra cannot unambiguously determine matching certificates "
"for all recipients/senders of the message.\n"
"Please select the correct certificates for each recipient:") :
sign ? i18n("Kleopatra cannot unambiguously determine matching certificates "
"for the sender of the message.\n"
"Please select the correct certificates for the sender:") :
enc ? i18n("Kleopatra cannot unambiguously determine matching certificates "
"for all recipients of the message.\n"
"Please select the correct certificates for each recipient:") :
- /* else */ (kleo_assert_fail(sign || enc), QString());
+ (kleo_assert_fail(sign || enc), QString());
}
static QString make_top_label_quickmode_text(bool sign, bool enc)
{
return
enc ? i18n("Please verify that correct certificates have been selected for each recipient:") :
sign ? i18n("Please verify that the correct certificate has been selected for the sender:") :
- /*else*/ (kleo_assert_fail(sign || enc), QString());
+ (kleo_assert_fail(sign || enc), QString());
}
class SignEncryptEMailConflictDialog::Private
{
friend class ::Kleo::Crypto::Gui::SignEncryptEMailConflictDialog;
SignEncryptEMailConflictDialog *const q;
public:
explicit Private(SignEncryptEMailConflictDialog *qq)
: q(qq),
senders(),
recipients(),
sign(true),
encrypt(true),
presetProtocol(UnknownProtocol),
ui(q)
{
}
private:
void updateTopLabelText()
{
ui.conflictTopLB.setText(make_top_label_conflict_text(sign, encrypt));
ui.quickModeTopLB.setText(make_top_label_quickmode_text(sign, encrypt));
}
void showHideWidgets()
{
const Protocol proto = q->selectedProtocol();
const bool quickMode = q->isQuickMode();
const bool needProtocolSelection = presetProtocol == UnknownProtocol;
const bool needShowAllRecipientsCB =
quickMode ? false :
needProtocolSelection ? needShowAllRecipients(OpenPGP) || needShowAllRecipients(CMS) :
- /* else */ needShowAllRecipients(proto)
+ needShowAllRecipients(proto)
;
ui.showAllRecipientsCB.setVisible(needShowAllRecipientsCB);
ui.pgpRB.setVisible(needProtocolSelection);
ui.cmsRB.setVisible(needProtocolSelection);
const bool showAll = !needShowAllRecipientsCB || ui.showAllRecipientsCB.isChecked();
bool first;
first = true;
for (const CertificateSelectionLine &line : std::as_const(ui.signers)) {
line.showHide(proto, first, showAll, sign);
}
ui.selectSigningCertificatesGB.setVisible(sign && (showAll || !first));
first = true;
for (const CertificateSelectionLine &line : std::as_const(ui.recipients)) {
line.showHide(proto, first, showAll, encrypt);
}
ui.selectEncryptionCertificatesGB.setVisible(encrypt && (showAll || !first));
}
bool needShowAllRecipients(Protocol proto) const
{
if (sign) {
if (const unsigned int num = std::count_if(ui.signers.cbegin(), ui.signers.cend(),
[proto](const CertificateSelectionLine &l) {
return l.wasInitiallyAmbiguous(proto);
})) {
if (num != ui.signers.size()) {
return true;
}
}
}
if (encrypt) {
if (const unsigned int num = std::count_if(ui.recipients.cbegin(), ui.recipients.cend(),
[proto](const CertificateSelectionLine &l) {
return l.wasInitiallyAmbiguous(proto);
})) {
if (num != ui.recipients.size()) {
return true;
}
}
}
return false;
}
void createSendersAndRecipients()
{
ui.clearSendersAndRecipients();
ui.addSelectSigningCertificatesGB();
for (const Sender &s : std::as_const(senders)) {
addSigner(s);
}
ui.addSelectEncryptionCertificatesGB();
for (const Sender &s : std::as_const(senders)) {
addRecipient(s);
}
for (const Recipient &r : std::as_const(recipients)) {
addRecipient(r);
}
}
void addSigner(const Sender &s)
{
ui.addSigner(s.mailbox().prettyAddress(),
s.signingCertificateCandidates(OpenPGP),
s.isSigningAmbiguous(OpenPGP),
s.signingCertificateCandidates(CMS),
s.isSigningAmbiguous(CMS),
q);
}
void addRecipient(const Sender &s)
{
ui.addRecipient(s.mailbox().prettyAddress(),
s.encryptToSelfCertificateCandidates(OpenPGP),
s.isEncryptionAmbiguous(OpenPGP),
s.encryptToSelfCertificateCandidates(CMS),
s.isEncryptionAmbiguous(CMS),
q);
}
void addRecipient(const Recipient &r)
{
ui.addRecipient(r.mailbox().prettyAddress(),
r.encryptionCertificateCandidates(OpenPGP),
r.isEncryptionAmbiguous(OpenPGP),
r.encryptionCertificateCandidates(CMS),
r.isEncryptionAmbiguous(CMS),
q);
}
bool isComplete(Protocol proto) const;
private:
void updateComplianceStatus()
{
if (!DeVSCompliance::isCompliant()) {
return;
}
if (q->selectedProtocol() == UnknownProtocol ||
(q->resolvedSigningKeys().empty() && q->resolvedEncryptionKeys().empty())) {
return;
}
// Handle compliance
bool de_vs = true;
for (const auto &key: q->resolvedSigningKeys()) {
if (!DeVSCompliance::keyIsCompliant(key)) {
de_vs = false;
break;
}
}
if (de_vs) {
for (const auto &key: q->resolvedEncryptionKeys()) {
if (!DeVSCompliance::keyIsCompliant(key)) {
de_vs = false;
break;
}
}
}
auto btn = ui.buttonBox.button(QDialogButtonBox::Ok);
DeVSCompliance::decorate(btn, de_vs);
ui.complianceLB.setText(DeVSCompliance::name(de_vs));
ui.complianceLB.setVisible(true);
}
void updateDialogStatus()
{
ui.setOkButtonEnabled(q->isComplete());
updateComplianceStatus();
}
void slotCompleteChanged()
{
updateDialogStatus();
}
void slotShowAllRecipientsToggled(bool)
{
showHideWidgets();
}
void slotProtocolChanged()
{
showHideWidgets();
updateDialogStatus();
}
void slotCertificateSelectionDialogRequested()
{
const QObject *const s = q->sender();
const Protocol proto = q->selectedProtocol();
QPointer<CertificateSelectionDialog> dlg;
for (const CertificateSelectionLine &l : std::as_const(ui.signers))
if (s == l.toolButton()) {
dlg = create_signing_certificate_selection_dialog(q, proto, l.mailboxText());
if (dlg->exec()) {
l.addAndSelectCertificate(dlg->selectedCertificate());
}
// ### switch to key.protocol(), in case proto == UnknownProtocol
break;
}
for (const CertificateSelectionLine &l : std::as_const(ui.recipients))
if (s == l.toolButton()) {
dlg = create_encryption_certificate_selection_dialog(q, proto, l.mailboxText());
if (dlg->exec()) {
l.addAndSelectCertificate(dlg->selectedCertificate());
}
// ### switch to key.protocol(), in case proto == UnknownProtocol
break;
}
#ifndef Q_OS_WIN
// This leads to a crash on Windows. We don't really
// leak memory here anyway because the destruction of the
// dialog happens when the parent (q) is destroyed anyway.
delete dlg;
#endif
}
private:
std::vector<Sender> senders;
std::vector<Recipient> recipients;
bool sign : 1;
bool encrypt : 1;
Protocol presetProtocol;
private:
struct Ui {
QLabel conflictTopLB, quickModeTopLB;
QCheckBox showAllRecipientsCB;
QRadioButton pgpRB, cmsRB;
QGroupBox selectSigningCertificatesGB;
QGroupBox selectEncryptionCertificatesGB;
QCheckBox quickModeCB;
QDialogButtonBox buttonBox;
QVBoxLayout vlay;
QHBoxLayout hlay;
QHBoxLayout hlay2;
QGridLayout glay;
std::vector<CertificateSelectionLine> signers, recipients;
QLabel complianceLB;
void setOkButtonEnabled(bool enable)
{
return buttonBox.button(QDialogButtonBox::Ok)->setEnabled(enable);
}
explicit Ui(SignEncryptEMailConflictDialog *q)
: conflictTopLB(make_top_label_conflict_text(true, true), q),
quickModeTopLB(make_top_label_quickmode_text(true, true), q),
showAllRecipientsCB(i18n("Show all recipients"), q),
pgpRB(i18n("OpenPGP"), q),
cmsRB(i18n("S/MIME"), q),
selectSigningCertificatesGB(i18n("Select Signing Certificate"), q),
selectEncryptionCertificatesGB(i18n("Select Encryption Certificate"), q),
quickModeCB(i18n("Only show this dialog in case of conflicts (experimental)"), q),
buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, q),
vlay(q),
hlay(),
glay(),
signers(),
recipients()
{
KDAB_SET_OBJECT_NAME(conflictTopLB);
KDAB_SET_OBJECT_NAME(quickModeTopLB);
KDAB_SET_OBJECT_NAME(showAllRecipientsCB);
KDAB_SET_OBJECT_NAME(pgpRB);
KDAB_SET_OBJECT_NAME(cmsRB);
KDAB_SET_OBJECT_NAME(selectSigningCertificatesGB);
KDAB_SET_OBJECT_NAME(selectEncryptionCertificatesGB);
KDAB_SET_OBJECT_NAME(quickModeCB);
KDAB_SET_OBJECT_NAME(buttonBox);
KDAB_SET_OBJECT_NAME(hlay);
KDAB_SET_OBJECT_NAME(glay);
KDAB_SET_OBJECT_NAME(vlay);
q->setWindowTitle(i18nc("@title:window", "Select Certificates for Message"));
conflictTopLB.hide();
selectSigningCertificatesGB.setFlat(true);
selectEncryptionCertificatesGB.setFlat(true);
selectSigningCertificatesGB.setAlignment(Qt::AlignCenter);
selectEncryptionCertificatesGB.setAlignment(Qt::AlignCenter);
glay.setColumnStretch(2, 1);
glay.setColumnStretch(3, 1);
vlay.setSizeConstraint(QLayout::SetMinimumSize);
vlay.addWidget(&conflictTopLB);
vlay.addWidget(&quickModeTopLB);
hlay.addWidget(&showAllRecipientsCB);
hlay.addStretch(1);
hlay.addWidget(&pgpRB);
hlay.addWidget(&cmsRB);
vlay.addLayout(&hlay);
addSelectSigningCertificatesGB();
addSelectEncryptionCertificatesGB();
vlay.addLayout(&glay);
vlay.addStretch(1);
complianceLB.setVisible(false);
hlay2.addStretch(1);
hlay2.addWidget(&complianceLB, 0, Qt::AlignRight);
hlay2.addWidget(&buttonBox, 0, Qt::AlignRight);
vlay.addWidget(&quickModeCB, 0, Qt::AlignRight);
vlay.addLayout(&hlay2);
connect(&buttonBox, &QDialogButtonBox::accepted, q, &SignEncryptEMailConflictDialog::accept);
connect(&buttonBox, &QDialogButtonBox::rejected, q, &SignEncryptEMailConflictDialog::reject);
connect(&showAllRecipientsCB, SIGNAL(toggled(bool)), q, SLOT(slotShowAllRecipientsToggled(bool)));
connect(&pgpRB, SIGNAL(toggled(bool)),
q, SLOT(slotProtocolChanged()));
connect(&cmsRB, SIGNAL(toggled(bool)),
q, SLOT(slotProtocolChanged()));
}
void clearSendersAndRecipients()
{
std::vector<CertificateSelectionLine> sig, enc;
sig.swap(signers);
enc.swap(recipients);
std::for_each(sig.begin(), sig.end(), std::mem_fn(&CertificateSelectionLine::kill));
std::for_each(enc.begin(), enc.end(), std::mem_fn(&CertificateSelectionLine::kill));
glay.removeWidget(&selectSigningCertificatesGB);
glay.removeWidget(&selectEncryptionCertificatesGB);
}
void addSelectSigningCertificatesGB()
{
glay.addWidget(&selectSigningCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns);
}
void addSelectEncryptionCertificatesGB()
{
glay.addWidget(&selectEncryptionCertificatesGB, glay.rowCount(), 0, 1, CertificateSelectionLine::NumColumns);
}
void addSigner(const QString &mailbox,
const std::vector<Key> &pgp, bool pgpAmbiguous,
const std::vector<Key> &cms, bool cmsAmbiguous, QWidget *q)
{
CertificateSelectionLine line(i18n("From:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay);
signers.push_back(line);
}
void addRecipient(const QString &mailbox,
const std::vector<Key> &pgp, bool pgpAmbiguous,
const std::vector<Key> &cms, bool cmsAmbiguous, QWidget *q)
{
CertificateSelectionLine line(i18n("To:"), mailbox, pgp, pgpAmbiguous, cms, cmsAmbiguous, q, glay);
recipients.push_back(line);
}
} ui;
};
SignEncryptEMailConflictDialog::SignEncryptEMailConflictDialog(QWidget *parent)
: QDialog(parent), d(new Private(this))
{
}
SignEncryptEMailConflictDialog::~SignEncryptEMailConflictDialog() {}
void SignEncryptEMailConflictDialog::setPresetProtocol(Protocol p)
{
if (p == d->presetProtocol) {
return;
}
const QSignalBlocker pgpBlocker(d->ui.pgpRB);
const QSignalBlocker cmsBlocker(d->ui.cmsRB);
really_check(d->ui.pgpRB, p == OpenPGP);
really_check(d->ui.cmsRB, p == CMS);
d->presetProtocol = p;
d->showHideWidgets();
d->updateDialogStatus();
}
Protocol SignEncryptEMailConflictDialog::selectedProtocol() const
{
if (d->presetProtocol != UnknownProtocol) {
return d->presetProtocol;
}
if (d->ui.pgpRB.isChecked()) {
return OpenPGP;
}
if (d->ui.cmsRB.isChecked()) {
return CMS;
}
return UnknownProtocol;
}
void SignEncryptEMailConflictDialog::setSubject(const QString &subject)
{
setWindowTitle(i18nc("@title:window", "Select Certificates for Message \"%1\"", subject));
}
void SignEncryptEMailConflictDialog::setSign(bool sign)
{
if (sign == d->sign) {
return;
}
d->sign = sign;
d->updateTopLabelText();
d->showHideWidgets();
d->updateDialogStatus();
}
void SignEncryptEMailConflictDialog::setEncrypt(bool encrypt)
{
if (encrypt == d->encrypt) {
return;
}
d->encrypt = encrypt;
d->updateTopLabelText();
d->showHideWidgets();
d->updateDialogStatus();
}
void SignEncryptEMailConflictDialog::setSenders(const std::vector<Sender> &senders)
{
if (senders == d->senders) {
return;
}
d->senders = senders;
d->createSendersAndRecipients();
d->showHideWidgets();
d->updateDialogStatus();
}
void SignEncryptEMailConflictDialog::setRecipients(const std::vector<Recipient> &recipients)
{
if (d->recipients == recipients) {
return;
}
d->recipients = recipients;
d->createSendersAndRecipients();
d->showHideWidgets();
d->updateDialogStatus();
}
void SignEncryptEMailConflictDialog::pickProtocol()
{
if (selectedProtocol() != UnknownProtocol) {
return; // already picked
}
const bool pgp = d->isComplete(OpenPGP);
const bool cms = d->isComplete(CMS);
if (pgp && !cms) {
d->ui.pgpRB.setChecked(true);
} else if (cms && !pgp) {
d->ui.cmsRB.setChecked(true);
}
}
bool SignEncryptEMailConflictDialog::isComplete() const
{
const Protocol proto = selectedProtocol();
return proto != UnknownProtocol && d->isComplete(proto);
}
bool SignEncryptEMailConflictDialog::Private::isComplete(Protocol proto) const
{
- return (!sign || std::none_of(ui.signers.cbegin(), ui.signers.cend(),
+ return (!sign || std::none_of(ui.signers.cbegin(), //
+ ui.signers.cend(),
[proto](const CertificateSelectionLine &l) {
return l.isStillAmbiguous(proto);
}))
- && (!encrypt || std::none_of(ui.recipients.cbegin(), ui.recipients.cend(),
+ && (!encrypt || std::none_of(ui.recipients.cbegin(), //
+ ui.recipients.cend(),
[proto](const CertificateSelectionLine &l) {
return l.isStillAmbiguous(proto);
}));
}
static std::vector<Key> get_keys(const std::vector<CertificateSelectionLine> &lines, Protocol proto)
{
if (proto == UnknownProtocol) {
return std::vector<Key>();
}
Q_ASSERT(proto == OpenPGP || proto == CMS);
std::vector<Key> keys;
keys.reserve(lines.size());
std::transform(lines.cbegin(), lines.cend(), std::back_inserter(keys),
[proto](const CertificateSelectionLine &l) {
return l.key(proto);
});
return keys;
}
std::vector<Key> SignEncryptEMailConflictDialog::resolvedSigningKeys() const
{
return d->sign ? get_keys(d->ui.signers, selectedProtocol()) : std::vector<Key>();
}
std::vector<Key> SignEncryptEMailConflictDialog::resolvedEncryptionKeys() const
{
return d->encrypt ? get_keys(d->ui.recipients, selectedProtocol()) : std::vector<Key>();
}
void SignEncryptEMailConflictDialog::setQuickMode(bool on)
{
d->ui.quickModeCB.setChecked(on);
}
bool SignEncryptEMailConflictDialog::isQuickMode() const
{
return d->ui.quickModeCB.isChecked();
}
void SignEncryptEMailConflictDialog::setConflict(bool conflict)
{
d->ui.conflictTopLB.setVisible(conflict);
d->ui.quickModeTopLB.setVisible(!conflict);
}
#include "moc_signencryptemailconflictdialog.cpp"
diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp
index 2f9b01675..d63b209d0 100644
--- a/src/crypto/gui/signencryptfileswizard.cpp
+++ b/src/crypto/gui/signencryptfileswizard.cpp
@@ -1,697 +1,697 @@
/* crypto/gui/signencryptfileswizard.cpp
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
*/
#include <config-kleopatra.h>
#include "signencryptfileswizard.h"
#include "signencryptwidget.h"
#include "newresultpage.h"
#include <fileoperationspreferences.h>
#include <settings.h>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KColorScheme>
#include <KConfigGroup>
#include <KWindowConfig>
#include <KMessageBox>
#include <KMessageWidget>
#include "kleopatra_debug.h"
#include <Libkleo/Compliance>
#include <Libkleo/GnuPG>
#include <Libkleo/Formatting>
#include <Libkleo/SystemInfo>
#include <Libkleo/FileNameRequester>
#include <QWindow>
#include <QVBoxLayout>
#include <QWizardPage>
#include <QGroupBox>
#include <QLabel>
#include <QIcon>
#include <QCheckBox>
#include <QPushButton>
#include <QStyle>
#include <gpgme++/key.h>
#include <array>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Crypto::Gui;
enum Page {
SigEncPageId,
ResultPageId,
NumPages
};
class FileNameRequesterWithIcon : public QWidget
{
Q_OBJECT
public:
explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr)
: QWidget(parent)
{
auto layout = new QHBoxLayout{this};
layout->setContentsMargins(0, 0, 0, 0);
mIconLabel = new QLabel{this};
mRequester = new FileNameRequester{filter, this};
mRequester->setExistingOnly(false);
layout->addWidget(mIconLabel);
layout->addWidget(mRequester);
setFocusPolicy(mRequester->focusPolicy());
setFocusProxy(mRequester);
connect(mRequester, &FileNameRequester::fileNameChanged,
this, &FileNameRequesterWithIcon::fileNameChanged);
}
void setIcon(const QIcon &icon)
{
mIconLabel->setPixmap(icon.pixmap(32, 32));
}
void setFileName(const QString &name)
{
mRequester->setFileName(name);
}
QString fileName() const
{
return mRequester->fileName();
}
void setNameFilter(const QString &nameFilter)
{
mRequester->setNameFilter(nameFilter);
}
QString nameFilter() const
{
return mRequester->nameFilter();
}
FileNameRequester *requester()
{
return mRequester;
}
Q_SIGNALS:
void fileNameChanged(const QString &filename);
protected:
bool event(QEvent *e) override
{
if (e->type() == QEvent::ToolTipChange) {
mRequester->setToolTip(toolTip());
}
return QWidget::event(e);
}
private:
QLabel *mIconLabel;
FileNameRequester *mRequester;
};
class SigEncPage: public QWizardPage
{
Q_OBJECT
public:
explicit SigEncPage(QWidget *parent = nullptr)
: QWizardPage(parent),
mParent((SignEncryptFilesWizard *) parent),
mWidget(new SignEncryptWidget),
mOutLayout(new QVBoxLayout),
mOutputLabel{nullptr},
mArchive(false),
mUseOutputDir(false),
mSingleFile{true}
{
setTitle(i18nc("@title", "Sign / Encrypt Files"));
auto vLay = new QVBoxLayout(this);
vLay->setContentsMargins(0, 0, 0, 0);
if (!Settings{}.cmsEnabled()) {
mWidget->setProtocol(GpgME::OpenPGP);
}
mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage",
"&Sign as:"));
mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage",
"Encrypt for &me:"));
mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage",
"Encrypt for &others:"));
mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage",
"Encrypt with &password. Anyone you share the password with can read the data."));
vLay->addWidget(mWidget);
connect(mWidget, &SignEncryptWidget::operationChanged, this,
&SigEncPage::updateCommitButton);
connect(mWidget, &SignEncryptWidget::keysChanged, this,
&SigEncPage::updateFileWidgets);
auto outputGrp = new QGroupBox(i18nc("@title:group", "Output"));
outputGrp->setLayout(mOutLayout);
mPlaceholderWidget = new QLabel(i18n("Please select an action."));
mOutLayout->addWidget(mPlaceholderWidget);
mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:"));
mOutLayout->addWidget(mOutputLabel);
createRequesters(mOutLayout);
mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage",
"Encrypt / Sign &each file separately."));
mUseOutputDirChk->setToolTip(i18nc("@info:tooltip",
"Keep each file separate instead of creating an archive for all."));
mOutLayout->addWidget(mUseOutputDirChk);
connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) {
mUseOutputDir = state;
mArchive = !mUseOutputDir && !mSingleFile;
updateFileWidgets();
});
vLay->addWidget(outputGrp);
auto messageWidget = new KMessageWidget;
messageWidget->setMessageType(KMessageWidget::Error);
messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this));
messageWidget->setText(i18n("Invalid compliance settings for signing and encrypting files."));
messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>You cannot use <application>Kleopatra</application> for signing or encrypting files "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
messageWidget->setCloseButtonVisible(false);
messageWidget->setVisible(DeVSCompliance::isActive() && !DeVSCompliance::isCompliant());
vLay->addWidget(messageWidget);
setMinimumHeight(300);
}
void setEncryptionPreset(bool value)
{
mWidget->setEncryptionChecked(value);
}
void setSigningPreset(bool value)
{
mWidget->setSigningChecked(value);
}
bool isComplete() const override
{
if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
return false;
}
return mWidget->isComplete();
}
int nextId() const override
{
return ResultPageId;
}
void initializePage() override
{
setCommitPage(true);
updateCommitButton(mWidget->currentOp());
}
void setArchiveForced(bool archive)
{
mArchive = archive;
setArchiveMutable(!archive);
}
void setArchiveMutable(bool archive)
{
mUseOutputDirChk->setVisible(archive);
if (archive) {
const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false));
} else {
mUseOutputDirChk->setChecked(false);
}
}
void setSingleFile(bool singleFile)
{
mSingleFile = singleFile;
mArchive = !mUseOutputDir && !mSingleFile;
}
bool validatePage() override
{
if (DeVSCompliance::isActive() && !DeVSCompliance::isCompliant()) {
KMessageBox::error(topLevelWidget(),
xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant",
"<para>Sorry! You cannot use <application>Kleopatra</application> for signing or encrypting files "
"because the <application>GnuPG</application> system used by <application>Kleopatra</application> is not %1.</para>",
DeVSCompliance::name(true)));
return false;
}
bool sign = !mWidget->signKey().isNull();
bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty();
if (!mWidget->validate()) {
return false;
}
mWidget->saveOwnKeys();
if (mUseOutputDirChk->isVisible()) {
KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
archCfg.writeEntry("LastUseOutputDir", mUseOutputDir);
}
if (sign && !encrypt && mArchive) {
return KMessageBox::warningContinueCancel(this,
xi18nc("@info",
"<para>Archiving in combination with sign-only currently requires what are known as opaque signatures - "
"unlike detached ones, these embed the content in the signature.</para>"
"<para>This format is rather unusual. You might want to archive the files separately, "
"and then sign the archive as one file with Kleopatra.</para>"
"<para>Future versions of Kleopatra are expected to also support detached signatures in this case.</para>"),
i18nc("@title:window", "Unusual Signature Warning"),
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
QStringLiteral("signencryptfileswizard-archive+sign-only-warning"))
== KMessageBox::Continue;
} else if (sign && !encrypt) {
return true;
}
if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) {
return true;
}
const auto recipientKeys = recipients();
const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys),
[](const auto &k) { return k.hasSecret(); });
if (!hasSecret) {
if (KMessageBox::warningContinueCancel(this,
xi18nc("@info",
"<para>None of the recipients you are encrypting to seems to be your own.</para>"
"<para>This means that you will not be able to decrypt the data anymore, once encrypted.</para>"
"<para>Do you want to continue, or cancel to change the recipient selection?</para>"),
i18nc("@title:window", "Encrypt-To-Self Warning"),
KStandardGuiItem::cont(),
KStandardGuiItem::cancel(),
QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous)
== KMessageBox::Cancel) {
return false;
}
}
return true;
}
std::vector<Key> recipients() const
{
return mWidget->recipients();
}
/* In the future we might find a usecase for multiple
* signers */
std::vector<Key> signers() const
{
const Key k = mWidget->signKey();
if (!k.isNull()) {
return {k};
}
return {};
}
private:
struct RequesterInfo {
SignEncryptFilesWizard::KindNames id;
QString icon;
QString toolTip;
QString accessibleName;
QString nameFilterBinary;
QString nameFilterAscii;
};
void createRequesters(QBoxLayout *lay) {
static const std::array<RequesterInfo, 6> requestersInfo = {{
{
SignEncryptFilesWizard::SignatureCMS,
QStringLiteral("document-sign"),
i18nc("@info:tooltip", "This is the filename of the S/MIME signature."),
i18nc("Lineedit accessible name", "S/MIME signature file"),
i18nc("Name filter binary", "S/MIME Signatures (*.p7s)"),
i18nc("Name filter ASCII", "S/MIME Signatures (*.p7s *.pem)"),
},
{
SignEncryptFilesWizard::SignaturePGP,
QStringLiteral("document-sign"),
i18nc("@info:tooltip", "This is the filename of the detached OpenPGP signature."),
i18nc("Lineedit accessible name", "OpenPGP signature file"),
i18nc("Name filter binary", "OpenPGP Signatures (*.sig *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Signatures (*.asc *.sig)"),
},
{
SignEncryptFilesWizard::CombinedPGP,
QStringLiteral("document-edit-sign-encrypt"),
i18nc("@info:tooltip", "This is the filename of the OpenPGP-signed and encrypted file."),
i18nc("Lineedit accessible name", "OpenPGP signed and encrypted file"),
i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"),
},
{
SignEncryptFilesWizard::EncryptedPGP,
QStringLiteral("document-encrypt"),
i18nc("@info:tooltip", "This is the filename of the OpenPGP encrypted file."),
i18nc("Lineedit accessible name", "OpenPGP encrypted file"),
i18nc("Name filter binary", "OpenPGP Files (*.gpg *.pgp)"),
i18nc("Name filter ASCII", "OpenPGP Files (*.asc)"),
},
{
SignEncryptFilesWizard::EncryptedCMS,
QStringLiteral("document-encrypt"),
i18nc("@info:tooltip", "This is the filename of the S/MIME encrypted file."),
i18nc("Lineedit accessible name", "S/MIME encrypted file"),
i18nc("Name filter binary", "S/MIME Files (*.p7m)"),
i18nc("Name filter ASCII", "S/MIME Files (*.p7m *.pem)"),
},
{
SignEncryptFilesWizard::Directory,
QStringLiteral("folder"),
i18nc("@info:tooltip", "The resulting files are written to this directory."),
i18nc("Lineedit accessible name", "Output directory"),
{},
{},
},
}};
if (!mRequesters.empty()) {
return;
}
const bool isAscii = FileOperationsPreferences().addASCIIArmor();
for (const auto &requester : requestersInfo) {
const auto id = requester.id;
auto requesterWithIcon = new FileNameRequesterWithIcon{id == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this};
requesterWithIcon->setIcon(QIcon::fromTheme(requester.icon));
requesterWithIcon->setToolTip(requester.toolTip);
requesterWithIcon->requester()->setAccessibleNameOfLineEdit(requester.accessibleName);
requesterWithIcon->setNameFilter(isAscii ? requester.nameFilterAscii : requester.nameFilterBinary);
lay->addWidget(requesterWithIcon);
connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, id](const QString &newName) {
mOutNames[id] = newName;
});
mRequesters.insert(id, requesterWithIcon);
}
}
public:
void setOutputNames(const QMap<int, QString> &names) {
Q_ASSERT(mOutNames.isEmpty());
for (auto it = std::begin(names); it != std::end(names); ++it) {
mRequesters.value(it.key())->setFileName(it.value());
}
mOutNames = names;
updateFileWidgets();
}
QMap <int, QString> outputNames() const {
if (!mUseOutputDir) {
auto ret = mOutNames;
ret.remove(SignEncryptFilesWizard::Directory);
return ret;
}
return mOutNames;
}
bool encryptSymmetric() const
{
return mWidget->encryptSymmetric();
}
private Q_SLOTS:
void updateCommitButton(const SignEncryptWidget::Operations op)
{
if (mParent->currentPage() != this) {
return;
}
QString label;
switch (op) {
case SignEncryptWidget::Sign:
label = i18nc("@action:button", "Sign");
break;
case SignEncryptWidget::Encrypt:
label = i18nc("@action:button", "Encrypt");
break;
case SignEncryptWidget::SignAndEncrypt:
label = i18nc("@action:button", "Sign / Encrypt");
break;
default:
;
};
auto btn = qobject_cast<QPushButton*>(mParent->button(QWizard::CommitButton));
if (!label.isEmpty()) {
mParent->setButtonText(QWizard::CommitButton, label);
if (DeVSCompliance::isActive()) {
const bool de_vs = DeVSCompliance::isCompliant() && mWidget->isDeVsAndValid();
DeVSCompliance::decorate(btn, de_vs);
mParent->setLabelText(DeVSCompliance::name(de_vs));
}
} else {
mParent->setButtonText(QWizard::CommitButton, i18n("Next"));
btn->setIcon(QIcon());
btn->setStyleSheet(QString());
}
Q_EMIT completeChanged();
}
void updateFileWidgets()
{
if (mRequesters.isEmpty()) {
return;
}
const std::vector<Key> recipients = mWidget->recipients();
const Key sigKey = mWidget->signKey();
const bool pgp = mWidget->encryptSymmetric() ||
std::any_of(std::cbegin(recipients), std::cend(recipients),
[](const auto &k) { return k.protocol() == Protocol::OpenPGP; });
const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients),
[](const auto &k) { return k.protocol() == Protocol::CMS; });
mOutLayout->setEnabled(false);
if (cms || pgp || !sigKey.isNull()) {
mPlaceholderWidget->setVisible(false);
mOutputLabel->setVisible(true);
mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS);
mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms);
mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp);
mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp);
mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp);
mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir);
auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters),
[](auto w) { return !w->isHidden(); });
mOutputLabel->setBuddy(*firstNotHidden);
} else {
mPlaceholderWidget->setVisible(true);
mOutputLabel->setVisible(false);
std::for_each(std::cbegin(mRequesters), std::cend(mRequesters),
[](auto w) { w->setVisible(false); });
mOutputLabel->setBuddy(nullptr);
}
mOutLayout->setEnabled(true);
}
private:
SignEncryptFilesWizard *mParent;
SignEncryptWidget *mWidget;
QMap <int, QString> mOutNames;
QMap <int, FileNameRequesterWithIcon *> mRequesters;
QVBoxLayout *mOutLayout;
QWidget *mPlaceholderWidget;
QCheckBox *mUseOutputDirChk;
QLabel *mOutputLabel;
bool mArchive;
bool mUseOutputDir;
bool mSingleFile;
};
class ResultPage : public NewResultPage
{
Q_OBJECT
public:
explicit ResultPage(QWidget *parent = nullptr)
: NewResultPage(parent),
mParent((SignEncryptFilesWizard *) parent)
{
setTitle(i18nc("@title", "Results"));
setSubTitle(i18nc("@title",
"Status and progress of the crypto operations is shown here."));
}
void initializePage() override
{
mParent->setLabelText(QString());
}
private:
SignEncryptFilesWizard *mParent;
};
SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f)
: QWizard(parent, f)
{
readConfig();
const bool de_vs = DeVSCompliance::isActive();
#ifdef Q_OS_WIN
// Enforce modern style to avoid vista style ugliness.
setWizardStyle(QWizard::ModernStyle);
#endif
mSigEncPage = new SigEncPage(this);
mResultPage = new ResultPage(this);
connect(this, &QWizard::currentIdChanged, this,
&SignEncryptFilesWizard::slotCurrentIdChanged);
setPage(SigEncPageId, mSigEncPage);
setPage(ResultPageId, mResultPage);
- setOptions(QWizard::IndependentPages |
- (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) |
- QWizard::NoBackButtonOnLastPage |
+ setOptions(QWizard::IndependentPages | //
+ (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | //
+ QWizard::NoBackButtonOnLastPage | //
QWizard::NoBackButtonOnStartPage);
if (de_vs) {
/* We use a custom button to display a label next to the
buttons. */
auto btn = button(QWizard::CustomButton1);
/* We style the button so that it looks and acts like a
label. */
btn->setStyleSheet(QStringLiteral("border: none"));
btn->setFocusPolicy(Qt::NoFocus);
}
}
void SignEncryptFilesWizard::setLabelText(const QString &label)
{
button(QWizard::CommitButton)->setToolTip(label);
setButtonText(QWizard::CustomButton1, label);
}
void SignEncryptFilesWizard::slotCurrentIdChanged(int id)
{
if (id == ResultPageId) {
Q_EMIT operationPrepared();
}
}
SignEncryptFilesWizard::~SignEncryptFilesWizard()
{
qCDebug(KLEOPATRA_LOG);
writeConfig();
}
void SignEncryptFilesWizard::setSigningPreset(bool preset)
{
mSigEncPage->setSigningPreset(preset);
}
void SignEncryptFilesWizard::setSigningUserMutable(bool mut)
{
if (mut == mSigningUserMutable) {
return;
}
mSigningUserMutable = mut;
}
void SignEncryptFilesWizard::setEncryptionPreset(bool preset)
{
mSigEncPage->setEncryptionPreset(preset);
}
void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut)
{
if (mut == mEncryptionUserMutable) {
return;
}
mEncryptionUserMutable = mut;
}
void SignEncryptFilesWizard::setArchiveForced(bool archive)
{
mSigEncPage->setArchiveForced(archive);
}
void SignEncryptFilesWizard::setArchiveMutable(bool archive)
{
mSigEncPage->setArchiveMutable(archive);
}
void SignEncryptFilesWizard::setSingleFile(bool singleFile)
{
mSigEncPage->setSingleFile(singleFile);
}
std::vector<Key> SignEncryptFilesWizard::resolvedRecipients() const
{
return mSigEncPage->recipients();
}
std::vector<Key> SignEncryptFilesWizard::resolvedSigners() const
{
return mSigEncPage->signers();
}
void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr<Kleo::Crypto::TaskCollection> &coll)
{
mResultPage->setTaskCollection(coll);
}
void SignEncryptFilesWizard::setOutputNames(const QMap<int, QString> &map) const
{
mSigEncPage->setOutputNames(map);
}
QMap<int, QString> SignEncryptFilesWizard::outputNames() const
{
return mSigEncPage->outputNames();
}
bool SignEncryptFilesWizard::encryptSymmetric() const
{
return mSigEncPage->encryptSymmetric();
}
void SignEncryptFilesWizard::readConfig()
{
winId(); // ensure there's a window created
// set default window size
windowHandle()->resize(640, 480);
// restore size from config file
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup);
// NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform
// window was created -> QTBUG-40584. We therefore copy the size here.
// TODO: remove once this was resolved in QWidget QPA
resize(windowHandle()->size());
}
void SignEncryptFilesWizard::writeConfig()
{
KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard");
KWindowConfig::saveWindowSize(windowHandle(), cfgGroup);
cfgGroup.sync();
}
#include "signencryptfileswizard.moc"
diff --git a/src/crypto/gui/signencryptfileswizard.h b/src/crypto/gui/signencryptfileswizard.h
index 80bdacdbb..2e46de931 100644
--- a/src/crypto/gui/signencryptfileswizard.h
+++ b/src/crypto/gui/signencryptfileswizard.h
@@ -1,103 +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 <utils/pimpl_ptr.h>
#include <gpgme++/global.h>
#include <QWizard>
#include <QMap>
#include <memory>
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
+ 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<int, QString> &nameMap) const;
QMap<int, QString> outputNames() const;
void setTaskCollection(const std::shared_ptr<Kleo::Crypto::TaskCollection> &coll);
// Outputs
std::vector<GpgME::Key> resolvedRecipients() const;
std::vector<GpgME::Key> 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/signencryptwidget.cpp b/src/crypto/gui/signencryptwidget.cpp
index 91562a11d..ebbf770b1 100644
--- a/src/crypto/gui/signencryptwidget.cpp
+++ b/src/crypto/gui/signencryptwidget.cpp
@@ -1,856 +1,856 @@
/* crypto/gui/signencryptwidget.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-License-Identifier: GPL-2.0-or-later
*/
#include "signencryptwidget.h"
#include "kleopatra_debug.h"
#include "certificatelineedit.h"
#include "fileoperationspreferences.h"
#include "kleopatraapplication.h"
#include "settings.h"
#include "unknownrecipientwidget.h"
#include "dialogs/certificateselectiondialog.h"
#include "utils/gui-helper.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QCheckBox>
#include <QScrollArea>
#include <QScrollBar>
#include <Libkleo/Compliance>
#include <Libkleo/DefaultKeyFilter>
#include <Libkleo/ExpiryChecker>
#include <Libkleo/ExpiryCheckerConfig>
#include <Libkleo/ExpiryCheckerSettings>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeySelectionCombo>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/GnuPG>
#include <KLocalizedString>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KMessageBox>
#include <KMessageWidget>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
namespace {
class SignCertificateFilter: public DefaultKeyFilter
{
public:
SignCertificateFilter(GpgME::Protocol proto) : DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setHasSecret(DefaultKeyFilter::Set);
setCanSign(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
if (proto == GpgME::OpenPGP) {
setIsOpenPGP(DefaultKeyFilter::Set);
} else if (proto == GpgME::CMS) {
setIsOpenPGP(DefaultKeyFilter::NotSet);
}
}
};
class EncryptCertificateFilter: public DefaultKeyFilter
{
public:
EncryptCertificateFilter(GpgME::Protocol proto): DefaultKeyFilter()
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
if (proto == GpgME::OpenPGP) {
setIsOpenPGP(DefaultKeyFilter::Set);
} else if (proto == GpgME::CMS) {
setIsOpenPGP(DefaultKeyFilter::NotSet);
}
}
};
class EncryptSelfCertificateFilter: public EncryptCertificateFilter
{
public:
EncryptSelfCertificateFilter(GpgME::Protocol proto): EncryptCertificateFilter(proto)
{
setRevoked(DefaultKeyFilter::NotSet);
setExpired(DefaultKeyFilter::NotSet);
setCanEncrypt(DefaultKeyFilter::Set);
setHasSecret(DefaultKeyFilter::Set);
setValidIfSMIME(DefaultKeyFilter::Set);
}
};
}
class SignEncryptWidget::Private
{
SignEncryptWidget *const q;
public:
struct RecipientWidgets {
CertificateLineEdit *edit;
KMessageWidget *expiryMessage;
};
explicit Private(SignEncryptWidget *qq, bool sigEncExclusive)
: q{qq}
, mModel{AbstractKeyListModel::createFlatKeyListModel(qq)}
, mIsExclusive{sigEncExclusive}
{
}
CertificateLineEdit* addRecipientWidget();
/* Inserts a new recipient widget after widget @p after or at the end
* if @p after is null. */
CertificateLineEdit* insertRecipientWidget(CertificateLineEdit *after);
void recpRemovalRequested(const RecipientWidgets &recipient);
void onProtocolChanged();
void updateCheckBoxes();
ExpiryChecker *expiryChecker();
void updateExpiryMessages(KMessageWidget *w, const GpgME::Key &key, ExpiryChecker::CheckFlags flags);
void updateAllExpiryMessages();
public:
KeySelectionCombo *mSigSelect = nullptr;
KMessageWidget *mSignKeyExpiryMessage = nullptr;
KeySelectionCombo *mSelfSelect = nullptr;
KMessageWidget *mEncryptToSelfKeyExpiryMessage = nullptr;
std::vector<RecipientWidgets> mRecpWidgets;
QVector<UnknownRecipientWidget *> mUnknownWidgets;
QVector<GpgME::Key> mAddedKeys;
QVector<KeyGroup> mAddedGroups;
QVBoxLayout *mRecpLayout = nullptr;
Operations mOp;
AbstractKeyListModel *mModel = nullptr;
QCheckBox *mSymmetric = nullptr;
QCheckBox *mSigChk = nullptr;
QCheckBox *mEncOtherChk = nullptr;
QCheckBox *mEncSelfChk = nullptr;
GpgME::Protocol mCurrentProto = GpgME::UnknownProtocol;
const bool mIsExclusive;
std::unique_ptr<ExpiryChecker> mExpiryChecker;
};
SignEncryptWidget::SignEncryptWidget(QWidget *parent, bool sigEncExclusive)
: QWidget{parent}
, d{new Private{this, sigEncExclusive}}
{
auto lay = new QVBoxLayout(this);
lay->setContentsMargins(0, 0, 0, 0);
d->mModel->useKeyCache(true, KeyList::IncludeGroups);
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
/* The signature selection */
{
auto sigGrp = new QGroupBox{i18nc("@title:group", "Prove authenticity (sign)"), this};
d->mSigChk = new QCheckBox{i18n("Sign as:"), this};
d->mSigChk->setEnabled(haveSecretKeys);
d->mSigChk->setChecked(haveSecretKeys);
d->mSigSelect = new KeySelectionCombo{this};
d->mSigSelect->setEnabled(d->mSigChk->isChecked());
d->mSignKeyExpiryMessage = new KMessageWidget{this};
d->mSignKeyExpiryMessage->setVisible(false);
auto groupLayout = new QGridLayout{sigGrp};
groupLayout->setColumnStretch(1, 1);
groupLayout->addWidget(d->mSigChk, 0, 0);
groupLayout->addWidget(d->mSigSelect, 0, 1);
groupLayout->addWidget(d->mSignKeyExpiryMessage, 1, 1);
lay->addWidget(sigGrp);
connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool checked) {
d->mSigSelect->setEnabled(checked);
updateOp();
d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
});
connect(d->mSigSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
updateOp();
d->updateExpiryMessages(d->mSignKeyExpiryMessage, signKey(), ExpiryChecker::OwnSigningKey);
});
}
// Recipient selection
{
auto encBox = new QGroupBox{i18nc("@title:group", "Encrypt"), this};
auto encBoxLay = new QVBoxLayout{encBox};
auto recipientGrid = new QGridLayout;
int row = 0;
// Own key
d->mEncSelfChk = new QCheckBox{i18n("Encrypt for me:"), this};
d->mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
d->mSelfSelect = new KeySelectionCombo{this};
d->mSelfSelect->setEnabled(d->mEncSelfChk->isChecked());
d->mEncryptToSelfKeyExpiryMessage = new KMessageWidget{this};
d->mEncryptToSelfKeyExpiryMessage->setVisible(false);
recipientGrid->addWidget(d->mEncSelfChk, row, 0);
recipientGrid->addWidget(d->mSelfSelect, row, 1);
row++;
recipientGrid->addWidget(d->mEncryptToSelfKeyExpiryMessage, row, 1);
// Checkbox for other keys
row++;
d->mEncOtherChk = new QCheckBox{i18n("Encrypt for others:"), this};
d->mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
recipientGrid->addWidget(d->mEncOtherChk, row, 0, Qt::AlignTop);
connect(d->mEncOtherChk, &QCheckBox::toggled, this,
[this](bool checked) {
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
recipient.edit->setEnabled(checked);
d->updateExpiryMessages(recipient.expiryMessage, checked ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
}
updateOp();
});
d->mRecpLayout = new QVBoxLayout;
recipientGrid->addLayout(d->mRecpLayout, row, 1);
recipientGrid->setRowStretch(row + 1, 1);
// Scroll area for other keys
auto recipientWidget = new QWidget;
auto recipientScroll = new QScrollArea;
recipientWidget->setLayout(recipientGrid);
recipientScroll->setWidget(recipientWidget);
recipientScroll->setWidgetResizable(true);
recipientScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
recipientScroll->setFrameStyle(QFrame::NoFrame);
recipientScroll->setFocusPolicy(Qt::NoFocus);
recipientGrid->setContentsMargins(0, 0, 0, 0);
encBoxLay->addWidget(recipientScroll, 1);
auto bar = recipientScroll->verticalScrollBar();
connect (bar, &QScrollBar::rangeChanged, this, [bar] (int, int max) {
bar->setValue(max);
});
d->addRecipientWidget();
// Checkbox for password
d->mSymmetric = new QCheckBox(i18n("Encrypt with password. Anyone you share the password with can read the data."));
d->mSymmetric->setToolTip(i18nc("Tooltip information for symmetric encryption",
"Additionally to the keys of the recipients you can encrypt your data with a password. "
"Anyone who has the password can read the data without any secret key. "
"Using a password is <b>less secure</b> then public key cryptography. Even if you pick a very strong password."));
d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
encBoxLay->addWidget(d->mSymmetric);
// Connect it
connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool checked) {
d->mSelfSelect->setEnabled(checked);
updateOp();
d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
});
connect(d->mSelfSelect, &KeySelectionCombo::currentKeyChanged, this, [this]() {
updateOp();
d->updateExpiryMessages(d->mEncryptToSelfKeyExpiryMessage, selfKey(), ExpiryChecker::OwnEncryptionKey);
});
connect(d->mSymmetric, &QCheckBox::toggled, this, &SignEncryptWidget::updateOp);
if (d->mIsExclusive) {
connect(d->mEncOtherChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mSigChk->setChecked(false);
}
});
connect(d->mEncSelfChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mSigChk->setChecked(false);
}
});
connect(d->mSigChk, &QCheckBox::toggled, this, [this](bool value) {
if (d->mCurrentProto != GpgME::CMS) {
return;
}
if (value) {
d->mEncSelfChk->setChecked(false);
d->mEncOtherChk->setChecked(false);
}
});
}
// Ensure that the d->mSigChk is aligned together with the encryption check boxes.
d->mSigChk->setMinimumWidth(qMax(d->mEncOtherChk->width(), d->mEncSelfChk->width()));
lay->addWidget(encBox);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, this, [this]() {
d->updateCheckBoxes();
d->updateAllExpiryMessages();
});
connect(KleopatraApplication::instance(), &KleopatraApplication::configurationChanged, this, [this]() {
d->updateCheckBoxes();
d->mExpiryChecker.reset();
d->updateAllExpiryMessages();
});
loadKeys();
d->onProtocolChanged();
updateOp();
}
SignEncryptWidget::~SignEncryptWidget() = default;
void SignEncryptWidget::setSignAsText(const QString &text)
{
d->mSigChk->setText(text);
}
void SignEncryptWidget::setEncryptForMeText(const QString &text)
{
d->mEncSelfChk->setText(text);
}
void SignEncryptWidget::setEncryptForOthersText(const QString &text)
{
d->mEncOtherChk->setText(text);
}
void SignEncryptWidget::setEncryptWithPasswordText(const QString& text)
{
d->mSymmetric->setText(text);
}
CertificateLineEdit *SignEncryptWidget::Private::addRecipientWidget()
{
return insertRecipientWidget(nullptr);
}
CertificateLineEdit *SignEncryptWidget::Private::insertRecipientWidget(CertificateLineEdit *after)
{
Q_ASSERT(!after || mRecpLayout->indexOf(after) != -1);
const auto index = after ? mRecpLayout->indexOf(after) + 2 : mRecpLayout->count();
const RecipientWidgets recipient{
new CertificateLineEdit{mModel, new EncryptCertificateFilter{mCurrentProto}, q},
new KMessageWidget{q}
};
recipient.edit->setAccessibleNameOfLineEdit(i18nc("text for screen readers", "recipient key"));
recipient.edit->setEnabled(mEncOtherChk->isChecked());
recipient.expiryMessage->setVisible(false);
if (static_cast<unsigned>(index / 2) < mRecpWidgets.size()) {
mRecpWidgets.insert(mRecpWidgets.begin() + index / 2, recipient);
} else {
mRecpWidgets.push_back(recipient);
}
if (mRecpLayout->count() > 0) {
auto prevWidget = after ? after : mRecpLayout->itemAt(mRecpLayout->count() - 1)->widget();
Kleo::forceSetTabOrder(prevWidget, recipient.edit);
Kleo::forceSetTabOrder(recipient.edit, recipient.expiryMessage);
}
mRecpLayout->insertWidget(index, recipient.edit);
mRecpLayout->insertWidget(index + 1, recipient.expiryMessage);
connect(recipient.edit, &CertificateLineEdit::keyChanged,
q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::editingStarted,
q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::cleared,
q, &SignEncryptWidget::recipientsChanged);
connect(recipient.edit, &CertificateLineEdit::certificateSelectionRequested,
q, [this, recipient]() { q->certificateSelectionRequested(recipient.edit); });
return recipient.edit;
}
void SignEncryptWidget::addRecipient(const Key &key)
{
CertificateLineEdit *certSel = d->addRecipientWidget();
if (!key.isNull()) {
certSel->setKey(key);
d->mAddedKeys << key;
}
}
void SignEncryptWidget::addRecipient(const KeyGroup &group)
{
CertificateLineEdit *certSel = d->addRecipientWidget();
if (!group.isNull()) {
certSel->setGroup(group);
d->mAddedGroups << group;
}
}
void SignEncryptWidget::certificateSelectionRequested(CertificateLineEdit *certificateLineEdit)
{
CertificateSelectionDialog dlg{this};
- dlg.setOptions(CertificateSelectionDialog::Options(
- CertificateSelectionDialog::MultiSelection |
- CertificateSelectionDialog::EncryptOnly |
- CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) |
+ dlg.setOptions(CertificateSelectionDialog::Options( //
+ CertificateSelectionDialog::MultiSelection | //
+ CertificateSelectionDialog::EncryptOnly | //
+ CertificateSelectionDialog::optionsFromProtocol(d->mCurrentProto) | //
CertificateSelectionDialog::IncludeGroups));
if (!certificateLineEdit->key().isNull()) {
const auto key = certificateLineEdit->key();
const auto name = QString::fromUtf8(key.userID(0).name());
const auto email = QString::fromUtf8(key.userID(0).email());
dlg.setStringFilter(!name.isEmpty() ? name : email);
} else if (!certificateLineEdit->group().isNull()) {
dlg.setStringFilter(certificateLineEdit->group().name());
} else {
dlg.setStringFilter(certificateLineEdit->text());
}
if (dlg.exec()) {
const std::vector<Key> keys = dlg.selectedCertificates();
const std::vector<KeyGroup> groups = dlg.selectedGroups();
if (keys.size() == 0 && groups.size() == 0) {
return;
}
CertificateLineEdit *certWidget = nullptr;
for (const Key &key : keys) {
if (!certWidget) {
certWidget = certificateLineEdit;
} else {
certWidget = d->insertRecipientWidget(certWidget);
}
certWidget->setKey(key);
}
for (const KeyGroup &group : groups) {
if (!certWidget) {
certWidget = certificateLineEdit;
} else {
certWidget = d->insertRecipientWidget(certWidget);
}
certWidget->setGroup(group);
}
}
recipientsChanged();
}
void SignEncryptWidget::clearAddedRecipients()
{
for (auto w: std::as_const(d->mUnknownWidgets)) {
d->mRecpLayout->removeWidget(w);
delete w;
}
for (auto &key: std::as_const(d->mAddedKeys)) {
removeRecipient(key);
}
for (auto &group: std::as_const(d->mAddedGroups)) {
removeRecipient(group);
}
}
void SignEncryptWidget::addUnknownRecipient(const char *keyID)
{
auto unknownWidget = new UnknownRecipientWidget(keyID);
d->mUnknownWidgets << unknownWidget;
if (d->mRecpLayout->count() > 0) {
auto lastWidget = d->mRecpLayout->itemAt(d->mRecpLayout->count() - 1)->widget();
setTabOrder(lastWidget, unknownWidget);
}
d->mRecpLayout->addWidget(unknownWidget);
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
this, [this] () {
// Check if any unknown recipient can now be found.
for (auto w: d->mUnknownWidgets) {
auto key = KeyCache::instance()->findByKeyIDOrFingerprint(w->keyID().toLatin1().constData());
if (key.isNull()) {
std::vector<std::string> subids;
subids.push_back(std::string(w->keyID().toLatin1().constData()));
for (const auto &subkey: KeyCache::instance()->findSubkeysByKeyID(subids)) {
key = subkey.parent();
}
}
if (key.isNull()) {
continue;
}
// Key is now available replace by line edit.
qCDebug(KLEOPATRA_LOG) << "Removing widget for keyid: " << w->keyID();
d->mRecpLayout->removeWidget(w);
d->mUnknownWidgets.removeAll(w);
delete w;
addRecipient(key);
}
});
}
void SignEncryptWidget::recipientsChanged()
{
const bool hasEmptyRecpWidget =
std::any_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets),
[](auto w) { return w.edit->isEmpty(); });
if (!hasEmptyRecpWidget) {
d->addRecipientWidget();
}
updateOp();
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
if (!recipient.edit->isEditingInProgress() || recipient.edit->isEmpty()) {
d->updateExpiryMessages(recipient.expiryMessage, d->mEncOtherChk->isChecked() ? recipient.edit->key() : Key{}, ExpiryChecker::EncryptionKey);
}
}
}
Key SignEncryptWidget::signKey() const
{
if (d->mSigSelect->isEnabled()) {
return d->mSigSelect->currentKey();
}
return Key();
}
Key SignEncryptWidget::selfKey() const
{
if (d->mSelfSelect->isEnabled()) {
return d->mSelfSelect->currentKey();
}
return Key();
}
std::vector<Key> SignEncryptWidget::recipients() const
{
std::vector<Key> ret;
for (const auto &recipient : std::as_const(d->mRecpWidgets)) {
const auto *const w = recipient.edit;
if (!w->isEnabled()) {
// If one is disabled, all are disabled.
break;
}
const Key k = w->key();
const KeyGroup g = w->group();
if (!k.isNull()) {
ret.push_back(k);
} else if (!g.isNull()) {
const auto keys = g.keys();
std::copy(keys.begin(), keys.end(), std::back_inserter(ret));
}
}
const Key k = selfKey();
if (!k.isNull()) {
ret.push_back(k);
}
return ret;
}
bool SignEncryptWidget::isDeVsAndValid() const
{
if (!signKey().isNull() && !DeVSCompliance::keyIsCompliant(signKey())) {
return false;
}
if (!selfKey().isNull() && !DeVSCompliance::keyIsCompliant(selfKey())) {
return false;
}
for (const auto &key: recipients()) {
if (!DeVSCompliance::keyIsCompliant(key)) {
return false;
}
}
return true;
}
static QString expiryMessage(const ExpiryChecker::Result &result)
{
if (result.expiration.certificate.isNull()) {
return {};
}
switch (result.expiration.status) {
case ExpiryChecker::Expired:
return i18nc("@info", "This certificate is expired.");
case ExpiryChecker::ExpiresSoon: {
if (result.expiration.duration.count() == 0) {
return i18nc("@info", "This certificate expires today.");
} else {
return i18ncp("@info", "This certificate expires tomorrow.", "This certificate expires in %1 days.", result.expiration.duration.count());
}
}
case ExpiryChecker::NoSuitableSubkey:
if (result.checkFlags & ExpiryChecker::EncryptionKey) {
return i18nc("@info", "This certificate cannot be used for encryption.");
} else {
return i18nc("@info", "This certificate cannot be used for signing.");
}
case ExpiryChecker::InvalidKey:
case ExpiryChecker::InvalidCheckFlags:
break; // wrong usage of ExpiryChecker; can be ignored
case ExpiryChecker::NotNearExpiry:
;
}
return {};
}
void SignEncryptWidget::updateOp()
{
const Key sigKey = signKey();
const std::vector<Key> recp = recipients();
Operations op = NoOperation;
if (!sigKey.isNull()) {
op |= Sign;
}
if (!recp.empty() || encryptSymmetric()) {
op |= Encrypt;
}
d->mOp = op;
Q_EMIT operationChanged(d->mOp);
Q_EMIT keysChanged();
}
SignEncryptWidget::Operations SignEncryptWidget::currentOp() const
{
return d->mOp;
}
namespace
{
bool recipientWidgetHasFocus(QWidget *w)
{
// check if w (or its focus proxy) or a child widget of w has focus
return w->hasFocus() || w->isAncestorOf(qApp->focusWidget());
}
}
void SignEncryptWidget::Private::recpRemovalRequested(const RecipientWidgets &recipient)
{
if (!recipient.edit) {
return;
}
const int emptyEdits =
std::count_if(std::cbegin(mRecpWidgets), std::cend(mRecpWidgets),
[](const auto &r) { return r.edit->isEmpty(); });
if (emptyEdits > 1) {
if (recipientWidgetHasFocus(recipient.edit) || recipientWidgetHasFocus(recipient.expiryMessage)) {
const int index = mRecpLayout->indexOf(recipient.edit);
- const auto focusWidget = (index < mRecpLayout->count() - 2) ?
+ const auto focusWidget = (index < mRecpLayout->count() - 2) ? //
mRecpLayout->itemAt(index + 2)->widget() :
mRecpLayout->itemAt(mRecpLayout->count() - 3)->widget();
focusWidget->setFocus();
}
mRecpLayout->removeWidget(recipient.expiryMessage);
mRecpLayout->removeWidget(recipient.edit);
const auto it = std::find_if(std::begin(mRecpWidgets), std::end(mRecpWidgets), [recipient](const auto &r) {
return r.edit == recipient.edit;
});
mRecpWidgets.erase(it);
recipient.expiryMessage->deleteLater();
recipient.edit->deleteLater();
}
}
void SignEncryptWidget::removeRecipient(const GpgME::Key &key)
{
for (const auto &recipient: std::as_const(d->mRecpWidgets)) {
const auto editKey = recipient.edit->key();
if (key.isNull() && editKey.isNull()) {
d->recpRemovalRequested(recipient);
return;
}
if (editKey.primaryFingerprint() &&
key.primaryFingerprint() &&
!strcmp(editKey.primaryFingerprint(), key.primaryFingerprint())) {
d->recpRemovalRequested(recipient);
return;
}
}
}
void SignEncryptWidget::removeRecipient(const KeyGroup &group)
{
for (const auto &recipient: std::as_const(d->mRecpWidgets)) {
const auto editGroup = recipient.edit->group();
if (group.isNull() && editGroup.isNull()) {
d->recpRemovalRequested(recipient);
return;
}
if (editGroup.name() == group.name()) {
d->recpRemovalRequested(recipient);
return;
}
}
}
bool SignEncryptWidget::encryptSymmetric() const
{
return d->mSymmetric->isChecked();
}
void SignEncryptWidget::loadKeys()
{
KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
auto cache = KeyCache::instance();
d->mSigSelect->setDefaultKey(keys.readEntry("SigningKey", QString()));
d->mSelfSelect->setDefaultKey(keys.readEntry("EncryptKey", QString()));
}
void SignEncryptWidget::saveOwnKeys() const
{
KConfigGroup keys(KSharedConfig::openConfig(), "SignEncryptKeys");
auto sigKey = d->mSigSelect->currentKey();
auto encKey = d->mSelfSelect->currentKey();
if (!sigKey.isNull()) {
keys.writeEntry("SigningKey", sigKey.primaryFingerprint());
}
if (!encKey.isNull()) {
keys.writeEntry("EncryptKey", encKey.primaryFingerprint());
}
}
void SignEncryptWidget::setSigningChecked(bool value)
{
d->mSigChk->setChecked(value && !KeyCache::instance()->secretKeys().empty());
}
void SignEncryptWidget::setEncryptionChecked(bool checked)
{
if (checked) {
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
d->mEncSelfChk->setChecked(haveSecretKeys && !symmetricOnly);
d->mEncOtherChk->setChecked(havePublicKeys && !symmetricOnly);
d->mSymmetric->setChecked(symmetricOnly || !havePublicKeys);
} else {
d->mEncSelfChk->setChecked(false);
d->mEncOtherChk->setChecked(false);
d->mSymmetric->setChecked(false);
}
}
void SignEncryptWidget::setProtocol(GpgME::Protocol proto)
{
if (d->mCurrentProto == proto) {
return;
}
d->mCurrentProto = proto;
d->onProtocolChanged();
}
void Kleo::SignEncryptWidget::Private::onProtocolChanged()
{
mSigSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new SignCertificateFilter(mCurrentProto)));
mSelfSelect->setKeyFilter(std::shared_ptr<KeyFilter>(new EncryptSelfCertificateFilter(mCurrentProto)));
const auto encFilter = std::shared_ptr<KeyFilter>(new EncryptCertificateFilter(mCurrentProto));
for (const auto &recipient : std::as_const(mRecpWidgets)) {
recipient.edit->setKeyFilter(encFilter);
}
if (mIsExclusive) {
mSymmetric->setDisabled(mCurrentProto == GpgME::CMS);
if (mSymmetric->isChecked() && mCurrentProto == GpgME::CMS) {
mSymmetric->setChecked(false);
}
if (mSigChk->isChecked() && mCurrentProto == GpgME::CMS &&
(mEncSelfChk->isChecked() || mEncOtherChk->isChecked())) {
mSigChk->setChecked(false);
}
}
}
bool SignEncryptWidget::isComplete() const
{
- return currentOp() != NoOperation
+ return currentOp() != NoOperation //
&& std::all_of(std::cbegin(d->mRecpWidgets), std::cend(d->mRecpWidgets),
[](const auto &r) { return !r.edit->isEnabled() || r.edit->hasAcceptableInput(); });
}
bool SignEncryptWidget::validate()
{
CertificateLineEdit *firstUnresolvedRecipient = nullptr;
QStringList unresolvedRecipients;
for (const auto &recipient: std::as_const(d->mRecpWidgets)) {
if (recipient.edit->isEnabled() && !recipient.edit->hasAcceptableInput()) {
if (!firstUnresolvedRecipient) {
firstUnresolvedRecipient = recipient.edit;
}
unresolvedRecipients.push_back(recipient.edit->text().toHtmlEscaped());
}
}
if (!unresolvedRecipients.isEmpty()) {
KMessageBox::errorList(this,
i18n("Could not find a key for the following recipients:"),
unresolvedRecipients,
i18n("Failed to find some keys"));
}
if (firstUnresolvedRecipient) {
firstUnresolvedRecipient->setFocus();
}
return unresolvedRecipients.isEmpty();
}
void SignEncryptWidget::Private::updateCheckBoxes()
{
const bool haveSecretKeys = !KeyCache::instance()->secretKeys().empty();
const bool havePublicKeys = !KeyCache::instance()->keys().empty();
const bool symmetricOnly = FileOperationsPreferences().symmetricEncryptionOnly();
mSigChk->setEnabled(haveSecretKeys);
mEncSelfChk->setEnabled(haveSecretKeys && !symmetricOnly);
mEncOtherChk->setEnabled(havePublicKeys && !symmetricOnly);
if (symmetricOnly) {
mEncSelfChk->setChecked(false);
mEncOtherChk->setChecked(false);
mSymmetric->setChecked(true);
}
}
ExpiryChecker *Kleo::SignEncryptWidget::Private::expiryChecker()
{
if (!mExpiryChecker) {
mExpiryChecker.reset(new ExpiryChecker{ExpiryCheckerConfig{}.settings()});
}
return mExpiryChecker.get();
}
void SignEncryptWidget::Private::updateExpiryMessages(KMessageWidget *messageWidget, const GpgME::Key &key, ExpiryChecker::CheckFlags flags)
{
messageWidget->setCloseButtonVisible(false);
if (!Settings{}.showExpiryNotifications() || key.isNull()) {
messageWidget->setVisible(false);
} else {
const auto result = expiryChecker()->checkKey(key, flags);
const auto message = expiryMessage(result);
messageWidget->setText(message);
messageWidget->setVisible(!message.isEmpty());
}
}
void SignEncryptWidget::Private::updateAllExpiryMessages()
{
updateExpiryMessages(mSignKeyExpiryMessage, q->signKey(), ExpiryChecker::OwnSigningKey);
updateExpiryMessages(mEncryptToSelfKeyExpiryMessage, q->selfKey(), ExpiryChecker::OwnEncryptionKey);
for (const auto &recipient : std::as_const(mRecpWidgets)) {
if (recipient.edit->isEnabled()) {
updateExpiryMessages(recipient.expiryMessage, recipient.edit->key(), ExpiryChecker::EncryptionKey);
}
}
}
diff --git a/src/crypto/gui/signerresolvepage.h b/src/crypto/gui/signerresolvepage.h
index 881e2d3dc..098ae781b 100644
--- a/src/crypto/gui/signerresolvepage.h
+++ b/src/crypto/gui/signerresolvepage.h
@@ -1,124 +1,124 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/signerresolvepage.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 <crypto/gui/wizardpage.h>
#include <utils/pimpl_ptr.h>
#include <KMime/HeaderParsing>
#include <gpgme++/global.h>
#include <memory>
#include <set>
#include <vector>
namespace GpgME
{
class Key;
}
namespace Kleo
{
namespace Crypto
{
class SigningPreferences;
namespace Gui
{
class SignerResolvePage : public WizardPage
{
Q_OBJECT
public:
explicit SignerResolvePage(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~SignerResolvePage() override;
void setSignersAndCandidates(const std::vector<KMime::Types::Mailbox> &signers,
const std::vector< std::vector<GpgME::Key> > &keys);
std::vector<GpgME::Key> resolvedSigners() const;
std::vector<GpgME::Key> signingCertificates(GpgME::Protocol protocol = GpgME::UnknownProtocol) const;
bool isComplete() const override;
bool encryptionSelected() const;
void setEncryptionSelected(bool selected);
bool signingSelected() const;
void setSigningSelected(bool selected);
bool isEncryptionUserMutable() const;
void setEncryptionUserMutable(bool ismutable);
bool isSigningUserMutable() const;
void setSigningUserMutable(bool ismutable);
bool isAsciiArmorEnabled() const;
void setAsciiArmorEnabled(bool enabled);
void setPresetProtocol(GpgME::Protocol protocol);
void setPresetProtocols(const std::vector<GpgME::Protocol> &protocols);
std::set<GpgME::Protocol> selectedProtocols() const;
std::set<GpgME::Protocol> selectedProtocolsWithoutSigningCertificate() const;
void setMultipleProtocolsAllowed(bool allowed);
bool multipleProtocolsAllowed() const;
void setProtocolSelectionUserMutable(bool ismutable);
bool protocolSelectionUserMutable() const;
enum Operation {
SignAndEncrypt = 0,
SignOnly,
- EncryptOnly
+ EncryptOnly,
};
Operation operation() const;
class Validator
{
public:
virtual ~Validator() {}
virtual bool isComplete() const = 0;
virtual QString explanation() const = 0;
/**
* returns a custom window title, or a null string if no custom
* title is required.
* (use this if the title needs dynamic adaption
* depending on the user's selection)
*/
virtual QString customWindowTitle() const = 0;
};
void setValidator(const std::shared_ptr<Validator> &);
std::shared_ptr<Validator> validator() const;
void setSigningPreferences(const std::shared_ptr<SigningPreferences> &prefs);
std::shared_ptr<SigningPreferences> signingPreferences() const;
private:
void onNext() override;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_PRIVATE_SLOT(d, void operationButtonClicked(int))
Q_PRIVATE_SLOT(d, void selectCertificates())
Q_PRIVATE_SLOT(d, void updateUi())
};
}
}
}
diff --git a/src/crypto/gui/verifychecksumsdialog.h b/src/crypto/gui/verifychecksumsdialog.h
index d5275ff44..b55b3ad67 100644
--- a/src/crypto/gui/verifychecksumsdialog.h
+++ b/src/crypto/gui/verifychecksumsdialog.h
@@ -1,64 +1,64 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/verifychecksumsdialog.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 <QDialog>
#include <QMetaType>
#ifndef QT_NO_DIRMODEL
#include <utils/pimpl_ptr.h>
namespace Kleo
{
namespace Crypto
{
namespace Gui
{
class VerifyChecksumsDialog : public QDialog
{
Q_OBJECT
Q_ENUMS(Status)
public:
explicit VerifyChecksumsDialog(QWidget *parent = nullptr);
~VerifyChecksumsDialog() override;
enum Status {
Unknown,
OK,
Failed,
Error,
- NumStatii
+ NumStatii,
};
public Q_SLOTS:
void setBaseDirectories(const QStringList &bases);
void setProgress(int current, int total);
void setStatus(const QString &file, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status status);
void setErrors(const QStringList &errors);
void clearStatusInformation();
Q_SIGNALS:
void canceled();
private:
Q_PRIVATE_SLOT(d, void slotErrorButtonClicked())
class Private;
kdtools::pimpl_ptr<Private> d;
};
}
}
}
Q_DECLARE_METATYPE(Kleo::Crypto::Gui::VerifyChecksumsDialog::Status)
#endif // QT_NO_DIRMODEL
diff --git a/src/crypto/gui/wizard.h b/src/crypto/gui/wizard.h
index 414c0a14c..5b11410c1 100644
--- a/src/crypto/gui/wizard.h
+++ b/src/crypto/gui/wizard.h
@@ -1,78 +1,78 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/gui/wizard.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 <utils/pimpl_ptr.h>
#include <vector>
#include <QDialog>
namespace Kleo
{
namespace Crypto
{
namespace Gui
{
class WizardPage;
class Wizard : public QDialog
{
Q_OBJECT
public:
explicit Wizard(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~Wizard() override;
enum Page {
- InvalidPage = -1
+ InvalidPage = -1,
};
void setPage(int id, WizardPage *page);
const WizardPage *page(int id) const;
WizardPage *page(int id);
void setPageOrder(const std::vector<int> &pages);
void setPageVisible(int id, bool visible);
void setCurrentPage(int id);
int currentPage() const;
const WizardPage *currentPageWidget() const;
WizardPage *currentPageWidget();
bool canGoToPreviousPage() const;
bool canGoToNextPage() const;
public Q_SLOTS:
void next();
void back();
Q_SIGNALS:
void canceled();
protected:
virtual void onNext(int currentId);
virtual void onBack(int currentId);
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_PRIVATE_SLOT(d, void updateButtonStates())
Q_PRIVATE_SLOT(d, void updateHeader())
};
}
}
}
diff --git a/src/crypto/recipient.cpp b/src/crypto/recipient.cpp
index cae23d895..068e88f72 100644
--- a/src/crypto/recipient.cpp
+++ b/src/crypto/recipient.cpp
@@ -1,176 +1,173 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/recipient.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "recipient.h"
#include <Libkleo/Predicates>
#include <Libkleo/KeyCache>
#include <Libkleo/Stl_Util>
#include <utils/kleo_assert.h>
#include <utils/cached.h>
#include <KMime/HeaderParsing>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace KMime::Types;
using namespace GpgME;
namespace KMime
{
namespace Types
{
static bool operator==(const AddrSpec &lhs, const AddrSpec &rhs)
{
return lhs.localPart == rhs.localPart
&& lhs.domain == rhs.domain;
}
static bool operator==(const Mailbox &lhs, const Mailbox &rhs)
{
return lhs.name() == rhs.name()
&& lhs.addrSpec() == rhs.addrSpec();
}
static bool determine_ambiguous(const Mailbox &mb, const std::vector<Key> &keys)
{
Q_UNUSED(mb)
// ### really do check when we don't only show matching keys
return keys.size() != 1;
}
} // namespace Types
} // namespace KMime
class Recipient::Private
{
friend class ::Kleo::Crypto::Recipient;
public:
explicit Private(const Mailbox &mb)
: mailbox(mb)
{
// ### also fill up to a certain number of keys with those
// ### that don't match, for the case where there's a low
// ### total number of keys
const std::vector<Key> encrypt = KeyCache::instance()->findEncryptionKeysByMailbox(mb.addrSpec().asString());
kdtools::separate_if(encrypt.cbegin(), encrypt.cend(),
std::back_inserter(pgpEncryptionKeys), std::back_inserter(cmsEncryptionKeys),
[](const Key &key) { return key.protocol() == OpenPGP; });
}
private:
const Mailbox mailbox;
std::vector<Key> pgpEncryptionKeys, cmsEncryptionKeys;
Key cmsEncryptionKey;
UserID pgpEncryptionUid;
cached<bool> encryptionAmbiguous[2];
};
Recipient::Recipient(const Mailbox &mb)
: d(new Private(mb))
{
}
void Recipient::detach()
{
if (d && !d.unique()) {
d.reset(new Private(*d));
}
}
bool Recipient::deepEquals(const Recipient &other) const
{
static const _detail::ByFingerprint<std::equal_to> compare = {};
- return mailbox() == other.mailbox()
- && compare(d->cmsEncryptionKey, other.d->cmsEncryptionKey)
- && compare(d->pgpEncryptionUid.parent(), other.d->pgpEncryptionUid.parent())
- && strcmp(d->pgpEncryptionUid.id(), other.d->pgpEncryptionUid.id())
- && std::equal(d->pgpEncryptionKeys.cbegin(), d->pgpEncryptionKeys.cend(),
- other.d->pgpEncryptionKeys.cbegin(), compare)
- && std::equal(d->cmsEncryptionKeys.cbegin(), d->pgpEncryptionKeys.cend(),
- other.d->cmsEncryptionKeys.cbegin(), compare)
- ;
+ return mailbox() == other.mailbox() //
+ && compare(d->cmsEncryptionKey, other.d->cmsEncryptionKey) //
+ && compare(d->pgpEncryptionUid.parent(), other.d->pgpEncryptionUid.parent()) //
+ && strcmp(d->pgpEncryptionUid.id(), other.d->pgpEncryptionUid.id()) //
+ && std::equal(d->pgpEncryptionKeys.cbegin(), d->pgpEncryptionKeys.cend(), other.d->pgpEncryptionKeys.cbegin(), compare)
+ && std::equal(d->cmsEncryptionKeys.cbegin(), d->pgpEncryptionKeys.cend(), other.d->cmsEncryptionKeys.cbegin(), compare);
}
bool Recipient::isEncryptionAmbiguous(GpgME::Protocol proto) const
{
if (d->encryptionAmbiguous[proto].dirty()) {
d->encryptionAmbiguous[proto] = determine_ambiguous(d->mailbox, encryptionCertificateCandidates(proto));
}
return d->encryptionAmbiguous[proto];
}
const Mailbox &Recipient::mailbox() const
{
return d->mailbox;
}
const std::vector<Key> &Recipient::encryptionCertificateCandidates(GpgME::Protocol proto) const
{
if (proto == OpenPGP) {
return d->pgpEncryptionKeys;
}
if (proto == CMS) {
return d->cmsEncryptionKeys;
}
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#if 0
return
proto == OpenPGP ? d->pgpEncryptionKeys :
proto == CMS ? d->cmsEncryptionKeys :
// even though gcc warns about this line, it's completely ok, promise:
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#endif
}
void Recipient::setResolvedEncryptionKey(const Key &key)
{
if (key.isNull()) {
return;
}
const Protocol proto = key.protocol();
kleo_assert(proto == OpenPGP || proto == CMS);
detach();
if (proto == OpenPGP) {
d->pgpEncryptionUid = key.userID(0);
} else {
d->cmsEncryptionKey = key;
}
d->encryptionAmbiguous[proto] = false;
}
Key Recipient::resolvedEncryptionKey(GpgME::Protocol proto) const
{
kleo_assert(proto == OpenPGP || proto == CMS);
if (proto == OpenPGP) {
return d->pgpEncryptionUid.parent();
} else {
return d->cmsEncryptionKey;
}
}
void Recipient::setResolvedOpenPGPEncryptionUserID(const UserID &uid)
{
if (uid.isNull()) {
return;
}
detach();
d->pgpEncryptionUid = uid;
}
UserID Recipient::resolvedOpenPGPEncryptionUserID() const
{
return d->pgpEncryptionUid;
}
diff --git a/src/crypto/sender.cpp b/src/crypto/sender.cpp
index 9788ef0eb..28e44ee35 100644
--- a/src/crypto/sender.cpp
+++ b/src/crypto/sender.cpp
@@ -1,232 +1,226 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/sender.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "sender.h"
#include <Libkleo/Predicates>
#include <Libkleo/KeyCache>
#include <Libkleo/Stl_Util>
#include <utils/kleo_assert.h>
#include <utils/cached.h>
#include <KMime/HeaderParsing>
#include <gpgme++/key.h>
#include <algorithm>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace KMime::Types;
using namespace GpgME;
namespace KMime
{
namespace Types
{
static bool operator==(const AddrSpec &lhs, const AddrSpec &rhs)
{
return lhs.localPart == rhs.localPart
&& lhs.domain == rhs.domain;
}
static bool operator==(const Mailbox &lhs, const Mailbox &rhs)
{
return lhs.name() == rhs.name()
&& lhs.addrSpec() == rhs.addrSpec();
}
static bool determine_ambiguous(const Mailbox &mb, const std::vector<Key> &keys)
{
Q_UNUSED(mb)
// ### really do check when we don't only show matching keys
return keys.size() != 1;
}
} // namespace Types
} // namespace KMime
class Sender::Private
{
friend class ::Kleo::Crypto::Sender;
public:
explicit Private(const Mailbox &mb)
: mailbox(mb)
{
// ### also fill up to a certain number of keys with those
// ### that don't match, for the case where there's a low
// ### total number of keys
const QString email = mb.addrSpec().asString();
const std::vector<Key> signers = KeyCache::instance()->findSigningKeysByMailbox(email);
const std::vector<Key> encrypt = KeyCache::instance()->findEncryptionKeysByMailbox(email);
kdtools::separate_if(signers.cbegin(), signers.cend(),
std::back_inserter(pgpSigners), std::back_inserter(cmsSigners),
[](const Key &key) { return key.protocol() == OpenPGP; });
kdtools::separate_if(encrypt.cbegin(), encrypt.cend(),
std::back_inserter(pgpEncryptToSelfKeys), std::back_inserter(cmsEncryptToSelfKeys),
[](const Key &key) { return key.protocol() == OpenPGP; });
}
private:
const Mailbox mailbox;
std::vector<Key> pgpSigners, cmsSigners, pgpEncryptToSelfKeys, cmsEncryptToSelfKeys;
cached<bool> signingAmbiguous[2], encryptionAmbiguous[2];
Key signingKey[2], cmsEncryptionKey;
UserID pgpEncryptionUid;
};
Sender::Sender(const Mailbox &mb)
: d(new Private(mb))
{
}
void Sender::detach()
{
if (d && !d.unique()) {
d.reset(new Private(*d));
}
}
bool Sender::deepEquals(const Sender &other) const
{
static const _detail::ByFingerprint<std::equal_to> compare = {};
- return mailbox() == other.mailbox()
- && compare(d->signingKey[CMS], other.d->signingKey[CMS])
- && compare(d->signingKey[OpenPGP], other.d->signingKey[OpenPGP])
- && compare(d->cmsEncryptionKey, other.d->cmsEncryptionKey)
- && compare(d->pgpEncryptionUid.parent(), other.d->pgpEncryptionUid.parent())
- && strcmp(d->pgpEncryptionUid.id(), other.d->pgpEncryptionUid.id()) == 0
- && std::equal(d->pgpSigners.cbegin(), d->pgpSigners.cend(),
- other.d->pgpSigners.cbegin(), compare)
- && std::equal(d->cmsSigners.cbegin(), d->cmsSigners.cend(),
- other.d->cmsSigners.cbegin(), compare)
- && std::equal(d->pgpEncryptToSelfKeys.cbegin(), d->pgpEncryptToSelfKeys.cend(),
- other.d->pgpEncryptToSelfKeys.cbegin(), compare)
- && std::equal(d->cmsEncryptToSelfKeys.cbegin(), d->cmsEncryptToSelfKeys.cend(),
- other.d->cmsEncryptToSelfKeys.cbegin(), compare)
- ;
+ return mailbox() == other.mailbox() //
+ && compare(d->signingKey[CMS], other.d->signingKey[CMS]) //
+ && compare(d->signingKey[OpenPGP], other.d->signingKey[OpenPGP]) //
+ && compare(d->cmsEncryptionKey, other.d->cmsEncryptionKey) //
+ && compare(d->pgpEncryptionUid.parent(), other.d->pgpEncryptionUid.parent()) && strcmp(d->pgpEncryptionUid.id(), other.d->pgpEncryptionUid.id()) == 0
+ && std::equal(d->pgpSigners.cbegin(), d->pgpSigners.cend(), other.d->pgpSigners.cbegin(), compare)
+ && std::equal(d->cmsSigners.cbegin(), d->cmsSigners.cend(), other.d->cmsSigners.cbegin(), compare)
+ && std::equal(d->pgpEncryptToSelfKeys.cbegin(), d->pgpEncryptToSelfKeys.cend(), other.d->pgpEncryptToSelfKeys.cbegin(), compare)
+ && std::equal(d->cmsEncryptToSelfKeys.cbegin(), d->cmsEncryptToSelfKeys.cend(), other.d->cmsEncryptToSelfKeys.cbegin(), compare);
}
bool Sender::isSigningAmbiguous(GpgME::Protocol proto) const
{
if (d->signingAmbiguous[proto].dirty()) {
d->signingAmbiguous[proto] = determine_ambiguous(d->mailbox, signingCertificateCandidates(proto));
}
return d->signingAmbiguous[proto];
}
bool Sender::isEncryptionAmbiguous(GpgME::Protocol proto) const
{
if (d->encryptionAmbiguous[proto].dirty()) {
d->encryptionAmbiguous[proto] = determine_ambiguous(d->mailbox, encryptToSelfCertificateCandidates(proto));
}
return d->encryptionAmbiguous[proto];
}
const Mailbox &Sender::mailbox() const
{
return d->mailbox;
}
const std::vector<Key> &Sender::signingCertificateCandidates(GpgME::Protocol proto) const
{
if (proto == OpenPGP) {
return d->pgpSigners;
}
if (proto == CMS) {
return d->cmsSigners;
}
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#if 0
return
proto == OpenPGP ? d->pgpSigners :
proto == CMS ? d->cmsSigners :
// even though gcc warns about this line, it's completely ok, promise:
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#endif
}
const std::vector<Key> &Sender::encryptToSelfCertificateCandidates(GpgME::Protocol proto) const
{
if (proto == OpenPGP) {
return d->pgpEncryptToSelfKeys;
}
if (proto == CMS) {
return d->cmsEncryptToSelfKeys;
}
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#if 0
return
proto == OpenPGP ? d->pgpEncryptToSelfKeys :
proto == CMS ? d->cmsEncryptToSelfKeys :
// even though gcc warns about this line, it's completely ok, promise:
kleo_assert_fail(proto == OpenPGP || proto == CMS);
#endif
}
void Sender::setResolvedSigningKey(const Key &key)
{
if (key.isNull()) {
return;
}
const Protocol proto = key.protocol();
kleo_assert(proto == OpenPGP || proto == CMS);
detach();
d->signingKey[proto] = key;
d->signingAmbiguous[proto] = false;
}
Key Sender::resolvedSigningKey(GpgME::Protocol proto) const
{
kleo_assert(proto == OpenPGP || proto == CMS);
return d->signingKey[proto];
}
void Sender::setResolvedEncryptionKey(const Key &key)
{
if (key.isNull()) {
return;
}
const Protocol proto = key.protocol();
kleo_assert(proto == OpenPGP || proto == CMS);
detach();
if (proto == OpenPGP) {
d->pgpEncryptionUid = key.userID(0);
} else {
d->cmsEncryptionKey = key;
}
d->encryptionAmbiguous[proto] = false;
}
Key Sender::resolvedEncryptionKey(GpgME::Protocol proto) const
{
kleo_assert(proto == OpenPGP || proto == CMS);
if (proto == OpenPGP) {
return d->pgpEncryptionUid.parent();
} else {
return d->cmsEncryptionKey;
}
}
void Sender::setResolvedOpenPGPEncryptionUserID(const UserID &uid)
{
if (uid.isNull()) {
return;
}
detach();
d->pgpEncryptionUid = uid;
}
UserID Sender::resolvedOpenPGPEncryptionUserID() const
{
return d->pgpEncryptionUid;
}
diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp
index 8c8f2f58e..2174d2c47 100644
--- a/src/crypto/signencryptfilescontroller.cpp
+++ b/src/crypto/signencryptfilescontroller.cpp
@@ -1,775 +1,773 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signencryptfilescontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signencryptfilescontroller.h"
#include "signencrypttask.h"
#include "certificateresolver.h"
#include "crypto/gui/signencryptfileswizard.h"
#include "crypto/taskcollection.h"
#include "fileoperationspreferences.h"
#include "utils/input.h"
#include "utils/output.h"
#include "utils/kleo_assert.h"
#include "utils/archivedefinition.h"
#include "utils/path-helper.h"
#include <Libkleo/KleoException>
#include <Libkleo/Classify>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/SignEncryptArchiveJob>
#endif
#include <QPointer>
#include <QTimer>
#include <QFileInfo>
#include <QDir>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
using namespace KMime::Types;
class SignEncryptFilesController::Private
{
friend class ::Kleo::Crypto::SignEncryptFilesController;
SignEncryptFilesController *const q;
public:
explicit Private(SignEncryptFilesController *qq);
~Private();
private:
void slotWizardOperationPrepared();
void slotWizardCanceled();
private:
void ensureWizardCreated();
void ensureWizardVisible();
void updateWizardMode();
void cancelAllTasks();
void reportError(int err, const QString &details)
{
q->setLastError(err, details);
q->emitDoneOrError();
}
void schedule();
std::shared_ptr<SignEncryptTask> takeRunnable(GpgME::Protocol proto);
static void assertValidOperation(unsigned int);
static QString titleForOperation(unsigned int op);
private:
std::vector< std::shared_ptr<SignEncryptTask> > runnable, completed;
std::shared_ptr<SignEncryptTask> cms, openpgp;
QPointer<SignEncryptFilesWizard> wizard;
QStringList files;
unsigned int operation;
Protocol protocol;
};
SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq)
: q(qq),
runnable(),
cms(),
openpgp(),
wizard(),
files(),
operation(SignAllowed | EncryptAllowed | ArchiveAllowed),
protocol(UnknownProtocol)
{
}
SignEncryptFilesController::Private::~Private()
{
qCDebug(KLEOPATRA_LOG);
}
QString SignEncryptFilesController::Private::titleForOperation(unsigned int op)
{
const bool signDisallowed = (op & SignMask) == SignDisallowed;
const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed;
const bool archiveSelected = (op & ArchiveMask) == ArchiveForced;
kleo_assert(!signDisallowed || !encryptDisallowed);
if (!signDisallowed && encryptDisallowed) {
if (archiveSelected) {
return i18n("Archive and Sign Files");
} else {
return i18n("Sign Files");
}
}
if (signDisallowed && !encryptDisallowed) {
if (archiveSelected) {
return i18n("Archive and Encrypt Files");
} else {
return i18n("Encrypt Files");
}
}
if (archiveSelected) {
return i18n("Archive and Sign/Encrypt Files");
} else {
return i18n("Sign/Encrypt Files");
}
}
SignEncryptFilesController::SignEncryptFilesController(QObject *p)
: Controller(p), d(new Private(this))
{
}
SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p)
: Controller(ctx, p), d(new Private(this))
{
}
SignEncryptFilesController::~SignEncryptFilesController()
{
qCDebug(KLEOPATRA_LOG);
if (d->wizard && !d->wizard->isVisible()) {
delete d->wizard;
}
//d->wizard->close(); ### ?
}
void SignEncryptFilesController::setProtocol(Protocol proto)
{
kleo_assert(d->protocol == UnknownProtocol ||
d->protocol == proto);
d->protocol = proto;
d->ensureWizardCreated();
}
Protocol SignEncryptFilesController::protocol() const
{
return d->protocol;
}
// static
void SignEncryptFilesController::Private::assertValidOperation(unsigned int op)
{
- kleo_assert((op & SignMask) == SignDisallowed ||
- (op & SignMask) == SignAllowed ||
+ kleo_assert((op & SignMask) == SignDisallowed || //
+ (op & SignMask) == SignAllowed || //
(op & SignMask) == SignSelected);
- kleo_assert((op & EncryptMask) == EncryptDisallowed ||
- (op & EncryptMask) == EncryptAllowed ||
+ kleo_assert((op & EncryptMask) == EncryptDisallowed || //
+ (op & EncryptMask) == EncryptAllowed || //
(op & EncryptMask) == EncryptSelected);
- kleo_assert((op & ArchiveMask) == ArchiveDisallowed ||
- (op & ArchiveMask) == ArchiveAllowed ||
+ kleo_assert((op & ArchiveMask) == ArchiveDisallowed || //
+ (op & ArchiveMask) == ArchiveAllowed || //
(op & ArchiveMask) == ArchiveForced);
kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0);
}
void SignEncryptFilesController::setOperationMode(unsigned int mode)
{
Private::assertValidOperation(mode);
d->operation = mode;
d->updateWizardMode();
}
void SignEncryptFilesController::Private::updateWizardMode()
{
if (!wizard) {
return;
}
wizard->setWindowTitle(titleForOperation(operation));
const unsigned int signOp = (operation & SignMask);
const unsigned int encrOp = (operation & EncryptMask);
const unsigned int archOp = (operation & ArchiveMask);
if (signOp == SignDisallowed) {
wizard->setSigningUserMutable(false);
wizard->setSigningPreset(false);
} else {
wizard->setSigningUserMutable(true);
wizard->setSigningPreset(signOp == SignSelected);
}
if (encrOp == EncryptDisallowed) {
wizard->setEncryptionPreset(false);
wizard->setEncryptionUserMutable(false);
} else {
wizard->setEncryptionUserMutable(true);
wizard->setEncryptionPreset(encrOp == EncryptSelected);
}
wizard->setArchiveForced(archOp == ArchiveForced);
wizard->setArchiveMutable(archOp == ArchiveAllowed);
}
unsigned int SignEncryptFilesController::operationMode() const
{
return d->operation;
}
static const char *extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached)
{
unsigned int cls = pgp ? Class::OpenPGP : Class::CMS;
if (encrypt) {
cls |= Class::CipherText;
} else if (sign) {
cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature;
}
cls |= ascii ? Class::Ascii : Class::Binary;
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
if (const char *const ext = outputFileExtension(cls, usePGPFileExt)) {
return ext;
} else {
return "out";
}
}
static std::shared_ptr<ArchiveDefinition> getDefaultAd()
{
const std::vector<std::shared_ptr<ArchiveDefinition>> ads = ArchiveDefinition::getArchiveDefinitions();
Q_ASSERT(!ads.empty());
std::shared_ptr<ArchiveDefinition> ad = ads.front();
const FileOperationsPreferences prefs;
const QString archiveCmd = prefs.archiveCommand();
auto it = std::find_if(ads.cbegin(), ads.cend(), [&archiveCmd](const std::shared_ptr<ArchiveDefinition> &toCheck) {
return toCheck->id() == archiveCmd;
});
if (it != ads.cend()) {
ad = *it;
}
return ad;
}
static QMap <int, QString> buildOutputNames(const QStringList &files, const bool archive)
{
QMap <int, QString> nameMap;
// Build the default names for the wizard.
QString baseNameCms;
QString baseNamePgp;
const QFileInfo firstFile(files.first());
if (archive) {
- QString baseName;
- baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(files.size() > 1 ?
- i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") :
- firstFile.baseName());
+ QString baseName = files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName();
+ baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(baseName);
const auto ad = getDefaultAd();
baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.');
baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.');
} else {
baseNameCms = baseNamePgp = files.first() + QLatin1Char('.');
}
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + QString::fromLatin1(extension(false, true, false, ascii, true)));
nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + QString::fromLatin1(extension(false, false, true, ascii, false)));
nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + QString::fromLatin1(extension(true, true, true, ascii, false)));
nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + QString::fromLatin1(extension(true, false, true, ascii, false)));
nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + QString::fromLatin1(extension(true, true, false, ascii, true)));
nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files));
return nameMap;
}
static QMap <int, QString> buildOutputNamesForDir(const QString &file, const QMap <int, QString> &orig)
{
QMap <int, QString> ret;
const QString dir = orig.value(SignEncryptFilesWizard::Directory);
if (dir.isEmpty()) {
return orig;
}
// Build the default names for the wizard.
const QFileInfo fi(file);
const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.');
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + QString::fromLatin1(extension(false, true, false, ascii, true)));
ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + QString::fromLatin1(extension(false, false, true, ascii, false)));
ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + QString::fromLatin1(extension(true, true, true, ascii, false)));
ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + QString::fromLatin1(extension(true, false, true, ascii, false)));
ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + QString::fromLatin1(extension(true, true, false, ascii, true)));
return ret;
}
// strips all trailing slashes from the filename, but keeps filename "/"
static QString stripTrailingSlashes(const QString &fileName)
{
if (fileName.size() < 2 || !fileName.endsWith(QLatin1Char('/'))) {
return fileName;
}
auto tmp = QStringView{fileName}.chopped(1);
while (tmp.size() > 1 && tmp.endsWith(QLatin1Char('/'))) {
tmp.chop(1);
}
return tmp.toString();
}
static QStringList stripTrailingSlashesForAll(const QStringList &fileNames)
{
QStringList result;
result.reserve(fileNames.size());
std::transform(fileNames.begin(), fileNames.end(), std::back_inserter(result), &stripTrailingSlashes);
return result;
}
void SignEncryptFilesController::setFiles(const QStringList &files)
{
kleo_assert(!files.empty());
d->files = stripTrailingSlashesForAll(files);
bool archive = false;
if (d->files.size() > 1) {
setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed);
archive = true;
}
for (const auto &file: d->files) {
if (QFileInfo(file).isDir()) {
setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced);
archive = true;
break;
}
}
d->ensureWizardCreated();
d->wizard->setSingleFile(!archive);
d->wizard->setOutputNames(buildOutputNames(d->files, archive));
}
void SignEncryptFilesController::Private::slotWizardCanceled()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
q->cancel();
reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel"));
}
void SignEncryptFilesController::start()
{
d->ensureWizardVisible();
}
static std::shared_ptr<SignEncryptTask>
createSignEncryptTaskForFileInfo(const QFileInfo &fi, bool ascii,
const std::vector<Key> &recipients, const std::vector<Key> &signers,
const QString &outputName, bool symmetric)
{
const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask);
Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric);
task->setAsciiArmor(ascii);
if (!signers.empty()) {
task->setSign(true);
task->setSigners(signers);
task->setDetachedSignature(true);
} else {
task->setSign(false);
}
if (!recipients.empty()) {
task->setEncrypt(true);
task->setRecipients(recipients);
task->setDetachedSignature(false);
} else {
task->setEncrypt(false);
}
task->setEncryptSymmetric(symmetric);
const QString input = fi.absoluteFilePath();
task->setInputFileName(input);
task->setInput(Input::createFromFile(input));
task->setOutputFileName(outputName);
return task;
}
static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol)
{
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported();
#else
return false;
#endif
}
static std::shared_ptr<SignEncryptTask>
createArchiveSignEncryptTaskForFiles(const QStringList &files,
const std::shared_ptr<ArchiveDefinition> &ad, bool pgp, bool ascii,
const std::vector<Key> &recipients, const std::vector<Key> &signers,
const QString& outputName, bool symmetric)
{
const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask);
task->setCreateArchive(true);
task->setEncryptSymmetric(symmetric);
Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric);
task->setAsciiArmor(ascii);
if (!signers.empty()) {
task->setSign(true);
task->setSigners(signers);
task->setDetachedSignature(false);
} else {
task->setSign(false);
}
if (!recipients.empty()) {
task->setEncrypt(true);
task->setRecipients(recipients);
} else {
task->setEncrypt(false);
}
const Protocol proto = pgp ? OpenPGP : CMS;
task->setInputFileNames(files);
if (!archiveJobsCanBeUsed(proto)) {
// use legacy archive creation
kleo_assert(ad);
task->setInput(ad->createInputFromPackCommand(proto, files));
}
task->setOutputFileName(outputName);
return task;
}
static std::vector< std::shared_ptr<SignEncryptTask> >
createSignEncryptTasksForFileInfo(const QFileInfo &fi, bool ascii, const std::vector<Key> &pgpRecipients, const std::vector<Key> &pgpSigners,
const std::vector<Key> &cmsRecipients, const std::vector<Key> &cmsSigners, const QMap<int, QString> &outputNames,
bool symmetric)
{
std::vector< std::shared_ptr<SignEncryptTask> > result;
const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty();
const bool cms = !cmsSigners.empty() || !cmsRecipients.empty();
result.reserve(pgp + cms);
if (pgp || symmetric) {
// Symmetric encryption is only supported for PGP
int outKind = 0;
if ((!pgpRecipients.empty() || symmetric)&& !pgpSigners.empty()) {
outKind = SignEncryptFilesWizard::CombinedPGP;
} else if (!pgpRecipients.empty() || symmetric) {
outKind = SignEncryptFilesWizard::EncryptedPGP;
} else {
outKind = SignEncryptFilesWizard::SignaturePGP;
}
result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric));
}
if (cms) {
// There is no combined sign / encrypt in gpgsm so we create one sign task
// and one encrypt task. Which leaves us with the age old dilemma, encrypt
// then sign, or sign then encrypt. Ugly.
if (!cmsSigners.empty()) {
result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, std::vector<Key>(),
cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS],
false));
}
if (!cmsRecipients.empty()) {
result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients,
std::vector<Key>(), outputNames[SignEncryptFilesWizard::EncryptedCMS],
false));
}
}
return result;
}
static std::vector< std::shared_ptr<SignEncryptTask> >
createArchiveSignEncryptTasksForFiles(const QStringList &files, const std::shared_ptr<ArchiveDefinition> &ad,
bool ascii, const std::vector<Key> &pgpRecipients,
const std::vector<Key> &pgpSigners, const std::vector<Key> &cmsRecipients, const std::vector<Key> &cmsSigners,
const QMap<int, QString> &outputNames, bool symmetric)
{
std::vector< std::shared_ptr<SignEncryptTask> > result;
const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty();
const bool cms = !cmsSigners.empty() || !cmsRecipients.empty();
result.reserve(pgp + cms);
if (pgp || symmetric) {
int outKind = 0;
if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) {
outKind = SignEncryptFilesWizard::CombinedPGP;
} else if (!pgpRecipients.empty() || symmetric) {
outKind = SignEncryptFilesWizard::EncryptedPGP;
} else {
outKind = SignEncryptFilesWizard::SignaturePGP;
}
result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric));
}
if (cms) {
if (!cmsSigners.empty()) {
result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii,
std::vector<Key>(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS],
false));
}
if (!cmsRecipients.empty()) {
result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii,
cmsRecipients, std::vector<Key>(), outputNames[SignEncryptFilesWizard::EncryptedCMS],
false));
}
}
return result;
}
namespace
{
static auto resolveFileNameConflicts(const std::vector<std::shared_ptr<SignEncryptTask>> &tasks, QWidget *parent)
{
std::vector<std::shared_ptr<SignEncryptTask>> resolvedTasks;
OverwritePolicy overwritePolicy{parent, tasks.size() > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}};
for (auto &task : tasks) {
// by default, do not overwrite existing files
task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Skip));
const auto outputFileName = task->outputFileName();
if (QFile::exists(outputFileName)) {
const auto newFileName = overwritePolicy.obtainOverwritePermission(outputFileName);
if (newFileName.isEmpty()) {
if (overwritePolicy.policy() == OverwritePolicy::Cancel) {
resolvedTasks.clear();
break;
}
// else Skip -> do not add task to the final task list
continue;
} else if (newFileName != outputFileName) {
task->setOutputFileName(newFileName);
} else {
task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Overwrite));
}
}
resolvedTasks.push_back(task);
}
return resolvedTasks;
}
}
void SignEncryptFilesController::Private::slotWizardOperationPrepared()
{
try {
kleo_assert(wizard);
kleo_assert(!files.empty());
- const bool archive = (wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) ||
- ((operation & ArchiveMask) == ArchiveForced);
+ const bool archive = ((wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) //
+ || ((operation & ArchiveMask) == ArchiveForced));
const std::vector<Key> recipients = wizard->resolvedRecipients();
const std::vector<Key> signers = wizard->resolvedSigners();
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
std::vector<Key> pgpRecipients, cmsRecipients, pgpSigners, cmsSigners;
for (const Key &k : recipients) {
if (k.protocol() == GpgME::OpenPGP) {
pgpRecipients.push_back(k);
} else {
cmsRecipients.push_back(k);
}
}
for (const Key &k : signers) {
if (k.protocol() == GpgME::OpenPGP) {
pgpSigners.push_back(k);
} else {
cmsSigners.push_back(k);
}
}
std::vector< std::shared_ptr<SignEncryptTask> > tasks;
if (!archive) {
tasks.reserve(files.size());
}
if (archive) {
tasks = createArchiveSignEncryptTasksForFiles(files,
getDefaultAd(),
ascii,
pgpRecipients,
pgpSigners,
cmsRecipients,
cmsSigners,
wizard->outputNames(),
wizard->encryptSymmetric());
} else {
for (const QString &file : std::as_const(files)) {
const std::vector< std::shared_ptr<SignEncryptTask> > created =
createSignEncryptTasksForFileInfo(QFileInfo(file), ascii,
pgpRecipients,
pgpSigners,
cmsRecipients,
cmsSigners,
buildOutputNamesForDir(file, wizard->outputNames()),
wizard->encryptSymmetric());
tasks.insert(tasks.end(), created.begin(), created.end());
}
}
tasks = resolveFileNameConflicts(tasks, wizard);
if (tasks.empty()) {
q->cancel();
return;
}
kleo_assert(runnable.empty());
runnable.swap(tasks);
for (const auto &task : std::as_const(runnable)) {
q->connectTask(task);
}
std::shared_ptr<TaskCollection> coll(new TaskCollection);
std::vector<std::shared_ptr<Task> > tmp;
std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp));
coll->setTasks(tmp);
wizard->setTaskCollection(coll);
QTimer::singleShot(0, q, SLOT(schedule()));
} catch (const Kleo::Exception &e) {
reportError(e.error().encodedError(), e.message());
} catch (const std::exception &e) {
reportError(gpg_error(GPG_ERR_UNEXPECTED),
i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1",
QString::fromLocal8Bit(e.what())));
} catch (...) {
reportError(gpg_error(GPG_ERR_UNEXPECTED),
i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared"));
}
}
void SignEncryptFilesController::Private::schedule()
{
if (!cms)
if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(CMS)) {
t->start();
cms = t;
}
if (!openpgp)
if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(OpenPGP)) {
t->start();
openpgp = t;
}
if (!cms && !openpgp) {
kleo_assert(runnable.empty());
q->emitDoneOrError();
}
}
std::shared_ptr<SignEncryptTask> SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto)
{
const auto it = std::find_if(runnable.begin(), runnable.end(),
[proto](const std::shared_ptr<Task> &task) { return task->protocol() == proto; });
if (it == runnable.end()) {
return std::shared_ptr<SignEncryptTask>();
}
const std::shared_ptr<SignEncryptTask> result = *it;
runnable.erase(it);
return result;
}
void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &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()));
}
void SignEncryptFilesController::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
try {
if (d->wizard) {
d->wizard->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
void SignEncryptFilesController::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 SignEncryptFilesController::Private::ensureWizardCreated()
{
if (wizard) {
return;
}
std::unique_ptr<SignEncryptFilesWizard> w(new SignEncryptFilesWizard);
w->setAttribute(Qt::WA_DeleteOnClose);
connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection);
connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection);
wizard = w.release();
updateWizardMode();
}
void SignEncryptFilesController::Private::ensureWizardVisible()
{
ensureWizardCreated();
q->bringToForeground(wizard);
}
#include "moc_signencryptfilescontroller.cpp"
diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp
index b00ae6f7d..8a40ae903 100644
--- a/src/crypto/signencrypttask.cpp
+++ b/src/crypto/signencrypttask.cpp
@@ -1,917 +1,916 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signencrypttask.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 <config-kleopatra.h>
#include "signencrypttask.h"
#include <utils/input.h>
#include <utils/output.h>
#include <utils/path-helper.h>
#include <utils/kleo_assert.h>
#include <Libkleo/AuditLogEntry>
#include <Libkleo/Formatting>
#include <Libkleo/Stl_Util>
#include <Libkleo/KleoException>
#include <QGpgME/Protocol>
#include <QGpgME/SignJob>
#include <QGpgME/SignEncryptJob>
#include <QGpgME/EncryptJob>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/EncryptArchiveJob>
#include <QGpgME/SignArchiveJob>
#include <QGpgME/SignEncryptArchiveJob>
#endif
#include <gpgme++/signingresult.h>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <QFileInfo>
#include <QPointer>
#include <QTextDocument> // for Qt::escape
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
namespace
{
QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted)
{
return i18nc("Input file --> Output file (rarr is arrow", "%1 &rarr; %2",
input.toHtmlEscaped(),
outputDeleted ? QStringLiteral("<s>%1</s>").arg(output.toHtmlEscaped()) : output.toHtmlEscaped());
}
class ErrorResult : public Task::Result
{
public:
ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog)
: Task::Result(), m_sign(sign), m_encrypt(encrypt), m_error(err), m_errString(errStr), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog) {}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override
{
return m_error;
}
QString errorString() const override
{
return m_errString;
}
VisualCode code() const override
{
return NeutralError;
}
AuditLogEntry auditLog() const override
{
return m_auditLog;
}
private:
const bool m_sign;
const bool m_encrypt;
const Error m_error;
const QString m_errString;
const QString m_inputLabel;
const QString m_outputLabel;
const AuditLogEntry m_auditLog;
};
namespace
{
struct LabelAndError
{
QString label;
QString errorString;
};
}
class SignEncryptFilesResult : public Task::Result
{
public:
SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_sresult(sr),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull());
}
SignEncryptFilesResult(const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_eresult(er),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_eresult.isNull());
}
SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog)
: Task::Result(),
m_sresult(sr),
m_eresult(er),
m_input{input},
m_output{output},
m_outputCreated(outputCreated),
m_auditLog(auditLog)
{
qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString
<< "\noutputError:" << m_output.errorString;
Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull());
}
QString overview() const override;
QString details() const override;
GpgME::Error error() const override;
QString errorString() const override;
VisualCode code() const override;
AuditLogEntry auditLog() const override;
private:
const SigningResult m_sresult;
const EncryptionResult m_eresult;
const LabelAndError m_input;
const LabelAndError m_output;
const bool m_outputCreated;
const AuditLogEntry m_auditLog;
};
static QString makeSigningOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Signing canceled.");
}
if (err) {
return i18n("Signing failed.");
}
return i18n("Signing succeeded.");
}
static QString makeResultOverview(const SigningResult &result)
{
return makeSigningOverview(result.error());
}
static QString makeEncryptionOverview(const Error &err)
{
if (err.isCanceled()) {
return i18n("Encryption canceled.");
}
if (err) {
return i18n("Encryption failed.");
}
return i18n("Encryption succeeded.");
}
static QString makeResultOverview(const EncryptionResult &result)
{
return makeEncryptionOverview(result.error());
}
static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er)
{
if (er.isNull() && sr.isNull()) {
return QString();
}
if (er.isNull()) {
return makeResultOverview(sr);
}
if (sr.isNull()) {
return makeResultOverview(er);
}
if (sr.error().isCanceled() || sr.error()) {
return makeResultOverview(sr);
}
if (er.error().isCanceled() || er.error()) {
return makeResultOverview(er);
}
return i18n("Signing and encryption succeeded.");
}
static QString escape(QString s)
{
s = s.toHtmlEscaped();
s.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
return s;
}
static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
return Formatting::errorAsString(err).toHtmlEscaped();
}
return QString();
}
static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError)
{
const Error err = result.error();
if (err.code() == GPG_ERR_EIO) {
if (!inputError.isEmpty()) {
return i18n("Input error: %1", escape(inputError));
} else if (!outputError.isEmpty()) {
return i18n("Output error: %1", escape(outputError));
}
}
if (err || err.isCanceled()) {
return Formatting::errorAsString(err).toHtmlEscaped();
}
return i18n(" Encryption succeeded.");
}
}
QString ErrorResult::overview() const
{
Q_ASSERT(m_error || m_error.isCanceled());
Q_ASSERT(m_sign || m_encrypt);
const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true);
const bool canceled = m_error.isCanceled();
if (m_sign && m_encrypt) {
return canceled ? i18n("%1: <b>Sign/encrypt canceled.</b>", label) : i18n(" %1: Sign/encrypt failed.", label);
}
return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: <b>%2</b>", label,
m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error));
}
QString ErrorResult::details() const
{
return m_errString;
}
class SignEncryptTask::Private
{
friend class ::Kleo::Crypto::SignEncryptTask;
SignEncryptTask *const q;
public:
explicit Private(SignEncryptTask *qq);
private:
QString inputLabel() const;
QString outputLabel() const;
bool removeExistingOutputFile();
void startSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignJob> createSignJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignEncryptJob> createSignEncryptJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::EncryptJob> createEncryptJob(GpgME::Protocol proto);
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void startSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignArchiveJob> createSignArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::SignEncryptArchiveJob> createSignEncryptArchiveJob(GpgME::Protocol proto);
std::unique_ptr<QGpgME::EncryptArchiveJob> createEncryptArchiveJob(GpgME::Protocol proto);
#endif
std::shared_ptr<const Task::Result> makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog);
private:
void slotResult(const SigningResult &);
void slotResult(const SigningResult &, const EncryptionResult &);
void slotResult(const EncryptionResult &);
void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &);
private:
std::shared_ptr<Input> input;
std::shared_ptr<Output> output;
QStringList inputFileNames;
QString outputFileName;
std::vector<Key> signers;
std::vector<Key> recipients;
bool sign : 1;
bool encrypt : 1;
bool detached : 1;
bool symmetric: 1;
bool clearsign: 1;
bool archive : 1;
QPointer<QGpgME::Job> job;
QString labelText;
std::shared_ptr<OverwritePolicy> m_overwritePolicy;
};
SignEncryptTask::Private::Private(SignEncryptTask *qq)
: q{qq}
, sign{true}
, encrypt{true}
, detached{false}
, clearsign{false}
, archive{false}
, m_overwritePolicy{new OverwritePolicy{OverwritePolicy::Ask}}
{
q->setAsciiArmor(true);
}
std::shared_ptr<const Task::Result> SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog)
{
return std::shared_ptr<const ErrorResult>(new ErrorResult(sign, encrypt, err, errStr, inputLabel(), outputLabel(), auditLog));
}
SignEncryptTask::SignEncryptTask(QObject *p)
: Task(p), d(new Private(this))
{
}
SignEncryptTask::~SignEncryptTask() {}
void SignEncryptTask::setInputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->inputFileNames = QStringList(fileName);
}
void SignEncryptTask::setInputFileNames(const QStringList &fileNames)
{
kleo_assert(!d->job);
kleo_assert(!fileNames.empty());
d->inputFileNames = fileNames;
}
void SignEncryptTask::setInput(const std::shared_ptr<Input> &input)
{
kleo_assert(!d->job);
kleo_assert(input);
d->input = input;
}
void SignEncryptTask::setOutput(const std::shared_ptr<Output> &output)
{
kleo_assert(!d->job);
kleo_assert(output);
d->output = output;
}
void SignEncryptTask::setOutputFileName(const QString &fileName)
{
kleo_assert(!d->job);
kleo_assert(!fileName.isEmpty());
d->outputFileName = fileName;
}
QString SignEncryptTask::outputFileName() const
{
return d->outputFileName;
}
void SignEncryptTask::setSigners(const std::vector<Key> &signers)
{
kleo_assert(!d->job);
d->signers = signers;
}
void SignEncryptTask::setRecipients(const std::vector<Key> &recipients)
{
kleo_assert(!d->job);
d->recipients = recipients;
}
void SignEncryptTask::setOverwritePolicy(const std::shared_ptr<OverwritePolicy> &policy)
{
kleo_assert(!d->job);
d->m_overwritePolicy = policy;
}
void SignEncryptTask::setSign(bool sign)
{
kleo_assert(!d->job);
d->sign = sign;
}
void SignEncryptTask::setEncrypt(bool encrypt)
{
kleo_assert(!d->job);
d->encrypt = encrypt;
}
void SignEncryptTask::setDetachedSignature(bool detached)
{
kleo_assert(!d->job);
d->detached = detached;
}
void SignEncryptTask::setEncryptSymmetric(bool symmetric)
{
kleo_assert(!d->job);
d->symmetric = symmetric;
}
void SignEncryptTask::setClearsign(bool clearsign)
{
kleo_assert(!d->job);
d->clearsign = clearsign;
}
void SignEncryptTask::setCreateArchive(bool archive)
{
kleo_assert(!d->job);
d->archive = archive;
}
Protocol SignEncryptTask::protocol() const
{
if (d->sign && !d->signers.empty()) {
return d->signers.front().protocol();
}
if (d->encrypt || d->symmetric) {
if (!d->recipients.empty()) {
return d->recipients.front().protocol();
} else {
return GpgME::OpenPGP; // symmetric OpenPGP encryption
}
}
throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL),
i18n("Cannot determine protocol for task"));
}
QString SignEncryptTask::label() const
{
if (!d->labelText.isEmpty()) {
return d->labelText;
}
return d->inputLabel();
}
QString SignEncryptTask::tag() const
{
return Formatting::displayName(protocol());
}
unsigned long long SignEncryptTask::inputSize() const
{
return d->input ? d->input->size() : 0U;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
static bool archiveJobsCanBeUsed(GpgME::Protocol protocol)
{
return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported();
}
#endif
void SignEncryptTask::doStart()
{
kleo_assert(!d->job);
if (d->sign) {
kleo_assert(!d->signers.empty());
if (d->archive) {
kleo_assert(!d->detached && !d->clearsign);
}
}
const auto proto = protocol();
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
if (d->archive && archiveJobsCanBeUsed(proto)) {
d->startSignEncryptArchiveJob(proto);
} else
#endif
{
if (!d->output) {
d->output = Output::createFromFile(d->outputFileName, d->m_overwritePolicy);
}
d->startSignEncryptJob(proto);
}
}
QString SignEncryptTask::Private::inputLabel() const
{
if (input) {
return input->label();
}
if (!inputFileNames.empty()) {
const auto firstFile = QFileInfo{inputFileNames.front()}.fileName();
return inputFileNames.size() == 1 ? firstFile : i18nc("<name of first file>, ...", "%1, ...", firstFile);
}
return {};
}
QString SignEncryptTask::Private::outputLabel() const
{
return output ? output->label() : QFileInfo{outputFileName}.fileName();
}
bool SignEncryptTask::Private::removeExistingOutputFile()
{
if (QFile::exists(outputFileName)) {
bool fileRemoved = false;
// we should already have asked the user for overwrite permission
if (m_overwritePolicy && (m_overwritePolicy->policy() == OverwritePolicy::Overwrite)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "going to remove file for overwriting" << outputFileName;
fileRemoved = QFile::remove(outputFileName);
if (!fileRemoved) {
qCDebug(KLEOPATRA_LOG) << __func__ << "removing file to overwrite failed";
}
} else {
qCDebug(KLEOPATRA_LOG) << __func__ << "we have no permission to overwrite" << outputFileName;
}
if (!fileRemoved) {
QMetaObject::invokeMethod(q, [this]() {
slotResult(nullptr, SigningResult{}, EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)});
}, Qt::QueuedConnection);
return false;
}
}
return true;
}
void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto)
{
kleo_assert(input);
if (encrypt || symmetric) {
Context::EncryptionFlags flags = Context::AlwaysTrust;
if (symmetric) {
flags = static_cast<Context::EncryptionFlags>(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
std::unique_ptr<QGpgME::SignEncryptJob> job = createSignEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_SUPPORTS_SET_FILENAME
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
#endif
job->start(signers, recipients,
input->ioDevice(), output->ioDevice(), flags);
this->job = job.release();
} else {
std::unique_ptr<QGpgME::EncryptJob> job = createEncryptJob(proto);
kleo_assert(job.get());
#if QGPGME_SUPPORTS_SET_FILENAME
if (inputFileNames.size() == 1) {
job->setFileName(inputFileNames.front());
}
#endif
job->start(recipients, input->ioDevice(), output->ioDevice(), flags);
this->job = job.release();
}
} else if (sign) {
std::unique_ptr<QGpgME::SignJob> job = createSignJob(proto);
kleo_assert(job.get());
kleo_assert(! (detached && clearsign));
job->start(signers,
input->ioDevice(), output->ioDevice(),
detached ? GpgME::Detached : clearsign ?
GpgME::Clearsigned : GpgME::NormalSignatureMode);
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
void SignEncryptTask::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->job) {
d->job->slotCancel();
}
}
std::unique_ptr<QGpgME::SignJob> SignEncryptTask::Private::createSignJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignJob> signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(signJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)),
q, SLOT(slotResult(GpgME::SigningResult)));
return signJob;
}
std::unique_ptr<QGpgME::SignEncryptJob> SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignEncryptJob> signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(signEncryptJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(signEncryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult,GpgME::EncryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::SigningResult,GpgME::EncryptionResult)));
return signEncryptJob;
}
std::unique_ptr<QGpgME::EncryptJob> SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::EncryptJob> encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false));
kleo_assert(encryptJob.get());
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress);
#else
connect(encryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) {
q->setProgress(processed, total);
});
#endif
connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)),
q, SLOT(slotResult(GpgME::EncryptionResult)));
return encryptJob;
}
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto)
{
kleo_assert(!input);
kleo_assert(!output);
#if ! QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
output = Output::createFromFile(outputFileName, m_overwritePolicy);
#endif
const auto baseDirectory = heuristicBaseDirectory(inputFileNames);
if (baseDirectory.isEmpty()) {
throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n'))));
}
qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory;
const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames);
const auto relativePaths = std::vector<QString>{tempPaths.begin(), tempPaths.end()};
qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths;
if (encrypt || symmetric) {
Context::EncryptionFlags flags = Context::AlwaysTrust;
if (symmetric) {
flags = static_cast<Context::EncryptionFlags>(flags | Context::Symmetric);
qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag";
}
if (sign) {
labelText = i18nc("@info", "Creating signed and encrypted archive ...");
std::unique_ptr<QGpgME::SignEncryptArchiveJob> job = createSignEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setSigners(signers);
job->setRecipients(recipients);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(signers, recipients, relativePaths, output->ioDevice(), flags);
#endif
this->job = job.release();
} else {
labelText = i18nc("@info", "Creating encrypted archive ...");
std::unique_ptr<QGpgME::EncryptArchiveJob> job = createEncryptArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setRecipients(recipients);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
job->setEncryptionFlags(flags);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(recipients, relativePaths, output->ioDevice(), flags);
#endif
this->job = job.release();
}
} else if (sign) {
labelText = i18nc("@info", "Creating signed archive ...");
std::unique_ptr<QGpgME::SignArchiveJob> job = createSignArchiveJob(proto);
kleo_assert(job.get());
job->setBaseDirectory(baseDirectory);
#if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME
job->setSigners(signers);
job->setInputPaths(relativePaths);
job->setOutputFile(outputFileName);
if (!removeExistingOutputFile()) {
return;
}
job->startIt();
#else
job->start(signers, relativePaths, output->ioDevice());
#endif
this->job = job.release();
} else {
kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!");
}
}
std::unique_ptr<QGpgME::SignArchiveJob> SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignArchiveJob> signJob(backend->signArchiveJob(q->asciiArmor()));
auto job = signJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) {
slotResult(job, signResult, EncryptionResult{});
});
return signJob;
}
std::unique_ptr<QGpgME::SignEncryptArchiveJob> SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::SignEncryptArchiveJob> signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor()));
auto job = signEncryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) {
slotResult(job, signResult, encryptResult);
});
return signEncryptJob;
}
std::unique_ptr<QGpgME::EncryptArchiveJob> SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto)
{
const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
kleo_assert(backend);
std::unique_ptr<QGpgME::EncryptArchiveJob> encryptJob(backend->encryptArchiveJob(q->asciiArmor()));
auto job = encryptJob.get();
kleo_assert(job);
connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress);
connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) {
slotResult(job, SigningResult{}, encryptResult);
});
return encryptJob;
}
#endif
void SignEncryptTask::Private::slotResult(const SigningResult &result)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), result, EncryptionResult{});
}
void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), sresult, eresult);
}
void SignEncryptTask::Private::slotResult(const EncryptionResult &result)
{
slotResult(qobject_cast<const QGpgME::Job *>(q->sender()), SigningResult{}, result);
}
void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult)
{
const AuditLogEntry auditLog = AuditLogEntry::fromJob(job);
bool outputCreated = false;
if (input && input->failed()) {
if (output) {
output->cancel();
}
q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO),
i18n("Input error: %1", escape( input->errorString())),
auditLog));
return;
} else if (sresult.error().code() || eresult.error().code()) {
if (output) {
output->cancel();
}
if (!outputFileName.isEmpty() && eresult.error().code() != GPG_ERR_EEXIST) {
// ensure that the output file is removed if the task was canceled or an error occurred;
// unless a "file exists" error occurred because this means that the file with the name
// of outputFileName wasn't created as result of this task
if (QFile::exists(outputFileName)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "after error or cancel";
if (!QFile::remove(outputFileName)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "failed";
}
}
}
} else {
try {
kleo_assert(!sresult.isNull() || !eresult.isNull());
if (output) {
output->finalize();
}
outputCreated = true;
if (input) {
input->finalize();
}
} catch (const GpgME::Exception &e) {
q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog));
return;
}
}
const LabelAndError inputInfo{inputLabel(), input ? input->errorString() : QString{}};
const LabelAndError outputInfo{outputLabel(), output ? output->errorString() : QString{}};
q->emitResult(std::shared_ptr<Result>(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog)));
}
QString SignEncryptFilesResult::overview() const
{
const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated);
return files + QLatin1String(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult));
}
QString SignEncryptFilesResult::details() const
{
return errorString();
}
GpgME::Error SignEncryptFilesResult::error() const
{
if (m_sresult.error().code()) {
return m_sresult.error();
}
if (m_eresult.error().code()) {
return m_eresult.error();
}
return {};
}
QString SignEncryptFilesResult::errorString() const
{
const bool sign = !m_sresult.isNull();
const bool encrypt = !m_eresult.isNull();
kleo_assert(sign || encrypt);
if (sign && encrypt) {
return
m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) :
m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString) :
QString();
}
- return
- sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) :
- /*else*/ makeResultDetails(m_eresult, m_input.errorString, m_output.errorString);
+ return sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) //
+ : makeResultDetails(m_eresult, m_input.errorString, m_output.errorString);
}
Task::Result::VisualCode SignEncryptFilesResult::code() const
{
if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) {
return Warning;
}
return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess;
}
AuditLogEntry SignEncryptFilesResult::auditLog() const
{
return m_auditLog;
}
#include "moc_signencrypttask.cpp"
diff --git a/src/crypto/task.h b/src/crypto/task.h
index 91aa30457..a73122aec 100644
--- a/src/crypto/task.h
+++ b/src/crypto/task.h
@@ -1,130 +1,130 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/task.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 <QObject>
#include <QString>
#include <utils/pimpl_ptr.h>
#include <gpgme++/global.h>
#include <memory>
#include <QPointer>
namespace Kleo
{
class AuditLogEntry;
}
namespace Kleo
{
namespace Crypto
{
class Task : public QObject
{
Q_OBJECT
public:
explicit Task(QObject *parent = nullptr);
~Task() override;
class Result;
void setAsciiArmor(bool armor);
bool asciiArmor() const;
virtual GpgME::Protocol protocol() const = 0;
void start();
virtual QString label() const = 0;
virtual QString tag() const;
int currentProgress() const;
int totalProgress() const;
int id() const;
static std::shared_ptr<Task> makeErrorTask(const GpgME::Error &error, const QString &details, const QString &label);
public Q_SLOTS:
virtual void cancel() = 0;
Q_SIGNALS:
void progress(int processed, int total, QPrivateSignal);
void result(const std::shared_ptr<const Kleo::Crypto::Task::Result> &, QPrivateSignal);
void started(QPrivateSignal);
protected:
std::shared_ptr<Result> makeErrorResult(const GpgME::Error &error, const QString &details);
void emitResult(const std::shared_ptr<const Task::Result> &result);
protected Q_SLOTS:
void setProgress(int processed, int total);
private Q_SLOTS:
void emitError(const GpgME::Error &error, const QString &details);
private:
virtual void doStart() = 0;
virtual unsigned long long inputSize() const = 0;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
};
class Task::Result
{
const QString m_nonce;
public:
Result();
virtual ~Result();
const QString &nonce() const
{
return m_nonce;
}
bool hasError() const;
enum VisualCode {
AllGood,
Warning,
Danger,
NeutralSuccess,
- NeutralError
+ NeutralError,
};
virtual QString icon() const;
virtual QString overview() const = 0;
virtual QString details() const = 0;
virtual GpgME::Error error() const = 0;
virtual QString errorString() const = 0;
virtual VisualCode code() const = 0;
virtual AuditLogEntry auditLog() const = 0;
virtual QPointer<Task> parentTask() const {return QPointer<Task>();}
protected:
static QString iconPath(VisualCode code);
static QString makeOverview(const QString &msg);
private:
class Private;
kdtools::pimpl_ptr<Private> d;
};
}
}
diff --git a/src/dialogs/adduseriddialog.cpp b/src/dialogs/adduseriddialog.cpp
index 84b383b24..6fff6db38 100644
--- a/src/dialogs/adduseriddialog.cpp
+++ b/src/dialogs/adduseriddialog.cpp
@@ -1,189 +1,189 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/adduseriddialog.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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "adduseriddialog.h"
#include "nameandemailwidget.h"
#include "utils/accessibility.h"
#include "utils/scrollarea.h"
#include "view/htmllabel.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <KSharedConfig>
#include <QDialogButtonBox>
#include <QLabel>
#include <QVBoxLayout>
#include "kleopatra_debug.h"
using namespace Kleo;
class AddUserIDDialog::Private
{
friend class ::Kleo::AddUserIDDialog;
AddUserIDDialog *const q;
struct {
ScrollArea *scrollArea;
NameAndEmailWidget *nameAndEmail;
HtmlLabel *resultLabel;
QDialogButtonBox *buttonBox;
} ui;
LabelHelper labelHelper;
public:
explicit Private(AddUserIDDialog *qq)
: q{qq}
{
q->setWindowTitle(i18nc("title:window", "Add User ID"));
const KConfigGroup config{KSharedConfig::openConfig(), "CertificateCreationWizard"};
const auto attrOrder = config.readEntry("OpenPGPAttributeOrder", QStringList{});
const auto nameIsRequired = attrOrder.contains(QLatin1String{"NAME!"}, Qt::CaseInsensitive);
const auto emailIsRequired = attrOrder.contains(QLatin1String{"EMAIL!"}, Qt::CaseInsensitive);
auto mainLayout = new QVBoxLayout{q};
{
- const auto infoText = nameIsRequired || emailIsRequired
+ const auto infoText = nameIsRequired || emailIsRequired //
? i18n("Enter a name and an email address to use for the user ID.")
: i18n("Enter a name and/or an email address to use for the user ID.");
auto label = new QLabel{infoText, q};
label->setWordWrap(true);
mainLayout->addWidget(label);
}
mainLayout->addWidget(new KSeparator{Qt::Horizontal, q});
ui.scrollArea = new ScrollArea{q};
ui.scrollArea->setFocusPolicy(Qt::NoFocus);
ui.scrollArea->setFrameStyle(QFrame::NoFrame);
ui.scrollArea->setBackgroundRole(q->backgroundRole());
ui.scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui.scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
auto scrollAreaLayout = qobject_cast<QBoxLayout *>(ui.scrollArea->widget()->layout());
scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
ui.nameAndEmail = new NameAndEmailWidget{q};
ui.nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0);
ui.nameAndEmail->setNameIsRequired(nameIsRequired);
ui.nameAndEmail->setNameLabel(config.readEntry("NAME_label"));
ui.nameAndEmail->setNameHint(config.readEntry("NAME_hint", config.readEntry("NAME_placeholder")));
ui.nameAndEmail->setNamePattern(config.readEntry("NAME_regex"));
ui.nameAndEmail->setEmailIsRequired(emailIsRequired);
ui.nameAndEmail->setEmailLabel(config.readEntry("EMAIL_label"));
ui.nameAndEmail->setEmailHint(config.readEntry("EMAIL_hint", config.readEntry("EMAIL_placeholder")));
ui.nameAndEmail->setEmailPattern(config.readEntry("EMAIL_regex"));
scrollAreaLayout->addWidget(ui.nameAndEmail);
scrollAreaLayout->addWidget(new KSeparator{Qt::Horizontal, q});
{
ui.resultLabel = new HtmlLabel{q};
ui.resultLabel->setWordWrap(true);
ui.resultLabel->setFocusPolicy(Qt::ClickFocus);
labelHelper.addLabel(ui.resultLabel);
scrollAreaLayout->addWidget(ui.resultLabel);
}
scrollAreaLayout->addStretch(1);
mainLayout->addWidget(ui.scrollArea);
mainLayout->addWidget(new KSeparator{Qt::Horizontal, q});
ui.buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, q};
mainLayout->addWidget(ui.buttonBox);
connect(ui.nameAndEmail, &NameAndEmailWidget::userIDChanged, q, [this]() {
updateResultLabel();
});
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); });
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
updateResultLabel();
}
private:
void checkAccept()
{
QStringList errors;
if (ui.nameAndEmail->userID().isEmpty()
&& !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) {
errors.push_back(i18n("Enter a name or an email address."));
}
const auto nameError = ui.nameAndEmail->nameError();
if (!nameError.isEmpty()) {
errors.push_back(nameError);
}
const auto emailError = ui.nameAndEmail->emailError();
if (!emailError.isEmpty()) {
errors.push_back(emailError);
}
if (errors.size() > 1) {
KMessageBox::errorList(q, i18n("There is a problem."), errors);
} else if (!errors.empty()) {
KMessageBox::error(q, errors.first());
} else {
q->accept();
}
}
void updateResultLabel()
{
ui.resultLabel->setHtml(i18nc("@info",
"<div>This is how the new user ID will be stored in the certificate:</div>"
"<center><strong>%1</strong></center>",
ui.nameAndEmail->userID().toHtmlEscaped()));
}
};
AddUserIDDialog::AddUserIDDialog(QWidget *parent, Qt::WindowFlags f)
: QDialog{parent, f}
, d(new Private{this})
{
}
AddUserIDDialog::~AddUserIDDialog() = default;
void AddUserIDDialog::setName(const QString &name)
{
d->ui.nameAndEmail->setName(name);
}
QString AddUserIDDialog::name() const
{
return d->ui.nameAndEmail->name();
}
void AddUserIDDialog::setEmail(const QString &email)
{
d->ui.nameAndEmail->setEmail(email);
}
QString AddUserIDDialog::email() const
{
return d->ui.nameAndEmail->email();
}
QString AddUserIDDialog::userID() const
{
return d->ui.nameAndEmail->userID();
}
diff --git a/src/dialogs/certificatedetailsinputwidget.cpp b/src/dialogs/certificatedetailsinputwidget.cpp
index 8e84318c1..fc55fc257 100644
--- a/src/dialogs/certificatedetailsinputwidget.cpp
+++ b/src/dialogs/certificatedetailsinputwidget.cpp
@@ -1,349 +1,350 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/certificatedetailsinputwidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certificatedetailsinputwidget.h"
#include <utils/userinfo.h>
#include <utils/validation.h>
#include <Libkleo/Dn>
#include <Libkleo/OidMap>
#include <Libkleo/Stl_Util>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QLabel>
#include <QLineEdit>
#include <QValidator>
#include <QVBoxLayout>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Dialogs;
namespace
{
struct Line {
QString attr;
QString label;
QString regex;
QLineEdit *edit;
std::shared_ptr<QValidator> validator;
bool required;
};
QString attributeFromKey(QString key)
{
return key.remove(QLatin1Char('!'));
}
QString attributeLabel(const QString &attr)
{
if (attr.isEmpty()) {
return QString();
}
const QString label = DN::attributeNameToLabel(attr);
if (!label.isEmpty()) {
return i18nc("Format string for the labels in the \"Your Personal Data\" page",
"%1 (%2)", label, attr);
} else {
return attr;
}
}
QLineEdit * addRow(QGridLayout *l, const QString &label, const QString &preset, const std::shared_ptr<QValidator> &validator, bool readonly, bool required)
{
Q_ASSERT(l);
auto lb = new QLabel(l->parentWidget());
lb->setText(i18nc("interpunctation for labels", "%1:", label));
auto le = new QLineEdit(l->parentWidget());
le->setText(preset);
if (validator) {
le->setValidator(validator.get());
}
le->setReadOnly(readonly && le->hasAcceptableInput());
auto reqLB = new QLabel(l->parentWidget());
reqLB->setText(required ? i18n("(required)") : i18n("(optional)"));
const int row = l->rowCount();
l->addWidget(lb, row, 0);
l->addWidget(le, row, 1);
l->addWidget(reqLB, row, 2);
return le;
}
bool hasIntermediateInput(const QLineEdit *le)
{
QString text = le->text();
int pos = le->cursorPosition();
const QValidator *const v = le->validator();
return v && v->validate(text, pos) == QValidator::Intermediate;
}
QString requirementsAreMet(const QVector<Line> &lines)
{
for (const Line &line : lines) {
const QLineEdit *le = line.edit;
if (!le) {
continue;
}
qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << line.attr << "\" against \"" << le->text() << "\":";
if (le->text().trimmed().isEmpty()) {
if (line.required) {
if (line.regex.isEmpty()) {
return xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label);
} else {
return xi18nc("@info", "<interface>%1</interface> is required, but empty.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
}
}
} else if (hasIntermediateInput(le)) {
if (line.regex.isEmpty()) {
return xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label);
} else {
return xi18nc("@info", "<interface>%1</interface> is incomplete.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
}
} else if (!le->hasAcceptableInput()) {
if (line.regex.isEmpty()) {
return xi18nc("@info", "<interface>%1</interface> is invalid.", line.label);
} else {
return xi18nc("@info", "<interface>%1</interface> is invalid.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
}
}
}
return QString();
}
}
class CertificateDetailsInputWidget::Private
{
friend class ::Kleo::Dialogs::CertificateDetailsInputWidget;
CertificateDetailsInputWidget *const q;
struct {
QGridLayout *gridLayout;
QVector<Line> lines;
QLineEdit *dn;
QLabel *error;
} ui;
public:
Private(CertificateDetailsInputWidget *qq)
: q(qq)
{
auto mainLayout = new QVBoxLayout(q);
ui.gridLayout = new QGridLayout();
mainLayout->addLayout(ui.gridLayout);
createForm();
mainLayout->addStretch(1);
ui.dn = new QLineEdit();
ui.dn->setFrame(false);
ui.dn->setAlignment(Qt::AlignCenter);
ui.dn->setReadOnly(true);
mainLayout->addWidget(ui.dn);
ui.error = new QLabel();
{
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(ui.error->sizePolicy().hasHeightForWidth());
ui.error->setSizePolicy(sizePolicy);
}
{
QPalette palette;
QBrush brush(QColor(255, 0, 0, 255));
brush.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Active, QPalette::WindowText, brush);
palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush);
QBrush brush1(QColor(114, 114, 114, 255));
brush1.setStyle(Qt::SolidPattern);
palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1);
ui.error->setPalette(palette);
}
ui.error->setTextFormat(Qt::RichText);
// set error label to have a fixed height of two lines:
ui.error->setText(QStringLiteral("2<br>1"));
ui.error->setFixedHeight(ui.error->minimumSizeHint().height());
ui.error->clear();
mainLayout->addWidget(ui.error);
// select the preset text in the first line edit
if (!ui.lines.empty()) {
ui.lines.first().edit->selectAll();
}
// explicitly update DN and check requirements after setup is complete
updateDN();
checkRequirements();
}
~Private()
{
// remember current attribute values as presets for next certificate
KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
for ( const Line &line : ui.lines ) {
const QString attr = attributeFromKey(line.attr);
const QString value = line.edit->text().trimmed();
config.writeEntry(attr, value);
}
config.sync();
}
void createForm()
{
static const QStringList defaultAttributeOrder = {
QStringLiteral("CN!"),
QStringLiteral("EMAIL!"),
QStringLiteral("L"),
QStringLiteral("OU"),
QStringLiteral("O"),
QStringLiteral("C"),
};
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
const QStringList attrOrder = config.readEntry("DNAttributeOrder", defaultAttributeOrder);
for (const QString &rawKey : attrOrder) {
const QString key = rawKey.trimmed().toUpper();
const QString attr = attributeFromKey(key);
if (attr.isEmpty()) {
continue;
}
- const QString defaultPreset = (attr == QLatin1String("CN")) ? userFullName() :
- (attr == QLatin1String("EMAIL")) ? userEmailAddress() :
- QString();
+ const QString defaultPreset = //
+ (attr == QLatin1String("CN")) ? userFullName()
+ : (attr == QLatin1String("EMAIL")) ? userEmailAddress()
+ : QString();
const QString preset = config.readEntry(attr, defaultPreset);
const bool required = key.endsWith(QLatin1Char('!'));
const bool readonly = config.isEntryImmutable(attr);
const QString label = config.readEntry(attr + QLatin1String("_label"),
attributeLabel(attr));
const QString regex = config.readEntry(attr + QLatin1String("_regex"));
std::shared_ptr<QValidator> validator;
if (attr == QLatin1String("EMAIL")) {
validator = regex.isEmpty() ? Validation::email() : Validation::email(regex);
} else if (!regex.isEmpty()) {
validator = std::make_shared<QRegularExpressionValidator>(QRegularExpression{regex});
}
QLineEdit *le = addRow(ui.gridLayout, label, preset, validator, readonly, required);
const Line line = { attr, label, regex, le, validator, required };
ui.lines.push_back(line);
if (attr != QLatin1String("EMAIL")) {
connect(le, &QLineEdit::textChanged, [this] () { updateDN(); });
}
connect(le, &QLineEdit::textChanged, [this] () { checkRequirements(); });
}
}
void updateDN()
{
ui.dn->setText(cmsDN());
}
QString cmsDN() const
{
DN dn;
for ( const Line &line : ui.lines ) {
const QString text = line.edit->text().trimmed();
if (text.isEmpty()) {
continue;
}
QString attr = attributeFromKey(line.attr);
if (attr == QLatin1String("EMAIL")) {
continue;
}
if (const char *const oid = oidForAttributeName(attr)) {
attr = QString::fromUtf8(oid);
}
dn.append(DN::Attribute(attr, text));
}
return dn.dn();
}
void checkRequirements()
{
const QString error = requirementsAreMet(ui.lines);
ui.error->setText(error);
Q_EMIT q->validityChanged(error.isEmpty());
}
QLineEdit * attributeWidget(const QString &attribute)
{
for ( const Line &line : ui.lines ) {
if (attributeFromKey(line.attr) == attribute) {
return line.edit;
}
}
qCWarning(KLEOPATRA_LOG) << "CertificateDetailsInputWidget: No widget for attribute" << attribute;
return nullptr;
}
void setAttributeValue(const QString &attribute, const QString &value)
{
QLineEdit *w = attributeWidget(attribute);
if (w) {
w->setText(value);
}
}
QString attributeValue(const QString &attribute)
{
const QLineEdit *w = attributeWidget(attribute);
return w ? w->text().trimmed() : QString();
}
};
CertificateDetailsInputWidget::CertificateDetailsInputWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
CertificateDetailsInputWidget::~CertificateDetailsInputWidget()
{
}
void CertificateDetailsInputWidget::setName(const QString &name)
{
d->setAttributeValue(QStringLiteral("CN"), name);
}
void CertificateDetailsInputWidget::setEmail(const QString &email)
{
d->setAttributeValue(QStringLiteral("EMAIL"), email);
}
QString CertificateDetailsInputWidget::email() const
{
return d->attributeValue(QStringLiteral("EMAIL"));
}
QString CertificateDetailsInputWidget::dn() const
{
return d->ui.dn->text();
}
diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp
index 8112c4d43..48d549355 100644
--- a/src/dialogs/certificatedetailswidget.cpp
+++ b/src/dialogs/certificatedetailswidget.cpp
@@ -1,1153 +1,1153 @@
/*
dialogs/certificatedetailswidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "certificatedetailswidget.h"
#include "kleopatra_debug.h"
#include "exportdialog.h"
#include "trustchainwidget.h"
#include "subkeyswidget.h"
#include "weboftrustdialog.h"
#include "commands/changepassphrasecommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/certifycertificatecommand.h"
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#include "commands/refreshcertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/revokeuseridcommand.h"
#include "commands/setprimaryuseridcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/genrevokecommand.h"
#include "commands/detailscommand.h"
#include "commands/dumpcertificatecommand.h"
#include "utils/accessibility.h"
#include "utils/keys.h"
#include "utils/tags.h"
#include "view/infofield.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/Dn>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyHelpers>
#include <Libkleo/GnuPG>
#include <Libkleo/NavigatableTreeWidget>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <gpgme++/tofuinfo.h>
#include <QGpgME/Debug>
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#include <QClipboard>
#include <QDateTime>
#include <QGridLayout>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QLocale>
#include <QMenu>
#include <QPushButton>
#include <QStringBuilder>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <map>
#if __has_include(<ranges>)
#include <ranges>
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
#define USE_RANGES
#endif
#endif
#include <set>
Q_DECLARE_METATYPE(GpgME::UserID)
using namespace Kleo;
namespace
{
std::vector<GpgME::UserID> selectedUserIDs(const QTreeWidget *treeWidget) {
if (!treeWidget) {
return {};
}
std::vector<GpgME::UserID> userIDs;
const auto selected = treeWidget->selectedItems();
std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) {
return item->data(0, Qt::UserRole).value<GpgME::UserID>();
});
return userIDs;
}
}
class CertificateDetailsWidget::Private
{
public:
Private(CertificateDetailsWidget *qq);
void setupCommonProperties();
void updateUserIDActions();
void setUpUserIDTable();
void setUpSMIMEAdressList();
void setupPGPProperties();
void setupSMIMEProperties();
void revokeUserID(const GpgME::UserID &uid);
void revokeSelectedUserID();
void genRevokeCert();
void refreshCertificate();
void certifyUserIDs();
void revokeCertifications();
void webOfTrustClicked();
void exportClicked();
void addUserID();
void setPrimaryUserID(const GpgME::UserID &uid = {});
void changePassphrase();
void changeExpiration();
void keysMayHaveChanged();
void showTrustChainDialog();
void showMoreDetails();
void userIDTableContextMenuRequested(const QPoint &p);
QString tofuTooltipString(const GpgME::UserID &uid) const;
QIcon trustLevelIcon(const GpgME::UserID &uid) const;
QString trustLevelText(const GpgME::UserID &uid) const;
void showIssuerCertificate();
void updateKey();
void setUpdatedKey(const GpgME::Key &key);
void keyListDone(const GpgME::KeyListResult &,
const std::vector<GpgME::Key> &, const QString &,
const GpgME::Error &);
void copyFingerprintToClipboard();
private:
CertificateDetailsWidget *const q;
public:
GpgME::Key key;
bool updateInProgress = false;
private:
InfoField *attributeField(const QString &attributeName)
{
const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName);
if (keyValuePairIt != ui.smimeAttributeFields.end()) {
return (*keyValuePairIt).second.get();
}
return nullptr;
}
private:
struct UI {
QWidget *userIDs = nullptr;
QLabel *userIDTableLabel = nullptr;
NavigatableTreeWidget *userIDTable = nullptr;
QPushButton *addUserIDBtn = nullptr;
QPushButton *setPrimaryUserIDBtn = nullptr;
QPushButton *certifyBtn = nullptr;
QPushButton *revokeCertificationsBtn = nullptr;
QPushButton *revokeUserIDBtn = nullptr;
QPushButton *webOfTrustBtn = nullptr;
std::map<QString, std::unique_ptr<InfoField>> smimeAttributeFields;
std::unique_ptr<InfoField> smimeTrustLevelField;
std::unique_ptr<InfoField> validFromField;
std::unique_ptr<InfoField> expiresField;
QAction *changeExpirationAction = nullptr;
std::unique_ptr<InfoField> fingerprintField;
QAction *copyFingerprintAction = nullptr;
std::unique_ptr<InfoField> smimeIssuerField;
QAction *showIssuerCertificateAction = nullptr;
std::unique_ptr<InfoField> complianceField;
std::unique_ptr<InfoField> trustedIntroducerField;
QLabel *smimeRelatedAddresses = nullptr;
QListWidget *smimeAddressList = nullptr;
QPushButton *moreDetailsBtn = nullptr;
QPushButton *trustChainDetailsBtn = nullptr;
QPushButton *refreshBtn = nullptr;
QPushButton *changePassphraseBtn = nullptr;
QPushButton *exportBtn = nullptr;
QPushButton *genRevokeBtn = nullptr;
void setupUi(QWidget *parent)
{
auto mainLayout = new QVBoxLayout{parent};
userIDs = new QWidget{parent};
{
auto userIDsLayout = new QVBoxLayout{userIDs};
userIDsLayout->setContentsMargins({});
userIDTableLabel = new QLabel(i18n("User IDs:"), parent);
userIDsLayout->addWidget(userIDTableLabel);
userIDTable = new NavigatableTreeWidget{parent};
userIDTableLabel->setBuddy(userIDTable);
userIDTable->setAccessibleName(i18n("User IDs"));
QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem();
__qtreewidgetitem->setText(0, QString::fromUtf8("1"));
userIDTable->setHeaderItem(__qtreewidgetitem);
userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
userIDTable->setRootIsDecorated(false);
userIDTable->setUniformRowHeights(true);
userIDTable->setAllColumnsShowFocus(false);
userIDsLayout->addWidget(userIDTable);
{
auto buttonRow = new QHBoxLayout;
addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent);
buttonRow->addWidget(addUserIDBtn);
setPrimaryUserIDBtn = new QPushButton{i18nc("@action:button", "Flag as Primary"), parent};
setPrimaryUserIDBtn->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key."));
buttonRow->addWidget(setPrimaryUserIDBtn);
certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent);
buttonRow->addWidget(certifyBtn);
webOfTrustBtn = new QPushButton(i18nc("@action:button", "Show Certifications"), parent);
buttonRow->addWidget(webOfTrustBtn);
revokeCertificationsBtn = new QPushButton(i18nc("@action:button", "Revoke Certifications"), parent);
buttonRow->addWidget(revokeCertificationsBtn);
revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent);
buttonRow->addWidget(revokeUserIDBtn);
buttonRow->addStretch(1);
userIDsLayout->addLayout(buttonRow);
}
userIDsLayout->addWidget(new KSeparator{Qt::Horizontal, parent});
}
mainLayout->addWidget(userIDs);
{
auto gridLayout = new QGridLayout;
gridLayout->setColumnStretch(1, 1);
int row = -1;
for (const auto &attribute : DN::attributeOrder()) {
const auto attributeLabel = DN::attributeNameToLabel(attribute);
if (attributeLabel.isEmpty()) {
continue;
}
const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel);
const auto & [it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique<InfoField>(labelWithColon, parent));
if (inserted) {
row++;
const auto &field = it->second;
gridLayout->addWidget(field->label(), row, 0);
gridLayout->addLayout(field->layout(), row, 1);
}
}
row++;
smimeTrustLevelField = std::make_unique<InfoField>(i18n("Trust level:"), parent);
gridLayout->addWidget(smimeTrustLevelField->label(), row, 0);
gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1);
row++;
validFromField = std::make_unique<InfoField>(i18n("Valid from:"), parent);
gridLayout->addWidget(validFromField->label(), row, 0);
gridLayout->addLayout(validFromField->layout(), row, 1);
row++;
expiresField = std::make_unique<InfoField>(i18n("Valid until:"), parent);
changeExpirationAction = new QAction{parent};
changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period"));
Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity"));
expiresField->setAction(changeExpirationAction);
gridLayout->addWidget(expiresField->label(), row, 0);
gridLayout->addLayout(expiresField->layout(), row, 1);
row++;
fingerprintField = std::make_unique<InfoField>(i18n("Fingerprint:"), parent);
if (QGuiApplication::clipboard()) {
copyFingerprintAction = new QAction{parent};
copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard"));
Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint"));
fingerprintField->setAction(copyFingerprintAction);
}
gridLayout->addWidget(fingerprintField->label(), row, 0);
gridLayout->addLayout(fingerprintField->layout(), row, 1);
row++;
smimeIssuerField = std::make_unique<InfoField>(i18n("Issuer:"), parent);
showIssuerCertificateAction = new QAction{parent};
showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate"));
Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate"));
smimeIssuerField->setAction(showIssuerCertificateAction);
gridLayout->addWidget(smimeIssuerField->label(), row, 0);
gridLayout->addLayout(smimeIssuerField->layout(), row, 1);
row++;
complianceField = std::make_unique<InfoField>(i18n("Compliance:"), parent);
gridLayout->addWidget(complianceField->label(), row, 0);
gridLayout->addLayout(complianceField->layout(), row, 1);
row++;
trustedIntroducerField = std::make_unique<InfoField>(i18n("Trusted introducer for:"), parent);
gridLayout->addWidget(trustedIntroducerField->label(), row, 0);
trustedIntroducerField->setToolTip(i18n("See certifications for details."));
gridLayout->addLayout(trustedIntroducerField->layout(), row, 1);
mainLayout->addLayout(gridLayout);
}
smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent);
mainLayout->addWidget(smimeRelatedAddresses);
smimeAddressList = new QListWidget{parent};
smimeRelatedAddresses->setBuddy(smimeAddressList);
smimeAddressList->setAccessibleName(i18n("Related addresses"));
smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers);
smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection);
mainLayout->addWidget(smimeAddressList);
mainLayout->addStretch();
{
auto buttonRow = new QHBoxLayout;
moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), parent);
buttonRow->addWidget(moreDetailsBtn);
trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details"), parent);
buttonRow->addWidget(trustChainDetailsBtn);
refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent};
#if !QGPGME_SUPPORTS_KEY_REFRESH
refreshBtn->setVisible(false);
#endif
buttonRow->addWidget(refreshBtn);
exportBtn = new QPushButton(i18nc("@action:button", "Export"), parent);
buttonRow->addWidget(exportBtn);
changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent);
buttonRow->addWidget(changePassphraseBtn);
genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent);
genRevokeBtn->setToolTip(u"<html>" %
i18n("A revocation certificate is a file that serves as a \"kill switch\" to publicly "
"declare that a key shall not anymore be used. It is not possible "
"to retract such a revocation certificate once it has been published.") %
u"</html>");
buttonRow->addWidget(genRevokeBtn);
buttonRow->addStretch(1);
mainLayout->addLayout(buttonRow);
}
}
} ui;
};
CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq)
: q{qq}
{
ui.setupUi(q);
ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested,
q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); });
connect(ui.userIDTable, &QTreeWidget::itemSelectionChanged,
q, [this]() { updateUserIDActions(); });
connect(ui.addUserIDBtn, &QPushButton::clicked,
q, [this]() { addUserID(); });
connect(ui.setPrimaryUserIDBtn, &QPushButton::clicked,
q, [this]() { setPrimaryUserID(); });
connect(ui.revokeUserIDBtn, &QPushButton::clicked,
q, [this]() { revokeSelectedUserID(); });
connect(ui.changePassphraseBtn, &QPushButton::clicked,
q, [this]() { changePassphrase(); });
connect(ui.genRevokeBtn, &QPushButton::clicked,
q, [this]() { genRevokeCert(); });
connect(ui.changeExpirationAction, &QAction::triggered,
q, [this]() { changeExpiration(); });
connect(ui.showIssuerCertificateAction, &QAction::triggered,
q, [this]() { showIssuerCertificate(); });
connect(ui.trustChainDetailsBtn, &QPushButton::pressed,
q, [this]() { showTrustChainDialog(); });
connect(ui.moreDetailsBtn, &QPushButton::pressed,
q, [this]() { showMoreDetails(); });
connect(ui.refreshBtn, &QPushButton::clicked,
q, [this]() { refreshCertificate(); });
connect(ui.certifyBtn, &QPushButton::clicked,
q, [this]() { certifyUserIDs(); });
connect(ui.revokeCertificationsBtn, &QPushButton::clicked,
q, [this]() { revokeCertifications(); });
connect(ui.webOfTrustBtn, &QPushButton::clicked,
q, [this]() { webOfTrustClicked(); });
connect(ui.exportBtn, &QPushButton::clicked,
q, [this]() { exportClicked(); });
if (ui.copyFingerprintAction) {
connect(ui.copyFingerprintAction, &QAction::triggered,
q, [this]() { copyFingerprintToClipboard(); });
}
connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged,
q, [this]() { keysMayHaveChanged(); });
}
void CertificateDetailsWidget::Private::setupCommonProperties()
{
const bool isOpenPGP = key.protocol() == GpgME::OpenPGP;
const bool isSMIME = key.protocol() == GpgME::CMS;
const bool isOwnKey = key.hasSecret();
const auto isLocalKey = !isRemoteKey(key);
const auto keyCanBeCertified = Kleo::canBeCertified(key);
// update visibility of UI elements
ui.userIDs->setVisible(isOpenPGP);
ui.addUserIDBtn->setVisible(isOwnKey);
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
ui.setPrimaryUserIDBtn->setVisible(isOwnKey);
#else
ui.setPrimaryUserIDBtn->setVisible(false);
#endif
// ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys)
// ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys)
ui.revokeCertificationsBtn->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported());
ui.revokeUserIDBtn->setVisible(isOwnKey);
for (const auto &[_, field] : ui.smimeAttributeFields) {
field->setVisible(isSMIME);
}
ui.smimeTrustLevelField->setVisible(isSMIME);
// ui.validFromField->setVisible(true); // always visible
// ui.expiresField->setVisible(true); // always visible
if (isOpenPGP && isOwnKey) {
ui.expiresField->setAction(ui.changeExpirationAction);
} else {
ui.expiresField->setAction(nullptr);
}
// ui.fingerprintField->setVisible(true); // always visible
ui.smimeIssuerField->setVisible(isSMIME);
ui.complianceField->setVisible(DeVSCompliance::isCompliant());
ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties()
ui.smimeRelatedAddresses->setVisible(isSMIME);
ui.smimeAddressList->setVisible(isSMIME);
ui.moreDetailsBtn->setVisible(isLocalKey);
ui.trustChainDetailsBtn->setVisible(isSMIME);
ui.refreshBtn->setVisible(isLocalKey);
ui.changePassphraseBtn->setVisible(isSecretKeyStoredInKeyRing(key));
ui.exportBtn->setVisible(isLocalKey);
ui.genRevokeBtn->setVisible(isOpenPGP && isOwnKey);
// update availability of buttons
const auto userCanSignUserIDs = userHasCertificationKey();
ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
ui.setPrimaryUserIDBtn->setEnabled(false); // requires a selected user ID
ui.certifyBtn->setEnabled(isLocalKey && keyCanBeCertified && userCanSignUserIDs);
ui.webOfTrustBtn->setEnabled(isLocalKey);
ui.revokeCertificationsBtn->setEnabled(userCanSignUserIDs && isLocalKey);
ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID
ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key));
ui.changePassphraseBtn->setEnabled(isSecretKeyStoredInKeyRing(key));
ui.genRevokeBtn->setEnabled(canBeUsedForSecretKeyOperations(key));
// update values of protocol-independent UI elements
ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key));
ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")),
Formatting::accessibleExpirationDate(key));
ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()),
Formatting::accessibleHexID(key.primaryFingerprint()));
if (DeVSCompliance::isCompliant()) {
ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key));
}
}
void CertificateDetailsWidget::Private::updateUserIDActions()
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{};
const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
ui.setPrimaryUserIDBtn->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
ui.revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
void CertificateDetailsWidget::Private::setUpUserIDTable()
{
ui.userIDTable->clear();
QStringList headers = { i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags") };
ui.userIDTable->setColumnCount(headers.count());
ui.userIDTable->setColumnWidth(0, 200);
ui.userIDTable->setColumnWidth(1, 200);
ui.userIDTable->setHeaderLabels(headers);
const auto uids = key.userIDs();
for (unsigned int i = 0; i < uids.size(); ++i) {
const auto &uid = uids[i];
auto item = new QTreeWidgetItem;
const QString toolTip = tofuTooltipString(uid);
item->setData(0, Qt::UserRole, QVariant::fromValue(uid));
auto pMail = Kleo::Formatting::prettyEMail(uid);
auto pName = Kleo::Formatting::prettyName(uid);
item->setData(0, Qt::DisplayRole, pMail);
item->setData(0, Qt::ToolTipRole, toolTip);
item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail);
item->setData(1, Qt::DisplayRole, pName);
item->setData(1, Qt::ToolTipRole, toolTip);
item->setData(2, Qt::DecorationRole, trustLevelIcon(uid));
item->setData(2, Qt::DisplayRole, trustLevelText(uid));
item->setData(2, Qt::ToolTipRole, toolTip);
GpgME::Error err;
QStringList tagList;
for (const auto &tag: uid.remarks(Tags::tagKeys(), err)) {
if (err) {
qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err;
}
tagList << QString::fromStdString(tag);
}
qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList;
const auto tags = tagList.join(QStringLiteral("; "));
item->setData(3, Qt::DisplayRole, tags);
item->setData(3, Qt::ToolTipRole, toolTip);
ui.userIDTable->addTopLevelItem(item);
}
if (!Tags::tagsEnabled()) {
ui.userIDTable->hideColumn(3);
}
}
void CertificateDetailsWidget::Private::setUpSMIMEAdressList()
{
ui.smimeAddressList->clear();
const auto *const emailField = attributeField(QStringLiteral("EMAIL"));
// add email address from primary user ID if not listed already as attribute field
if (!emailField) {
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
const QString dnEmail = dn[QStringLiteral("EMAIL")];
if (!dnEmail.isEmpty()) {
ui.smimeAddressList->addItem(dnEmail);
}
}
if (key.numUserIDs() > 1) {
// iterate over the secondary user IDs
#ifdef USE_RANGES
for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) {
#else
const auto uids = key.userIDs();
for (auto it = std::next(uids.begin()); it != uids.end(); ++it) {
const auto &uid = *it;
#endif
const auto name = Kleo::Formatting::prettyName(uid);
const auto email = Kleo::Formatting::prettyEMail(uid);
QString itemText;
if (name.isEmpty() && !email.isEmpty()) {
// skip email addresses already listed in email attribute field
if (emailField && email == emailField->value()) {
continue;
}
itemText = email;
} else {
// S/MIME certificates sometimes contain urls where both
// name and mail is empty. In that case we print whatever
// the uid is as name.
//
// Can be ugly like (3:uri24:http://ca.intevation.org), but
// this is better then showing an empty entry.
itemText = QString::fromUtf8(uid.id());
}
// avoid duplicate entries in the list
if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) {
ui.smimeAddressList->addItem(itemText);
}
}
}
if (ui.smimeAddressList->count() == 0) {
ui.smimeRelatedAddresses->setVisible(false);
ui.smimeAddressList->setVisible(false);
}
}
void CertificateDetailsWidget::Private::revokeUserID(const GpgME::UserID &userId)
{
const QString message = xi18nc(
"@info",
"<para>Do you really want to revoke the user ID<nl/><emphasis>%1</emphasis> ?</para>",
QString::fromUtf8(userId.id()));
auto confirmButton = KStandardGuiItem::ok();
confirmButton.setText(i18nc("@action:button", "Revoke User ID"));
confirmButton.setToolTip({});
const auto choice = KMessageBox::questionTwoActions(
q->window(),
message,
i18nc("@title:window", "Confirm Revocation"),
confirmButton,
KStandardGuiItem::cancel(),
{},
KMessageBox::Notify | KMessageBox::WindowModal);
if (choice != KMessageBox::ButtonCode::PrimaryAction) {
return;
}
auto cmd = new Commands::RevokeUserIDCommand(userId);
cmd->setParentWidget(q);
connect(cmd, &Command::finished, q, [this]() {
ui.userIDTable->setEnabled(true);
// the Revoke User ID button will be updated by the key update
updateKey();
});
ui.userIDTable->setEnabled(false);
ui.revokeUserIDBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::revokeSelectedUserID()
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
if (userIDs.size() != 1) {
return;
}
revokeUserID(userIDs.front());
}
void CertificateDetailsWidget::Private::changeExpiration()
{
auto cmd = new Kleo::Commands::ChangeExpiryCommand(key);
QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished,
q, [this]() {
ui.changeExpirationAction->setEnabled(true);
});
ui.changeExpirationAction->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::changePassphrase()
{
auto cmd = new Kleo::Commands::ChangePassphraseCommand(key);
QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished,
q, [this]() {
ui.changePassphraseBtn->setEnabled(true);
});
ui.changePassphraseBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::genRevokeCert()
{
auto cmd = new Kleo::Commands::GenRevokeCommand(key);
QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished,
q, [this]() {
ui.genRevokeBtn->setEnabled(true);
});
ui.genRevokeBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::refreshCertificate()
{
auto cmd = new Kleo::RefreshCertificateCommand{key};
QObject::connect(cmd, &Kleo::RefreshCertificateCommand::finished,
q, [this]() {
ui.refreshBtn->setEnabled(true);
});
ui.refreshBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::certifyUserIDs()
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} //
: new Kleo::Commands::CertifyCertificateCommand{userIDs};
QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished,
q, [this]() {
updateKey();
ui.certifyBtn->setEnabled(true);
});
ui.certifyBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::revokeCertifications()
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} //
: new Kleo::Commands::RevokeCertificationCommand{userIDs};
QObject::connect(cmd, &Kleo::Command::finished,
q, [this]() {
updateKey();
ui.revokeCertificationsBtn->setEnabled(true);
});
ui.revokeCertificationsBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::webOfTrustClicked()
{
QScopedPointer<WebOfTrustDialog> dlg(new WebOfTrustDialog(q));
dlg->setKey(key);
dlg->exec();
}
void CertificateDetailsWidget::Private::exportClicked()
{
QScopedPointer<ExportDialog> dlg(new ExportDialog(q));
dlg->setKey(key);
dlg->exec();
}
void CertificateDetailsWidget::Private::addUserID()
{
auto cmd = new Kleo::Commands::AddUserIDCommand(key);
QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished,
q, [this]() {
ui.addUserIDBtn->setEnabled(true);
updateKey();
});
ui.addUserIDBtn->setEnabled(false);
cmd->start();
}
void CertificateDetailsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid)
{
auto userId = uid;
if (userId.isNull()) {
const auto userIDs = selectedUserIDs(ui.userIDTable);
if (userIDs.size() != 1) {
return;
}
userId = userIDs.front();
}
auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId);
QObject::connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() {
ui.userIDTable->setEnabled(true);
// the Flag As Primary button will be updated by the key update
updateKey();
});
ui.userIDTable->setEnabled(false);
ui.setPrimaryUserIDBtn->setEnabled(false);
cmd->start();
}
namespace
{
void ensureThatKeyDetailsAreLoaded(GpgME::Key &key)
{
if (key.userID(0).numSignatures() == 0) {
key.update();
}
}
}
void CertificateDetailsWidget::Private::keysMayHaveChanged()
{
auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint());
if (!newKey.isNull()) {
ensureThatKeyDetailsAreLoaded(newKey);
setUpdatedKey(newKey);
}
}
void CertificateDetailsWidget::Private::showTrustChainDialog()
{
QScopedPointer<TrustChainDialog> dlg(new TrustChainDialog(q));
dlg->setKey(key);
dlg->exec();
}
void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p)
{
const auto userIDs = selectedUserIDs(ui.userIDTable);
const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{};
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0));
#endif
const bool canSignUserIDs = userHasCertificationKey();
const auto isLocalKey = !isRemoteKey(key);
const auto keyCanBeCertified = Kleo::canBeCertified(key);
auto menu = new QMenu(q);
#if QGPGME_SUPPORTS_SET_PRIMARY_UID
if (key.hasSecret()) {
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")),
i18nc("@action:inmenu", "Flag as Primary User ID"),
q, [this, singleUserID]() {
setPrimaryUserID(singleUserID);
});
action->setEnabled(!singleUserID.isNull() //
&& !isPrimaryUserID //
&& !Kleo::isRevokedOrExpired(singleUserID) //
&& canBeUsedForSecretKeyOperations(key));
}
#endif
{
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...")
: i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")),
actionText,
q, [this]() {
certifyUserIDs();
});
action->setEnabled(isLocalKey && keyCanBeCertified && canSignUserIDs);
}
if (Kleo::Commands::RevokeCertificationCommand::isSupported()) {
const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...")
: i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size());
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")),
actionText,
q, [this]() {
revokeCertifications();
});
action->setEnabled(isLocalKey && canSignUserIDs);
}
#ifdef MAILAKONADI_ENABLED
if (key.hasSecret()) {
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")),
i18nc("@action:inmenu", "Publish at Mail Provider ..."),
q, [this, singleUserID]() {
auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID);
ui.userIDTable->setEnabled(false);
connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished,
q, [this]() { ui.userIDTable->setEnabled(true); });
cmd->start();
});
action->setEnabled(!singleUserID.isNull());
}
#endif // MAILAKONADI_ENABLED
{
auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")),
i18nc("@action:inmenu", "Revoke User ID"),
q, [this, singleUserID]() {
revokeUserID(singleUserID);
});
action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID));
}
connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
menu->popup(ui.userIDTable->viewport()->mapToGlobal(p));
}
void CertificateDetailsWidget::Private::showMoreDetails()
{
if (key.protocol() == GpgME::CMS) {
auto cmd = new Kleo::Commands::DumpCertificateCommand(key);
cmd->setParentWidget(q);
cmd->setUseDialog(true);
cmd->start();
} else {
auto dlg = new SubKeysDialog{q};
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setKey(key);
dlg->open();
}
}
QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const
{
const auto tofu = uid.tofuInfo();
if (tofu.isNull()) {
return QString();
}
QString html = QStringLiteral("<table border=\"0\" cell-padding=\"5\">");
const auto appendRow = [&html](const QString &lbl, const QString &val) {
html += QStringLiteral("<tr>"
"<th style=\"text-align: right; padding-right: 5px; white-space: nowrap;\">%1:</th>"
"<td style=\"white-space: nowrap;\">%2</td>"
"</tr>")
.arg(lbl, val);
};
const auto appendHeader = [this, &html](const QString &hdr) {
html += QStringLiteral("<tr><th colspan=\"2\" style=\"background-color: %1; color: %2\">%3</th></tr>")
.arg(q->palette().highlight().color().name(),
q->palette().highlightedText().color().name(),
hdr);
};
const auto dateTime = [](long ts) {
QLocale l;
return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat);
};
appendHeader(i18n("Signing"));
appendRow(i18n("First message"), dateTime(tofu.signFirst()));
appendRow(i18n("Last message"), dateTime(tofu.signLast()));
appendRow(i18n("Message count"), QString::number(tofu.signCount()));
appendHeader(i18n("Encryption"));
appendRow(i18n("First message"), dateTime(tofu.encrFirst()));
appendRow(i18n("Last message"), dateTime(tofu.encrLast()));
appendRow(i18n("Message count"), QString::number(tofu.encrCount()));
html += QStringLiteral("</table>");
// Make sure the tooltip string is different for each UserID, even if the
// data are the same, otherwise the tooltip is not updated and moved when
// user moves mouse from one row to another.
html += QStringLiteral("<!-- %1 //-->").arg(QString::fromUtf8(uid.id()));
return html;
}
QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const
{
if (updateInProgress) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
}
switch (uid.validity()) {
case GpgME::UserID::Unknown:
case GpgME::UserID::Undefined:
return QIcon::fromTheme(QStringLiteral("emblem-question"));
case GpgME::UserID::Never:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
case GpgME::UserID::Marginal:
return QIcon::fromTheme(QStringLiteral("emblem-warning"));
case GpgME::UserID::Full:
case GpgME::UserID::Ultimate:
return QIcon::fromTheme(QStringLiteral("emblem-success"));
}
return {};
}
QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const
{
return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid);
}
namespace
{
auto isGood(const GpgME::UserID::Signature &signature)
{
- return signature.status() == GpgME::UserID::Signature::NoError &&
- !signature.isInvalid() &&
- 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
+ return signature.status() == GpgME::UserID::Signature::NoError //
+ && !signature.isInvalid() //
+ && 0x10 <= signature.certClass() && signature.certClass() <= 0x13;
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID::Signature> &signatures)
{
return std::accumulate(
std::begin(signatures), std::end(signatures),
std::set<QString>(),
[] (auto domains, const auto &signature) {
if (isGood(signature) && signature.isTrustSignature()) {
domains.insert(Formatting::trustSignatureDomain(signature));
}
return domains;
}
);
}
auto accumulateTrustDomains(const std::vector<GpgME::UserID> &userIds)
{
return std::accumulate(
std::begin(userIds), std::end(userIds),
std::set<QString>(),
[] (auto domains, const auto &userID) {
const auto newDomains = accumulateTrustDomains(userID.signatures());
std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains)));
return domains;
}
);
}
}
void CertificateDetailsWidget::Private::setupPGPProperties()
{
setUpUserIDTable();
const auto trustDomains = accumulateTrustDomains(key.userIDs());
ui.trustedIntroducerField->setVisible(!trustDomains.empty());
ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", "));
ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the key from external sources."));
}
static QString formatDNToolTip(const Kleo::DN &dn)
{
QString html = QStringLiteral("<table border=\"0\" cell-spacing=15>");
const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) {
const QString val = dn[attr];
if (!val.isEmpty()) {
html += QStringLiteral(
"<tr><th style=\"text-align: left; white-space: nowrap\">%1:</th>"
"<td style=\"white-space: nowrap\">%2</td>"
"</tr>").arg(lbl, val);
}
};
appendRow(i18n("Common Name"), QStringLiteral("CN"));
appendRow(i18n("Organization"), QStringLiteral("O"));
appendRow(i18n("Street"), QStringLiteral("STREET"));
appendRow(i18n("City"), QStringLiteral("L"));
appendRow(i18n("State"), QStringLiteral("ST"));
appendRow(i18n("Country"), QStringLiteral("C"));
html += QStringLiteral("</table>");
return html;
}
void CertificateDetailsWidget::Private::setupSMIMEProperties()
{
const auto ownerId = key.userID(0);
const Kleo::DN dn(ownerId.id());
for (const auto &[attributeName, field] : ui.smimeAttributeFields) {
const QString attributeValue = dn[attributeName];
field->setValue(attributeValue);
field->setVisible(!attributeValue.isEmpty());
}
ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId));
ui.smimeTrustLevelField->setValue(trustLevelText(ownerId));
const Kleo::DN issuerDN(key.issuerName());
const QString issuerCN = issuerDN[QStringLiteral("CN")];
const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN;
ui.smimeIssuerField->setValue(issuer);
ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN));
ui.showIssuerCertificateAction->setEnabled(!key.isRoot());
setUpSMIMEAdressList();
ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the CRLs and do a full validation check of the certificate."));
}
void CertificateDetailsWidget::Private::showIssuerCertificate()
{
// there is either one or no parent key
const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption);
if (parentKeys.empty()) {
KMessageBox::error(q, i18n("The issuer certificate could not be found locally."));
return;
}
auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front());
cmd->setParentWidget(q);
cmd->start();
}
void CertificateDetailsWidget::Private::copyFingerprintToClipboard()
{
if (auto clipboard = QGuiApplication::clipboard()) {
clipboard->setText(QString::fromLatin1(key.primaryFingerprint()));
}
}
CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent)
: QWidget{parent}
, d{std::make_unique<Private>(this)}
{
}
CertificateDetailsWidget::~CertificateDetailsWidget() = default;
void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &,
const std::vector<GpgME::Key> &keys,
const QString &,
const GpgME::Error &)
{
updateInProgress = false;
if (keys.size() != 1) {
qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update.";
return;
}
// As we listen for keysmayhavechanged we get the update
// after updating the keycache.
KeyCache::mutableInstance()->insert(keys);
}
void CertificateDetailsWidget::Private::updateKey()
{
key.update();
setUpdatedKey(key);
}
void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k)
{
key = k;
setupCommonProperties();
if (key.protocol() == GpgME::OpenPGP) {
setupPGPProperties();
} else {
setupSMIMEProperties();
}
}
void CertificateDetailsWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() == GpgME::CMS) {
// For everything but S/MIME this should be quick
// and we don't need to show another status.
d->updateInProgress = true;
}
d->setUpdatedKey(key);
// Run a keylistjob with full details (TOFU / Validate)
QGpgME::KeyListJob *job = key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) :
QGpgME::smime()->keyListJob(false, true, true);
auto ctx = QGpgME::Job::context(job);
ctx->addKeyListMode(GpgME::WithTofu);
ctx->addKeyListMode(GpgME::SignatureNotations);
if (key.hasSecret()) {
ctx->addKeyListMode(GpgME::WithSecret);
}
// Windows QGpgME new style connect problem makes this necessary.
connect(job, SIGNAL(result(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error)),
this, SLOT(keyListDone(GpgME::KeyListResult,std::vector<GpgME::Key>,QString,GpgME::Error)));
job->start(QStringList() << QLatin1String(key.primaryFingerprint()));
}
GpgME::Key CertificateDetailsWidget::key() const
{
return d->key;
}
#include "moc_certificatedetailswidget.cpp"
diff --git a/src/dialogs/certificateselectiondialog.h b/src/dialogs/certificateselectiondialog.h
index 31cdccff6..9184aa366 100644
--- a/src/dialogs/certificateselectiondialog.h
+++ b/src/dialogs/certificateselectiondialog.h
@@ -1,100 +1,102 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/certificateselectiondialog.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 <QDialog>
#include <utils/pimpl_ptr.h>
#include <gpgme++/global.h>
#include <memory>
#include <vector>
namespace GpgME
{
class Key;
}
namespace Kleo
{
class KeyFilter;
class KeyGroup;
namespace Dialogs
{
class CertificateSelectionDialog : public QDialog
{
Q_OBJECT
Q_FLAGS(Options)
public:
enum Option {
+ // clang-format off
SingleSelection = 0x00,
MultiSelection = 0x01,
SignOnly = 0x02,
EncryptOnly = 0x04,
AnyCertificate = 0x06,
OpenPGPFormat = 0x08,
CMSFormat = 0x10,
AnyFormat = 0x18,
Certificates = 0x00,
SecretKeys = 0x20,
IncludeGroups = 0x40,
OptionMask
+ // clang-format on
};
Q_DECLARE_FLAGS(Options, Option)
static Option optionsFromProtocol(GpgME::Protocol proto);
explicit CertificateSelectionDialog(QWidget *parent = nullptr);
~CertificateSelectionDialog() override;
void setCustomLabelText(const QString &text);
QString customLabelText() const;
void setOptions(Options options);
Options options() const;
void selectCertificates(const std::vector<GpgME::Key> &certs);
void selectCertificate(const GpgME::Key &key);
std::vector<GpgME::Key> selectedCertificates() const;
GpgME::Key selectedCertificate() const;
void selectGroups(const std::vector<Kleo::KeyGroup> &groups);
std::vector<Kleo::KeyGroup> selectedGroups() const;
static void filterAllowedKeys(std::vector<GpgME::Key> &keys, int options);
public Q_SLOTS:
void setStringFilter(const QString &text);
void setKeyFilter(const std::shared_ptr<Kleo::KeyFilter> &filter);
void accept() override;
protected:
void hideEvent(QHideEvent *) override;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
};
}
}
Q_DECLARE_OPERATORS_FOR_FLAGS(Kleo::Dialogs::CertificateSelectionDialog::Options)
diff --git a/src/dialogs/editgroupdialog.h b/src/dialogs/editgroupdialog.h
index 0475d9096..23ca70588 100644
--- a/src/dialogs/editgroupdialog.h
+++ b/src/dialogs/editgroupdialog.h
@@ -1,58 +1,58 @@
/*
dialogs/editgroupdialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include <memory>
#include <vector>
namespace GpgME
{
class Key;
}
namespace Kleo
{
namespace Dialogs
{
class EditGroupDialog : public QDialog
{
Q_OBJECT
public:
enum FocusWidget {
GroupName,
- KeysFilter
+ KeysFilter,
};
explicit EditGroupDialog(QWidget *parent = nullptr);
~EditGroupDialog() override;
void setInitialFocus(FocusWidget widget);
void setGroupName(const QString &name);
QString groupName() const;
void setGroupKeys(const std::vector<GpgME::Key> &keys);
std::vector<GpgME::Key> groupKeys() const;
protected:
void showEvent(QShowEvent *event) override;
private:
class Private;
const std::unique_ptr<Private> d;
};
}
}
diff --git a/src/dialogs/gencardkeydialog.h b/src/dialogs/gencardkeydialog.h
index 4fc8290be..02812dcf5 100644
--- a/src/dialogs/gencardkeydialog.h
+++ b/src/dialogs/gencardkeydialog.h
@@ -1,65 +1,67 @@
/* dialogs/gencardkeydialog.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-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDialog>
#include <gpgme++/key.h>
#include <memory>
#include <vector>
namespace Kleo
{
namespace SmartCard
{
struct AlgorithmInfo;
}
class GenCardKeyDialog: public QDialog
{
Q_OBJECT
public:
struct KeyParams
{
QString name;
QString email;
QString comment;
std::string algorithm;
bool backup;
};
enum KeyAttribute {
+ // clang-format off
NoKeyAttributes = 0,
KeyOwnerName = 1,
KeyOwnerEmail = 2,
KeyComment = 4,
KeyAlgorithm = 8,
LocalKeyBackup = 16,
_AllKeyAttributes_Helper,
AllKeyAttributes = 2 * (_AllKeyAttributes_Helper - 1) - 1
+ // clang-format on
};
Q_DECLARE_FLAGS(KeyAttributes, KeyAttribute)
explicit GenCardKeyDialog(KeyAttributes requiredAttributes, QWidget *parent = nullptr);
KeyParams getKeyParams() const;
void setSupportedAlgorithms(const std::vector<SmartCard::AlgorithmInfo> &algorithms, const std::string &defaultAlgo);
private:
class Private;
std::shared_ptr<Private> d;
};
} // namespace Kleo
diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp
index 71afd8091..68f7eed80 100644
--- a/src/dialogs/revokekeydialog.cpp
+++ b/src/dialogs/revokekeydialog.cpp
@@ -1,296 +1,296 @@
/* -*- 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
*/
#include "revokekeydialog.h"
#include "utils/accessibility.h"
#include "view/errorlabel.h"
#include <Libkleo/Formatting>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <QApplication>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QFocusEvent>
#include <QGroupBox>
#include <QLabel>
#include <QPushButton>
#include <QRadioButton>
#include <QRegularExpression>
#include <QTextEdit>
#include <QVBoxLayout>
#if QGPGME_SUPPORTS_KEY_REVOCATION
#include <gpgme++/global.h>
#endif
#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);
}
};
}
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);
#if 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<int>(RevocationReason::Unspecified));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key has been compromised"), q},
static_cast<int>(RevocationReason::Compromised));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is superseded"), q},
static_cast<int>(RevocationReason::Superseded));
reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is no longer used"), q},
static_cast<int>(RevocationReason::NoLongerUsed));
reasonGroup.button(static_cast<int>(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()
+ 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("<para>You are about to revoke the following key:<nl/>%1</para>")
.arg(Formatting::summaryLine(key)));
}
#if QGPGME_SUPPORTS_KEY_REVOCATION
GpgME::RevocationReason RevokeKeyDialog::reason() const
{
return static_cast<RevocationReason>(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/selftestdialog.cpp b/src/dialogs/selftestdialog.cpp
index 64a349dce..8e6ccef42 100644
--- a/src/dialogs/selftestdialog.cpp
+++ b/src/dialogs/selftestdialog.cpp
@@ -1,517 +1,516 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/selftestdialog.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 "selftestdialog.h"
#include <selftest/selftest.h>
#include <utils/accessibility.h>
#include <utils/scrollarea.h>
#include <Libkleo/NavigatableTreeView>
#include <Libkleo/SystemInfo>
#include <KLocalizedString>
#include <KColorScheme>
#include <QAbstractTableModel>
#include <QApplication>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QSplitter>
#include <QVBoxLayout>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::Dialogs;
namespace
{
class Model : public QAbstractTableModel
{
Q_OBJECT
public:
explicit Model(QObject *parent = nullptr)
: QAbstractTableModel(parent),
m_tests()
{
}
enum Column {
TestName,
TestResult,
NumColumns
};
const std::shared_ptr<SelfTest> &fromModelIndex(const QModelIndex &idx) const
{
const unsigned int row = idx.row();
if (row < m_tests.size()) {
return m_tests[row];
}
static const std::shared_ptr<SelfTest> null;
return null;
}
int rowCount(const QModelIndex &idx) const override
{
return idx.isValid() ? 0 : m_tests.size();
}
int columnCount(const QModelIndex &) const override
{
return NumColumns;
}
QVariant data(const QModelIndex &idx, int role) const override
{
const unsigned int row = idx.row();
if (idx.isValid() && row < m_tests.size())
switch (role) {
case Qt::DisplayRole:
case Qt::ToolTipRole:
switch (idx.column()) {
case TestName:
return m_tests[row]->name();
case TestResult:
- return
- m_tests[row]->skipped() ? i18n("Skipped") :
- m_tests[row]->passed() ? i18n("Passed") :
- /* else */ m_tests[row]->shortError();
+ return m_tests[row]->skipped() ? i18n("Skipped") //
+ : m_tests[row]->passed() ? i18n("Passed")
+ : m_tests[row]->shortError();
}
break;
case Qt::BackgroundRole:
if (!SystemInfo::isHighContrastModeActive()) {
KColorScheme scheme(qApp->palette().currentColorGroup());
return (m_tests[row]->skipped() ? scheme.background(KColorScheme::NeutralBackground) :
m_tests[row]->passed() ? scheme.background(KColorScheme::PositiveBackground) :
scheme.background(KColorScheme::NegativeBackground)).color();
}
}
return QVariant();
}
QVariant headerData(int section, Qt::Orientation o, int role) const override
{
if (o == Qt::Horizontal &&
section >= 0 && section < NumColumns &&
role == Qt::DisplayRole)
switch (section) {
case TestName: return i18n("Test Name");
case TestResult: return i18n("Result");
}
return QVariant();
}
void clear()
{
if (m_tests.empty()) {
return;
}
beginRemoveRows(QModelIndex(), 0, m_tests.size() - 1);
m_tests.clear();
endRemoveRows();
}
void append(const std::vector<std::shared_ptr<SelfTest>> &tests)
{
if (tests.empty()) {
return;
}
beginInsertRows(QModelIndex(), m_tests.size(), m_tests.size() + tests.size());
m_tests.insert(m_tests.end(), tests.begin(), tests.end());
endInsertRows();
}
void reloadData()
{
if (!m_tests.empty()) {
Q_EMIT dataChanged(index(0, 0), index(m_tests.size() - 1, NumColumns - 1));
}
}
const std::shared_ptr<SelfTest> &at(unsigned int idx) const
{
return m_tests.at(idx);
}
private:
std::vector<std::shared_ptr<SelfTest>> m_tests;
};
class Proxy : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit Proxy(QObject *parent = nullptr)
: QSortFilterProxyModel(parent), m_showAll(true)
{
setDynamicSortFilter(true);
}
bool showAll() const
{
return m_showAll;
}
Q_SIGNALS:
void showAllChanged(bool);
public Q_SLOTS:
void setShowAll(bool on)
{
if (on == m_showAll) {
return;
}
m_showAll = on;
invalidateFilter();
Q_EMIT showAllChanged(on);
}
private:
bool filterAcceptsRow(int src_row, const QModelIndex &src_parent) const override
{
if (m_showAll) {
return true;
}
if (const Model *const model = qobject_cast<Model *>(sourceModel())) {
if (!src_parent.isValid() && src_row >= 0 &&
src_row < model->rowCount(src_parent)) {
if (const std::shared_ptr<SelfTest> &t = model->at(src_row)) {
return !t->passed();
} else {
qCWarning(KLEOPATRA_LOG) << "NULL test??";
}
} else {
if (src_parent.isValid()) {
qCWarning(KLEOPATRA_LOG) << "view asks for subitems!";
} else {
qCWarning(KLEOPATRA_LOG) << "index " << src_row
<< " is out of range [" << 0
<< "," << model->rowCount(src_parent)
<< "]";
}
}
} else {
qCWarning(KLEOPATRA_LOG) << "expected a ::Model, got ";
if (!sourceModel()) {
qCWarning(KLEOPATRA_LOG) << "a null pointer";
} else {
qCWarning(KLEOPATRA_LOG) << sourceModel()->metaObject()->className();
}
}
return false;
}
private:
bool m_showAll;
};
class TreeView : public NavigatableTreeView
{
Q_OBJECT
public:
using NavigatableTreeView::NavigatableTreeView;
protected:
void focusInEvent(QFocusEvent *event) override
{
NavigatableTreeView::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeView::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
};
}
class SelfTestDialog::Private
{
friend class ::Kleo::Dialogs::SelfTestDialog;
SelfTestDialog *const q;
public:
explicit Private(SelfTestDialog *qq)
: q(qq),
model(q),
proxy(q),
ui(q)
{
proxy.setSourceModel(&model);
ui.resultsTV->setModel(&proxy);
ui.detailsGB->hide();
ui.proposedCorrectiveActionGB->hide();
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
connect(ui.doItPB, &QAbstractButton::clicked, q, [this]() {
slotDoItClicked();
});
connect(ui.rerunPB, &QAbstractButton::clicked, q, &SelfTestDialog::updateRequested);
connect(ui.resultsTV->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() {
slotSelectionChanged();
});
connect(ui.showAllCB, &QAbstractButton::toggled, q, [this](bool checked) {
proxy.setShowAll(checked);
if (checked) {
updateColumnSizes();
}
ensureCurrentItemIsVisible();
});
proxy.setShowAll(ui.showAllCB->isChecked());
ui.resultsTV->setFocus();
}
private:
void slotSelectionChanged()
{
const int row = selectedRowIndex();
if (row < 0) {
ui.detailsLB->setText(i18n("(select test first)"));
ui.detailsGB->hide();
ui.proposedCorrectiveActionGB->hide();
} else {
const std::shared_ptr<SelfTest> &t = model.at(row);
ui.detailsLB->setText(t->longError());
ui.detailsGB->setVisible(!t->passed());
const QString action = t->proposedFix();
ui.proposedCorrectiveActionGB->setVisible(!t->passed() && !action.isEmpty());
ui.proposedCorrectiveActionLB->setText(action);
ui.doItPB->setVisible(!t->passed() && t->canFixAutomatically());
QMetaObject::invokeMethod(q, [this]() { ensureCurrentItemIsVisible(); }, Qt::QueuedConnection);
}
}
void slotDoItClicked()
{
if (const std::shared_ptr<SelfTest> st = model.fromModelIndex(selectedRow()))
if (st->fix()) {
model.reloadData();
}
}
private:
void ensureCurrentItemIsVisible()
{
ui.resultsTV->scrollTo(ui.resultsTV->currentIndex());
}
void updateColumnSizes()
{
ui.resultsTV->header()->resizeSections(QHeaderView::ResizeToContents);
}
private:
QModelIndex selectedRow() const
{
const QItemSelectionModel *const ism = ui.resultsTV->selectionModel();
if (!ism) {
return QModelIndex();
}
const QModelIndexList mil = ism->selectedRows();
return mil.empty() ? QModelIndex() : proxy.mapToSource(mil.front());
}
int selectedRowIndex() const
{
return selectedRow().row();
}
private:
Model model;
Proxy proxy;
struct UI {
TreeView *resultsTV = nullptr;
QCheckBox *showAllCB = nullptr;
QGroupBox *detailsGB = nullptr;
QLabel *detailsLB = nullptr;
QGroupBox *proposedCorrectiveActionGB = nullptr;
QLabel *proposedCorrectiveActionLB = nullptr;
QPushButton *doItPB = nullptr;
QCheckBox *runAtStartUpCB;
QDialogButtonBox *buttonBox;
QPushButton *rerunPB = nullptr;
LabelHelper labelHelper;
explicit UI(SelfTestDialog *qq)
{
auto mainLayout = new QVBoxLayout{qq};
{
auto label = new QLabel{xi18n(
"<para>These are the results of the Kleopatra self-test suite. Click on a test for details.</para>"
"<para>Note that all but the first failure might be due to prior tests failing.</para>"), qq};
label->setWordWrap(true);
labelHelper.addLabel(label);
mainLayout->addWidget(label);
}
auto splitter = new QSplitter{qq};
splitter->setOrientation(Qt::Vertical);
{
auto widget = new QWidget{qq};
auto vbox = new QVBoxLayout{widget};
vbox->setContentsMargins(0, 0, 0, 0);
resultsTV = new TreeView{qq};
resultsTV->setAccessibleName(i18n("test results"));
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(1);
sizePolicy.setHeightForWidth(resultsTV->sizePolicy().hasHeightForWidth());
resultsTV->setSizePolicy(sizePolicy);
resultsTV->setMinimumHeight(100);
resultsTV->setRootIsDecorated(false);
resultsTV->setAllColumnsShowFocus(true);
vbox->addWidget(resultsTV);
splitter->addWidget(widget);
}
{
detailsGB = new QGroupBox{i18nc("@title:group", "Details"), qq};
auto groupBoxLayout = new QVBoxLayout{detailsGB};
auto scrollArea = new Kleo::ScrollArea{qq};
scrollArea->setFocusPolicy(Qt::NoFocus);
scrollArea->setMinimumHeight(100);
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout());
detailsLB = new QLabel{qq};
detailsLB->setTextFormat(Qt::RichText);
detailsLB->setTextInteractionFlags(Qt::TextSelectableByMouse);
detailsLB->setWordWrap(true);
labelHelper.addLabel(detailsLB);
scrollAreaLayout->addWidget(detailsLB);
scrollAreaLayout->addStretch();
groupBoxLayout->addWidget(scrollArea);
splitter->addWidget(detailsGB);
}
{
proposedCorrectiveActionGB = new QGroupBox{i18nc("@title:group", "Proposed Corrective Action"), qq};
auto groupBoxLayout = new QVBoxLayout{proposedCorrectiveActionGB};
auto scrollArea = new Kleo::ScrollArea{qq};
scrollArea->setFocusPolicy(Qt::NoFocus);
scrollArea->setMinimumHeight(100);
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto scrollAreaLayout = qobject_cast<QBoxLayout *>(scrollArea->widget()->layout());
proposedCorrectiveActionLB = new QLabel{qq};
proposedCorrectiveActionLB->setTextFormat(Qt::RichText);
proposedCorrectiveActionLB->setTextInteractionFlags(Qt::TextSelectableByMouse);
proposedCorrectiveActionLB->setWordWrap(true);
labelHelper.addLabel(proposedCorrectiveActionLB);
scrollAreaLayout->addWidget(proposedCorrectiveActionLB);
scrollAreaLayout->addStretch();
groupBoxLayout->addWidget(scrollArea);
{
auto hbox = new QHBoxLayout;
hbox->addStretch();
doItPB = new QPushButton{i18nc("@action:button", "Do It"), qq};
doItPB->setEnabled(false);
hbox->addWidget(doItPB);
groupBoxLayout->addLayout(hbox);
}
splitter->addWidget(proposedCorrectiveActionGB);
}
mainLayout->addWidget(splitter);
showAllCB = new QCheckBox{i18nc("@option:check", "Show all test results"), qq};
showAllCB->setChecked(true);
mainLayout->addWidget(showAllCB);
runAtStartUpCB = new QCheckBox{i18nc("@option:check", "Run these tests at startup"), qq};
runAtStartUpCB->setChecked(true);
mainLayout->addWidget(runAtStartUpCB);
buttonBox = new QDialogButtonBox{qq};
buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close|QDialogButtonBox::Ok);
buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Continue"));
rerunPB = buttonBox->addButton(i18nc("@action:button", "Rerun Tests"), QDialogButtonBox::ActionRole);
mainLayout->addWidget(buttonBox);
}
} ui;
};
SelfTestDialog::SelfTestDialog(QWidget *p, Qt::WindowFlags f)
: QDialog(p, f), d(new Private(this))
{
setWindowTitle(i18nc("@title:window", "Self Test"));
resize(448, 610);
setAutomaticMode(false);
}
SelfTestDialog::~SelfTestDialog() = default;
void SelfTestDialog::setTests(const std::vector<std::shared_ptr<SelfTest>> &tests)
{
d->model.clear();
d->model.append(tests);
d->updateColumnSizes();
}
void SelfTestDialog::setRunAtStartUp(bool on)
{
d->ui.runAtStartUpCB->setChecked(on);
}
bool SelfTestDialog::runAtStartUp() const
{
return d->ui.runAtStartUpCB->isChecked();
}
void SelfTestDialog::setAutomaticMode(bool automatic)
{
d->ui.showAllCB->setChecked(!automatic);
d->ui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(automatic);
d->ui.buttonBox->button(QDialogButtonBox::Cancel)->setVisible(automatic);
d->ui.buttonBox->button(QDialogButtonBox::Close)->setVisible(!automatic);
}
#include "selftestdialog.moc"
#include "moc_selftestdialog.cpp"
diff --git a/src/dialogs/setinitialpindialog.cpp b/src/dialogs/setinitialpindialog.cpp
index 42d2d3c30..b4b258206 100644
--- a/src/dialogs/setinitialpindialog.cpp
+++ b/src/dialogs/setinitialpindialog.cpp
@@ -1,202 +1,200 @@
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/setinitialpindialog.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "setinitialpindialog.h"
#include "ui_setinitialpindialog.h"
#include <Libkleo/Formatting>
#include <KIconLoader>
#include <KLocalizedString>
#include <QIcon>
#include <QTextDocument> // for Qt::escape
#include <gpgme++/error.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
enum State {
Unknown = 0,
NotSet,
AlreadySet,
Ongoing,
Ok,
Failed,
- NumStates
+ NumStates,
};
const char *icons[] = {
// PENDING(marc) use better icons, once available
"", // Unknown
"", // NotSet
"security-medium", // AlreadySet
"movie-process-working-kde", // Ongoing
"security-high", // Ok
"security-low", // Failed
};
static_assert(sizeof icons / sizeof(*icons) == NumStates, "");
static_assert(sizeof("movie-") == 7, "");
static void update_widget(State state, bool delay, QLabel *resultLB, QLabel *lb, QPushButton *pb, QLabel *statusLB)
{
Q_ASSERT(state >= 0); Q_ASSERT(state < NumStates);
const char *icon = icons[state];
if (qstrncmp(icon, "movie-", sizeof("movie-") - 1) == 0) {
resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1String(icon + sizeof("movie-")), KIconLoader::NoGroup));
} else if (icon && *icon) {
resultLB->setPixmap(QIcon::fromTheme(QLatin1String(icon)).pixmap(32));
} else {
resultLB->setPixmap(QPixmap());
}
lb->setEnabled((state == NotSet || state == Failed) && !delay);
pb->setEnabled((state == NotSet || state == Failed) && !delay);
if (state == AlreadySet) {
statusLB->setText(xi18nc("@info", "No NullPin found. <warning>If this PIN was not set by you personally, the card might have been tampered with.</warning>"));
}
}
static QString format_error(const Error &err)
{
if (err.isCanceled()) {
return i18nc("@info", "Canceled setting PIN.");
}
if (err)
return xi18nc("@info",
"There was an error setting the PIN: <message>%1</message>.",
Formatting::errorAsString(err).toHtmlEscaped());
else {
return i18nc("@info", "PIN set successfully.");
}
}
class SetInitialPinDialog::Private
{
friend class ::Kleo::Dialogs::SetInitialPinDialog;
SetInitialPinDialog *const q;
public:
explicit Private(SetInitialPinDialog *qq)
: q(qq),
nksState(Unknown),
sigGState(Unknown),
ui(q)
{
}
private:
void slotNksButtonClicked()
{
nksState = Ongoing;
ui.nksStatusLB->clear();
updateWidgets();
Q_EMIT q->nksPinRequested();
}
void slotSigGButtonClicked()
{
sigGState = Ongoing;
ui.sigGStatusLB->clear();
updateWidgets();
Q_EMIT q->sigGPinRequested();
}
private:
void updateWidgets()
{
update_widget(nksState, false,
ui.nksResultIcon, ui.nksLB, ui.nksPB, ui.nksStatusLB);
update_widget(sigGState, nksState == NotSet || nksState == Failed || nksState == Ongoing,
ui.sigGResultIcon, ui.sigGLB, ui.sigGPB, ui.sigGStatusLB);
ui.closePB()->setEnabled(q->isComplete());
ui.cancelPB()->setEnabled(!q->isComplete());
}
private:
State nksState, sigGState;
struct UI : public Ui::SetInitialPinDialog {
explicit UI(Dialogs::SetInitialPinDialog *qq)
: Ui::SetInitialPinDialog()
{
setupUi(qq);
closePB()->setEnabled(false);
connect(closePB(), &QAbstractButton::clicked, qq, &QDialog::accept);
}
QAbstractButton *closePB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Close);
}
QAbstractButton *cancelPB() const
{
Q_ASSERT(dialogButtonBox);
return dialogButtonBox->button(QDialogButtonBox::Cancel);
}
} ui;
};
SetInitialPinDialog::SetInitialPinDialog(QWidget *p)
: QDialog(p), d(new Private(this))
{
}
SetInitialPinDialog::~SetInitialPinDialog() {}
void SetInitialPinDialog::setNksPinPresent(bool on)
{
d->nksState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinPresent(bool on)
{
d->sigGState = on ? AlreadySet : NotSet;
d->updateWidgets();
}
void SetInitialPinDialog::setNksPinSettingResult(const Error &err)
{
d->ui.nksStatusLB->setText(format_error(err));
- d->nksState =
- err.isCanceled() ? NotSet :
- err ? Failed :
- Ok;
+ d->nksState = (err.isCanceled() ? NotSet //
+ : err ? Failed
+ : Ok);
d->updateWidgets();
}
void SetInitialPinDialog::setSigGPinSettingResult(const Error &err)
{
d->ui.sigGStatusLB->setText(format_error(err));
- d->sigGState =
- err.isCanceled() ? NotSet :
- err ? Failed :
- Ok;
+ d->sigGState = (err.isCanceled() ? NotSet //
+ : err ? Failed
+ : Ok);
d->updateWidgets();
}
bool SetInitialPinDialog::isComplete() const
{
return (d->nksState == Ok || d->nksState == AlreadySet);
}
#include "moc_setinitialpindialog.cpp"
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 64c75e9aa..f990a7ff5 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,845 +1,845 @@
/* -*- mode: c++; c-basic-offset:4 -*-
mainwindow.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 <config-kleopatra.h>
#include "mainwindow.h"
#include "aboutdata.h"
#include "settings.h"
#include <interfaces/focusfirstchild.h>
#include "view/padwidget.h"
#include "view/searchbar.h"
#include "view/tabwidget.h"
#include "view/keylistcontroller.h"
#include "view/keycacheoverlay.h"
#include "view/smartcardwidget.h"
#include "view/welcomewidget.h"
#include "commands/selftestcommand.h"
#include "commands/importcrlcommand.h"
#include "commands/importcertificatefromfilecommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/signencryptfilescommand.h"
#include "conf/groupsconfigdialog.h"
#include "utils/detail_p.h"
#include <Libkleo/GnuPG>
#include "utils/action_data.h"
#include "utils/filedialog.h"
#include "utils/clipboardmenu.h"
#include "utils/gui-helper.h"
#include "utils/qt-cxx20-compat.h"
#include "dialogs/updatenotification.h"
#include <KXMLGUIFactory>
#include <QApplication>
#include <QSize>
#include <QLineEdit>
#include <KActionMenu>
#include <KActionCollection>
#include <KLocalizedString>
#include <KStandardAction>
#include <QAction>
#include <KAboutData>
#include <KMessageBox>
#include <KStandardGuiItem>
#include <KShortcutsDialog>
#include <KToolBar>
#include <KEditToolBar>
#include "kleopatra_debug.h"
#include <KConfigGroup>
#include <KConfigDialog>
#include <KColorScheme>
#include <QAbstractItemView>
#include <QCloseEvent>
#include <QMenu>
#include <QTimer>
#include <QProcess>
#include <QVBoxLayout>
#include <QMimeData>
#include <QDesktopServices>
#include <QDir>
#include <QStackedWidget>
#include <QStatusBar>
#include <QLabel>
#include <QPixmap>
#include <Libkleo/Compliance>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/Stl_Util>
#include <Libkleo/Classify>
#include <Libkleo/KeyCache>
#include <Libkleo/DocAction>
#include <Libkleo/SystemInfo>
#include <vector>
#include <KSharedConfig>
#include <chrono>
using namespace std::chrono_literals;
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
static KGuiItem KStandardGuiItem_quit()
{
static const QString app = KAboutData::applicationData().displayName();
KGuiItem item = KStandardGuiItem::quit();
item.setText(xi18nc("@action:button", "&Quit <application>%1</application>", app));
return item;
}
static KGuiItem KStandardGuiItem_close()
{
KGuiItem item = KStandardGuiItem::close();
item.setText(i18nc("@action:button", "Only &Close Window"));
return item;
}
static bool isQuitting = false;
namespace
{
static const std::vector<QString> mainViewActionNames = {
QStringLiteral("view_certificate_overview"),
QStringLiteral("manage_smartcard"),
- QStringLiteral("pad_view")
+ QStringLiteral("pad_view"),
};
class CertificateView : public QWidget, public FocusFirstChild
{
Q_OBJECT
public:
CertificateView(QWidget* parent = nullptr)
: QWidget{parent}
, ui{this}
{
}
SearchBar *searchBar() const
{
return ui.searchBar;
}
TabWidget *tabWidget() const
{
return ui.tabWidget;
}
void focusFirstChild(Qt::FocusReason reason) override
{
ui.searchBar->lineEdit()->setFocus(reason);
}
private:
struct UI {
TabWidget *tabWidget = nullptr;
SearchBar *searchBar = nullptr;
explicit UI(CertificateView *q)
{
auto vbox = new QVBoxLayout{q};
vbox->setSpacing(0);
searchBar = new SearchBar{q};
vbox->addWidget(searchBar);
tabWidget = new TabWidget{q};
vbox->addWidget(tabWidget);
tabWidget->connectSearchBar(searchBar);
}
} ui;
};
}
class MainWindow::Private
{
friend class ::MainWindow;
MainWindow *const q;
public:
explicit Private(MainWindow *qq);
~Private();
template <typename T>
void createAndStart()
{
(new T(this->currentView(), &this->controller))->start();
}
template <typename T>
void createAndStart(QAbstractItemView *view)
{
(new T(view, &this->controller))->start();
}
template <typename T>
void createAndStart(const QStringList &a)
{
(new T(a, this->currentView(), &this->controller))->start();
}
template <typename T>
void createAndStart(const QStringList &a, QAbstractItemView *view)
{
(new T(a, view, &this->controller))->start();
}
void closeAndQuit()
{
const QString app = KAboutData::applicationData().displayName();
const int rc = KMessageBox::questionTwoActionsCancel(q,
xi18n("<application>%1</application> may be used by other applications as a service.<nl/>"
"You may instead want to close this window without exiting <application>%1</application>.",
app),
i18nc("@title:window", "Really Quit?"),
KStandardGuiItem_close(),
KStandardGuiItem_quit(),
KStandardGuiItem::cancel(),
QLatin1String("really-quit-") + app.toLower());
if (rc == KMessageBox::Cancel) {
return;
}
isQuitting = true;
if (!q->close()) {
return;
}
// WARNING: 'this' might be deleted at this point!
if (rc == KMessageBox::ButtonCode::SecondaryAction) {
qApp->quit();
}
}
void configureToolbars()
{
KEditToolBar dlg(q->factory());
dlg.exec();
}
void editKeybindings()
{
KShortcutsDialog::showDialog(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, q);
updateSearchBarClickMessage();
}
void updateSearchBarClickMessage()
{
const QString shortcutStr = focusToClickSearchAction->shortcut().toString();
ui.searchTab->searchBar()->updateClickMessage(shortcutStr);
}
void updateStatusBar()
{
if (DeVSCompliance::isActive()) {
auto statusBar = std::make_unique<QStatusBar>();
auto statusLbl = std::make_unique<QLabel>(DeVSCompliance::name());
if (!SystemInfo::isHighContrastModeActive()) {
const auto color = KColorScheme(QPalette::Active, KColorScheme::View).foreground(
DeVSCompliance::isCompliant() ? KColorScheme::NormalText: KColorScheme::NegativeText
).color();
const auto background = KColorScheme(QPalette::Active, KColorScheme::View).background(
DeVSCompliance::isCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground
).color();
statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }").
arg(color.name()).arg(background.name()));
}
statusBar->insertPermanentWidget(0, statusLbl.release());
q->setStatusBar(statusBar.release()); // QMainWindow takes ownership
} else {
q->setStatusBar(nullptr);
}
}
void selfTest()
{
createAndStart<SelfTestCommand>();
}
void configureGroups()
{
if (KConfigDialog::showDialog(GroupsConfigDialog::dialogName())) {
return;
}
KConfigDialog *dialog = new GroupsConfigDialog(q);
dialog->show();
}
void showHandbook();
void gnupgLogViewer()
{
// Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process.
if (!QProcess::startDetached(QStringLiteral("kwatchgnupg"), QStringList()))
KMessageBox::error(q, i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). "
"Please check your installation."),
i18n("Error Starting KWatchGnuPG"));
}
void forceUpdateCheck()
{
UpdateNotification::forceUpdateCheck(q);
}
void slotConfigCommitted();
void slotContextMenuRequested(QAbstractItemView *, const QPoint &p)
{
if (auto const menu = qobject_cast<QMenu *>(q->factory()->container(QStringLiteral("listview_popup"), q))) {
menu->exec(p);
} else {
qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" <Menu> in kleopatra's ui.rc file";
}
}
void slotFocusQuickSearch()
{
ui.searchTab->searchBar()->lineEdit()->setFocus();
}
void showView(const QString &actionName, QWidget *widget)
{
const auto coll = q->actionCollection();
if (coll) {
for ( const QString &name : mainViewActionNames ) {
if (auto action = coll->action(name)) {
action->setChecked(name == actionName);
}
}
}
ui.stackWidget->setCurrentWidget(widget);
if (auto ffci = dynamic_cast<Kleo::FocusFirstChild *>(widget)) {
ffci->focusFirstChild(Qt::TabFocusReason);
}
}
void showCertificateView()
{
if (KeyCache::instance()->keys().empty()) {
showView(QStringLiteral("view_certificate_overview"), ui.welcomeWidget);
} else {
showView(QStringLiteral("view_certificate_overview"), ui.searchTab);
}
}
void showSmartcardView()
{
showView(QStringLiteral("manage_smartcard"), ui.scWidget);
}
void showPadView()
{
if (!ui.padWidget) {
ui.padWidget = new PadWidget;
ui.stackWidget->addWidget(ui.padWidget);
}
showView(QStringLiteral("pad_view"), ui.padWidget);
ui.stackWidget->resize(ui.padWidget->sizeHint());
}
void restartDaemons()
{
Kleo::killDaemons();
}
private:
void setupActions();
QAbstractItemView *currentView() const
{
return ui.searchTab->tabWidget()->currentView();
}
void keyListingDone()
{
const auto curWidget = ui.stackWidget->currentWidget();
if (curWidget == ui.scWidget || curWidget == ui.padWidget) {
return;
}
showCertificateView();
}
private:
Kleo::KeyListController controller;
bool firstShow : 1;
struct UI {
CertificateView *searchTab = nullptr;
PadWidget *padWidget = nullptr;
SmartCardWidget *scWidget = nullptr;
WelcomeWidget *welcomeWidget = nullptr;
QStackedWidget *stackWidget = nullptr;
explicit UI(MainWindow *q);
} ui;
QAction *focusToClickSearchAction = nullptr;
ClipboardMenu *clipboadMenu = nullptr;
};
MainWindow::Private::UI::UI(MainWindow *q)
: padWidget(nullptr)
{
auto mainWidget = new QWidget{q};
auto mainLayout = new QVBoxLayout(mainWidget);
stackWidget = new QStackedWidget{q};
searchTab = new CertificateView{q};
stackWidget->addWidget(searchTab);
new KeyCacheOverlay(mainWidget, q);
scWidget = new SmartCardWidget{q};
stackWidget->addWidget(scWidget);
welcomeWidget = new WelcomeWidget{q};
stackWidget->addWidget(welcomeWidget);
mainLayout->addWidget(stackWidget);
q->setCentralWidget(mainWidget);
}
MainWindow::Private::Private(MainWindow *qq)
: q(qq),
controller(q),
firstShow(true),
ui(q)
{
KDAB_SET_OBJECT_NAME(controller);
AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q);
AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q);
KDAB_SET_OBJECT_NAME(flatModel);
KDAB_SET_OBJECT_NAME(hierarchicalModel);
controller.setFlatModel(flatModel);
controller.setHierarchicalModel(hierarchicalModel);
controller.setTabWidget(ui.searchTab->tabWidget());
ui.searchTab->tabWidget()->setFlatModel(flatModel);
ui.searchTab->tabWidget()->setHierarchicalModel(hierarchicalModel);
setupActions();
ui.stackWidget->setCurrentWidget(ui.searchTab);
if (auto action = q->actionCollection()->action(QStringLiteral("view_certificate_overview"))) {
action->setChecked(true);
}
connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView*,QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView*,QPoint)));
connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this] () {keyListingDone();});
q->createGUI(QStringLiteral("kleopatra.rc"));
// make toolbar buttons accessible by keyboard
auto toolbar = q->findChild<KToolBar*>();
if (toolbar) {
auto toolbarButtons = toolbar->findChildren<QToolButton*>();
for (auto b : toolbarButtons) {
b->setFocusPolicy(Qt::TabFocus);
}
// move toolbar and its child widgets before the central widget in the tab order;
// this is necessary to make Shift+Tab work as expected
forceSetTabOrder(q, toolbar);
auto toolbarChildren = toolbar->findChildren<QWidget*>();
std::for_each(std::rbegin(toolbarChildren), std::rend(toolbarChildren), [toolbar](auto w) {
forceSetTabOrder(toolbar, w);
});
}
const auto title = Kleo::brandingWindowTitle();
if (!title.isEmpty()) {
QApplication::setApplicationDisplayName(title);
}
const auto icon = Kleo::brandingIcon();
if (!icon.isEmpty()) {
const auto dir = QDir(Kleo::gpg4winInstallPath() + QStringLiteral("/../share/kleopatra/pics"));
qCDebug(KLEOPATRA_LOG) << "Loading branding icon:" << dir.absoluteFilePath(icon);
QPixmap brandingIcon(dir.absoluteFilePath(icon));
if (!brandingIcon.isNull()) {
auto w = new QWidget;
auto hl = new QHBoxLayout;
auto lbl = new QLabel;
w->setLayout(hl);
hl->addWidget(lbl);
lbl->setPixmap(brandingIcon);
toolbar->addSeparator();
toolbar->addWidget(w);
}
}
// Disable whats this as the vast majority of things does not have whats this
// set in Kleopatra.
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
// With Qt 6 this is default and the attribute is no longer documented.
QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif
if (auto action = q->actionCollection()->action(QStringLiteral("help_whats_this"))) {
delete action;
}
q->setAcceptDrops(true);
// set default window size
q->resize(QSize(1024, 500));
q->setAutoSaveSettings();
updateSearchBarClickMessage();
updateStatusBar();
if (KeyCache::instance()->initialized()) {
keyListingDone();
}
}
MainWindow::Private::~Private() {}
MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
: KXmlGuiWindow(parent, flags), d(new Private(this))
{}
MainWindow::~MainWindow() {}
void MainWindow::Private::setupActions()
{
KActionCollection *const coll = q->actionCollection();
const std::vector<action_data> action_data = {
// see keylistcontroller.cpp for more actions
// Tools menu
#ifndef Q_OS_WIN
{
"tools_start_kwatchgnupg", i18n("GnuPG Log Viewer"), QString(),
- "kwatchgnupg", q, [this](bool) { gnupgLogViewer(); }, QString()
+ "kwatchgnupg", q, [this](bool) { gnupgLogViewer(); }, QString(),
},
#endif
{
"tools_restart_backend", i18nc("@action:inmenu", "Restart Background Processes"),
i18nc("@info:tooltip", "Restart the background processes, e.g. after making changes to the configuration."),
- "view-refresh", q, [this](bool) { restartDaemons(); }, {}
+ "view-refresh", q, [this](bool) { restartDaemons(); }, {},
},
// Help menu
#ifdef Q_OS_WIN
{
"help_check_updates", i18n("Check for updates"), QString(),
- "gpg4win-compact", q, [this](bool) { forceUpdateCheck(); }, QString()
+ "gpg4win-compact", q, [this](bool) { forceUpdateCheck(); }, QString(),
},
#endif
// View menu
{
"view_certificate_overview", i18nc("@action show certificate overview", "Certificates"),
- i18n("Show certificate overview"), "view-certificate", q, [this](bool) { showCertificateView(); }, QString()
+ i18n("Show certificate overview"), "view-certificate", q, [this](bool) { showCertificateView(); }, QString(),
},
{
"pad_view", i18nc("@action show input / output area for encrypting/signing resp. decrypting/verifying text", "Notepad"),
- i18n("Show pad for encrypting/decrypting and signing/verifying text"), "note", q, [this](bool) { showPadView(); }, QString()
+ i18n("Show pad for encrypting/decrypting and signing/verifying text"), "note", q, [this](bool) { showPadView(); }, QString(),
},
{
"manage_smartcard", i18nc("@action show smartcard management view", "Smartcards"),
- i18n("Show smartcard management"), "auth-sim-locked", q, [this](bool) { showSmartcardView(); }, QString()
+ i18n("Show smartcard management"), "auth-sim-locked", q, [this](bool) { showSmartcardView(); }, QString(),
},
// Settings menu
{
"settings_self_test", i18n("Perform Self-Test"), QString(),
- nullptr, q, [this](bool) { selfTest(); }, QString()
+ nullptr, q, [this](bool) { selfTest(); }, QString(),
},
{
"configure_groups", i18n("Configure Groups..."), QString(),
- "group", q, [this](bool) { configureGroups(); }, QString()
+ "group", q, [this](bool) { configureGroups(); }, QString(),
}
};
make_actions_from_data(action_data, coll);
if (!Settings().groupsEnabled()) {
if (auto action = coll->action(QStringLiteral("configure_groups"))) {
delete action;
}
}
for ( const QString &name : mainViewActionNames ) {
if (auto action = coll->action(name)) {
action->setCheckable(true);
}
}
KStandardAction::close(q, SLOT(close()), coll);
KStandardAction::quit(q, SLOT(closeAndQuit()), coll);
KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll);
KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll);
KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll);
focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q);
coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction);
coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q));
connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch()));
clipboadMenu = new ClipboardMenu(q);
clipboadMenu->setMainWindow(q);
clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
clipboadMenu->clipboardMenu()->setPopupMode(QToolButton::InstantPopup);
coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu());
/* Add additional help actions for documentation */
const auto compendium = new DocAction(QIcon::fromTheme(QStringLiteral("gpg4win-compact")), i18n("Gpg4win Compendium"),
i18nc("The Gpg4win compendium is only available"
"at this point (24.7.2017) in german and english."
"Please check with Gpg4win before translating this filename.",
"gpg4win-compendium-en.pdf"),
QStringLiteral("../share/gpg4win"));
coll->addAction(QStringLiteral("help_doc_compendium"), compendium);
/* Documentation centered around the german approved VS-NfD mode for official
* RESTRICTED communication. This is only available in some distributions with
* the focus on official communications. */
const auto quickguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Quickguide"),
i18nc("Only available in German and English. Leave to English for other languages.",
"encrypt_and_sign_gnupgvsd_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_quickguide"), quickguide);
const auto symguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Password-based encryption"),
i18nc("Only available in German and English. Leave to English for other languages.",
"symmetric_encryption_gnupgvsd_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_symenc"), symguide);
const auto groups = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Group configuration"),
i18nc("Only available in German and English. Leave to English for other languages.",
"groupfeature_gnupgvsd_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_groups"), groups);
#ifdef Q_OS_WIN
const auto gpgol = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Mail encryption in Outlook"),
i18nc("Only available in German and English. Leave to English for other languages. Only shown on Windows.",
"gpgol_outlook_addin_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_gpgol"), gpgol);
#endif
/* The submenu with advanced topics */
const auto certmngmnt = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Certification Management"),
i18nc("Only available in German and English. Leave to English for other languages.",
"certification_management_gnupgvsd_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_cert_management"), certmngmnt);
const auto smartcard = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Smartcard setup"),
i18nc("Only available in German and English. Leave to English for other languages.",
"smartcard_setup_gnupgvsd_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_smartcard"), smartcard);
const auto man_gnupg = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("GnuPG Command&line"),
QStringLiteral("gnupg_manual_en.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_gnupg"), man_gnupg);
/* The secops */
const auto vsa10573 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10573"),
i18nc("Only available in German and English. Leave to English for other languages.",
"BSI-VSA-10573-ENG_secops-20220207.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_vsa10573"), vsa10573);
const auto vsa10584 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10584"),
i18nc("Only available in German and English. Leave to English for other languages.",
"BSI-VSA-10584-ENG_secops-20220207.pdf"),
QStringLiteral("../share/doc/gnupg-vsd"));
coll->addAction(QStringLiteral("help_doc_vsa10584"), vsa10584);
q->setStandardToolBarMenuEnabled(true);
controller.createActions(coll);
ui.searchTab->tabWidget()->createActions(coll);
}
void MainWindow::Private::slotConfigCommitted()
{
controller.updateConfig();
updateStatusBar();
}
void MainWindow::closeEvent(QCloseEvent *e)
{
// KMainWindow::closeEvent() insists on quitting the application,
// so do not let it touch the event...
qCDebug(KLEOPATRA_LOG);
if (d->controller.hasRunningCommands()) {
if (d->controller.shutdownWarningRequired()) {
const int ret = KMessageBox::warningContinueCancel(this, i18n("There are still some background operations ongoing. "
"These will be terminated when closing the window. "
"Proceed?"),
i18n("Ongoing Background Tasks"));
if (ret != KMessageBox::Continue) {
e->ignore();
return;
}
}
d->controller.cancelCommands();
if (d->controller.hasRunningCommands()) {
// wait for them to be finished:
setEnabled(false);
QEventLoop ev;
QTimer::singleShot(100ms, &ev, &QEventLoop::quit);
connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit);
ev.exec();
if (d->controller.hasRunningCommands())
qCWarning(KLEOPATRA_LOG)
<< "controller still has commands running, this may crash now...";
setEnabled(true);
}
}
if (isQuitting || qApp->isSavingSession()) {
d->ui.searchTab->tabWidget()->saveViews(KSharedConfig::openConfig().data());
KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup()));
saveMainWindowSettings(grp);
e->accept();
} else {
e->ignore();
hide();
}
}
void MainWindow::showEvent(QShowEvent *e)
{
KXmlGuiWindow::showEvent(e);
if (d->firstShow) {
d->ui.searchTab->tabWidget()->loadViews(KSharedConfig::openConfig().data());
d->firstShow = false;
}
if (!savedGeometry.isEmpty()) {
restoreGeometry(savedGeometry);
}
}
void MainWindow::hideEvent(QHideEvent *e)
{
savedGeometry = saveGeometry();
KXmlGuiWindow::hideEvent(e);
}
void MainWindow::importCertificatesFromFile(const QStringList &files)
{
if (!files.empty()) {
d->createAndStart<ImportCertificateFromFileCommand>(files);
}
}
static QStringList extract_local_files(const QMimeData *data)
{
const QList<QUrl> urls = data->urls();
// begin workaround KDE/Qt misinterpretation of text/uri-list
QList<QUrl>::const_iterator end = urls.end();
if (urls.size() > 1 && !urls.back().isValid()) {
--end;
}
// end workaround
QStringList result;
std::transform(urls.begin(), end,
std::back_inserter(result),
std::mem_fn(&QUrl::toLocalFile));
result.erase(std::remove_if(result.begin(), result.end(),
std::mem_fn(&QString::isEmpty)), result.end());
return result;
}
static bool can_decode_local_files(const QMimeData *data)
{
if (!data) {
return false;
}
return !extract_local_files(data).empty();
}
void MainWindow::dragEnterEvent(QDragEnterEvent *e)
{
qCDebug(KLEOPATRA_LOG);
if (can_decode_local_files(e->mimeData())) {
e->acceptProposedAction();
}
}
void MainWindow::dropEvent(QDropEvent *e)
{
qCDebug(KLEOPATRA_LOG);
if (!can_decode_local_files(e->mimeData())) {
return;
}
e->setDropAction(Qt::CopyAction);
const QStringList files = extract_local_files(e->mimeData());
const unsigned int classification = classify(files);
QMenu menu;
QAction *const signEncrypt = menu.addAction(i18n("Sign/Encrypt..."));
QAction *const decryptVerify = mayBeAnyMessageType(classification) ? menu.addAction(i18n("Decrypt/Verify...")) : nullptr;
if (signEncrypt || decryptVerify) {
menu.addSeparator();
}
QAction *const importCerts = mayBeAnyCertStoreType(classification) ? menu.addAction(i18n("Import Certificates")) : nullptr;
QAction *const importCRLs = mayBeCertificateRevocationList(classification) ? menu.addAction(i18n("Import CRLs")) : nullptr;
if (importCerts || importCRLs) {
menu.addSeparator();
}
if (!signEncrypt && !decryptVerify && !importCerts && !importCRLs) {
return;
}
menu.addAction(i18n("Cancel"));
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const QAction *const chosen = menu.exec(mapToGlobal(e->pos()));
#else
const QAction *const chosen = menu.exec(mapToGlobal(e->position().toPoint()));
#endif
if (!chosen) {
return;
}
if (chosen == signEncrypt) {
d->createAndStart<SignEncryptFilesCommand>(files);
} else if (chosen == decryptVerify) {
d->createAndStart<DecryptVerifyFilesCommand>(files);
} else if (chosen == importCerts) {
d->createAndStart<ImportCertificateFromFileCommand>(files);
} else if (chosen == importCRLs) {
d->createAndStart<ImportCrlCommand>(files);
}
e->accept();
}
void MainWindow::readProperties(const KConfigGroup &cg)
{
qCDebug(KLEOPATRA_LOG);
KXmlGuiWindow::readProperties(cg);
setHidden(cg.readEntry("hidden", false));
}
void MainWindow::saveProperties(KConfigGroup &cg)
{
qCDebug(KLEOPATRA_LOG);
KXmlGuiWindow::saveProperties(cg);
cg.writeEntry("hidden", isHidden());
}
#include "mainwindow.moc"
#include "moc_mainwindow.cpp"
diff --git a/src/newcertificatewizard/advancedsettingsdialog.cpp b/src/newcertificatewizard/advancedsettingsdialog.cpp
index c046bd866..e9236db7d 100644
--- a/src/newcertificatewizard/advancedsettingsdialog.cpp
+++ b/src/newcertificatewizard/advancedsettingsdialog.cpp
@@ -1,1033 +1,1033 @@
/* -*- mode: c++; c-basic-offset:4 -*-
newcertificatewizard/advancedsettingsdialog.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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "advancedsettingsdialog_p.h"
#include "keyalgo_p.h"
#include "listwidget.h"
#include "utils/expiration.h"
#include "utils/gui-helper.h"
#include "utils/scrollarea.h"
#include <settings.h>
#include <Libkleo/Compat>
#include <Libkleo/Compliance>
#include <Libkleo/GnuPG>
#include <KDateComboBox>
#include <KLocalizedString>
#include <KMessageBox>
#include <QGpgME/CryptoConfig>
#include <QGpgME/Protocol>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QRadioButton>
#include <QTabWidget>
#include <QVBoxLayout>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::NewCertificateUi;
using namespace GpgME;
static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes";
static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes";
static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes";
static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels";
static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels";
static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels";
static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType";
static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType";
// This should come from gpgme in the future
// For now we only support the basic 2.1 curves and check
// for GnuPG 2.1. The whole subkey / usage generation needs
// new api and a reworked dialog. (ah 10.3.16)
// EDDSA should be supported, too.
static const QStringList curveNames {
{ QStringLiteral("brainpoolP256r1") },
{ QStringLiteral("brainpoolP384r1") },
{ QStringLiteral("brainpoolP512r1") },
{ QStringLiteral("NIST P-256") },
{ QStringLiteral("NIST P-384") },
{ QStringLiteral("NIST P-521") },
};
namespace
{
static void set_keysize(QComboBox *cb, unsigned int strength)
{
if (!cb) {
return;
}
const int idx = cb->findData(static_cast<int>(strength));
if (idx >= 0) {
cb->setCurrentIndex(idx);
}
}
static unsigned int get_keysize(const QComboBox *cb)
{
if (!cb) {
return 0;
}
const int idx = cb->currentIndex();
if (idx < 0) {
return 0;
}
return cb->itemData(idx).toInt();
}
static void set_curve(QComboBox *cb, const QString &curve)
{
if (!cb) {
return;
}
const int idx = cb->findText(curve, Qt::MatchFixedString);
if (idx >= 0) {
cb->setCurrentIndex(idx);
}
}
static QString get_curve(const QComboBox *cb)
{
if (!cb) {
return QString();
}
return cb->currentText();
}
// Extract the algo information from default_pubkey_algo format
//
// and put it into the return values size, algo and curve.
//
// Values look like:
// RSA-2048
// rsa2048/cert,sign+rsa2048/enc
// brainpoolP256r1+brainpoolP256r1
static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve)
{
const auto split = algoString.split(QLatin1Char('/'));
bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1String("enc"));
// Normalize
const auto lowered = split[0].toLower().remove(QLatin1Char('-'));
if (!algo || !size) {
return;
}
*algo = Subkey::AlgoUnknown;
if (lowered.startsWith(QLatin1String("rsa"))) {
*algo = Subkey::AlgoRSA;
} else if (lowered.startsWith(QLatin1String("dsa"))) {
*algo = Subkey::AlgoDSA;
} else if (lowered.startsWith(QLatin1String("elg"))) {
*algo = Subkey::AlgoELG;
}
if (*algo != Subkey::AlgoUnknown) {
bool ok;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
*size = lowered.rightRef(lowered.size() - 3).toInt(&ok);
#else
*size = QStringView(lowered).right(lowered.size() - 3).toInt(&ok);
#endif
if (!ok) {
qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered;
*size = 3072;
}
return;
}
// Now the ECC Algorithms
if (lowered.startsWith(QLatin1String("ed25519"))) {
// Special handling for this as technically
// this is a cv25519 curve used for EDDSA
if (isEncrypt) {
curve = QLatin1String("cv25519");
*algo = Subkey::AlgoECDH;
} else {
curve = split[0];
*algo = Subkey::AlgoEDDSA;
}
return;
}
- if (lowered.startsWith(QLatin1String("cv25519")) ||
- lowered.startsWith(QLatin1String("nist")) ||
- lowered.startsWith(QLatin1String("brainpool")) ||
- lowered.startsWith(QLatin1String("secp"))) {
+ if (lowered.startsWith(QLatin1String("cv25519")) //
+ || lowered.startsWith(QLatin1String("nist")) //
+ || lowered.startsWith(QLatin1String("brainpool")) //
+ || lowered.startsWith(QLatin1String("secp"))) {
curve = split[0];
*algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA;
return;
}
qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString;
}
}
struct AdvancedSettingsDialog::UI
{
QTabWidget *tabWidget = nullptr;
QRadioButton *rsaRB = nullptr;
QComboBox *rsaKeyStrengthCB = nullptr;
QCheckBox *rsaSubCB = nullptr;
QComboBox *rsaKeyStrengthSubCB = nullptr;
QRadioButton *dsaRB = nullptr;
QComboBox *dsaKeyStrengthCB = nullptr;
QCheckBox *elgCB = nullptr;
QComboBox *elgKeyStrengthCB = nullptr;
QRadioButton *ecdsaRB = nullptr;
QComboBox *ecdsaKeyCurvesCB = nullptr;
QCheckBox *ecdhCB = nullptr;
QComboBox *ecdhKeyCurvesCB = nullptr;
QCheckBox *certificationCB = nullptr;
QCheckBox *signingCB = nullptr;
QCheckBox *encryptionCB = nullptr;
QCheckBox *authenticationCB = nullptr;
QCheckBox *expiryCB = nullptr;
KDateComboBox *expiryDE = nullptr;
ScrollArea *personalTab = nullptr;
QGroupBox *uidGB = nullptr;
Kleo::NewCertificateUi::ListWidget *uidLW = nullptr;
QGroupBox *emailGB = nullptr;
Kleo::NewCertificateUi::ListWidget *emailLW = nullptr;
QGroupBox *dnsGB = nullptr;
Kleo::NewCertificateUi::ListWidget *dnsLW = nullptr;
QGroupBox *uriGB = nullptr;
Kleo::NewCertificateUi::ListWidget *uriLW = nullptr;
QDialogButtonBox *buttonBox = nullptr;
UI(QDialog *parent)
{
parent->setWindowTitle(i18nc("@title:window", "Advanced Settings"));
auto mainLayout = new QVBoxLayout{parent};
tabWidget = new QTabWidget{parent};
{
auto technicalTab = new ScrollArea{tabWidget};
technicalTab->setFocusPolicy(Qt::NoFocus);
technicalTab->setFrameStyle(QFrame::NoFrame);
technicalTab->setBackgroundRole(parent->backgroundRole());
technicalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
technicalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
auto tabLayout = qobject_cast<QVBoxLayout *>(technicalTab->widget()->layout());
{
auto groupBox = new QGroupBox{i18nc("@title:group", "Key Material"), technicalTab};
auto groupBoxGrid = new QGridLayout{groupBox};
int row = 0;
rsaRB = new QRadioButton{i18nc("@option:radio", "RSA"), groupBox};
rsaRB->setChecked(false);
groupBoxGrid->addWidget(rsaRB, row, 0, 1, 2);
rsaKeyStrengthCB = new QComboBox{groupBox};
rsaKeyStrengthCB->setEnabled(false);
groupBoxGrid->addWidget(rsaKeyStrengthCB, row, 2, 1, 1);
row++;
auto subKeyIndentation = new QSpacerItem(13, 13, QSizePolicy::Fixed, QSizePolicy::Minimum);
groupBoxGrid->addItem(subKeyIndentation, row, 0, 1, 1);
rsaSubCB = new QCheckBox{i18nc("@option:check", "+ RSA"), groupBox};
rsaSubCB->setEnabled(true);
groupBoxGrid->addWidget(rsaSubCB, row, 1, 1, 1);
rsaKeyStrengthSubCB = new QComboBox{groupBox};
rsaKeyStrengthSubCB->setEnabled(false);
groupBoxGrid->addWidget(rsaKeyStrengthSubCB, row, 2, 1, 1);
row++;
dsaRB = new QRadioButton{i18nc("@option:radio", "DSA"), groupBox};
groupBoxGrid->addWidget(dsaRB, row, 0, 1, 2);
dsaKeyStrengthCB = new QComboBox{groupBox};
dsaKeyStrengthCB->setEnabled(false);
groupBoxGrid->addWidget(dsaKeyStrengthCB, row, 2, 1, 1);
row++;
elgCB = new QCheckBox{i18nc("@option:check", "+ Elgamal"), groupBox};
elgCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption."));
elgCB->setEnabled(true);
groupBoxGrid->addWidget(elgCB, row, 1, 1, 1);
elgKeyStrengthCB = new QComboBox{groupBox};
elgKeyStrengthCB->setEnabled(false);
groupBoxGrid->addWidget(elgKeyStrengthCB, row, 2, 1, 1);
row++;
ecdsaRB = new QRadioButton{i18nc("@option:radio", "ECDSA"), groupBox};
groupBoxGrid->addWidget(ecdsaRB, row, 0, 1, 2);
ecdsaKeyCurvesCB = new QComboBox{groupBox};
ecdsaKeyCurvesCB->setEnabled(false);
groupBoxGrid->addWidget(ecdsaKeyCurvesCB, row, 2, 1, 1);
row++;
ecdhCB = new QCheckBox{i18nc("@option:check", "+ ECDH"), groupBox};
ecdhCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption."));
ecdhCB->setEnabled(true);
groupBoxGrid->addWidget(ecdhCB, row, 1, 1, 1);
ecdhKeyCurvesCB = new QComboBox{groupBox};
ecdhKeyCurvesCB->setEnabled(false);
groupBoxGrid->addWidget(ecdhKeyCurvesCB, row, 2, 1, 1);
groupBoxGrid->setColumnStretch(3, 1);
tabLayout->addWidget(groupBox);
}
{
auto groupBox = new QGroupBox{i18nc("@title:group", "Certificate Usage"), technicalTab};
auto groupBoxGrid = new QGridLayout{groupBox};
int row = 0;
signingCB = new QCheckBox{i18nc("@option:check", "Signing"), groupBox};
signingCB->setChecked(true);
groupBoxGrid->addWidget(signingCB, row, 0, 1, 1);
certificationCB = new QCheckBox{i18nc("@option:check", "Certification"), groupBox};
groupBoxGrid->addWidget(certificationCB, row, 1, 1, 1);
row++;
encryptionCB = new QCheckBox{i18nc("@option:check", "Encryption"), groupBox};
encryptionCB->setChecked(true);
groupBoxGrid->addWidget(encryptionCB, row, 0, 1, 1);
authenticationCB = new QCheckBox{i18nc("@option:check", "Authentication"), groupBox};
groupBoxGrid->addWidget(authenticationCB, row, 1, 1, 1);
row++;
{
auto hbox = new QHBoxLayout;
expiryCB = new QCheckBox{i18nc("@option:check", "Valid until:"), groupBox};
hbox->addWidget(expiryCB);
expiryDE = new KDateComboBox(groupBox);
hbox->addWidget(expiryDE, 1);
groupBoxGrid->addLayout(hbox, row, 0, 1, 2);
}
tabLayout->addWidget(groupBox);
}
tabLayout->addStretch(1);
tabWidget->addTab(technicalTab, i18nc("@title:tab", "Technical Details"));
}
{
personalTab = new ScrollArea{tabWidget};
personalTab->setFocusPolicy(Qt::NoFocus);
personalTab->setFrameStyle(QFrame::NoFrame);
personalTab->setBackgroundRole(parent->backgroundRole());
personalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
personalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents);
auto scrollAreaLayout = qobject_cast<QVBoxLayout *>(personalTab->widget()->layout());
auto tabGrid = new QGridLayout;
uidGB = new QGroupBox{i18nc("@title:group", "Additional User IDs"), personalTab};
{
auto layout = new QVBoxLayout{uidGB};
uidLW = new Kleo::NewCertificateUi::ListWidget{uidGB};
layout->addWidget(uidLW);
}
tabGrid->addWidget(uidGB, 0, 0, 1, 2);
emailGB = new QGroupBox{i18nc("@title:group", "EMail Addresses"), personalTab};
{
auto layout = new QVBoxLayout{emailGB};
emailLW = new Kleo::NewCertificateUi::ListWidget{emailGB};
layout->addWidget(emailLW);
}
tabGrid->addWidget(emailGB, 2, 0, 2, 1);
dnsGB = new QGroupBox{i18nc("@title:group", "DNS Names"), personalTab};
{
auto layout = new QVBoxLayout{dnsGB};
dnsLW = new Kleo::NewCertificateUi::ListWidget{dnsGB};
layout->addWidget(dnsLW);
}
tabGrid->addWidget(dnsGB, 2, 1, 1, 1);
uriGB = new QGroupBox{i18nc("@title:group", "URIs"), personalTab};
{
auto layout = new QVBoxLayout{uriGB};
uriLW = new Kleo::NewCertificateUi::ListWidget{uriGB};
layout->addWidget(uriLW);
}
tabGrid->addWidget(uriGB, 3, 1, 1, 1);
scrollAreaLayout->addLayout(tabGrid);
tabWidget->addTab(personalTab, i18nc("@title:tab", "Personal Details"));
}
mainLayout->addWidget(tabWidget);
buttonBox = new QDialogButtonBox{parent};
buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
mainLayout->addWidget(buttonBox);
}
};
AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent)
: QDialog{parent}
, ui{new UI{this}}
, mECCSupported{engineIsVersion(2, 1, 0)}
, mEdDSASupported{engineIsVersion(2, 1, 15)}
{
qRegisterMetaType<Subkey::PubkeyAlgo>("Subkey::PubkeyAlgo");
Kleo::setUpExpirationDateComboBox(ui->expiryDE);
if (unlimitedValidityIsAllowed()) {
ui->expiryDE->setEnabled(ui->expiryCB->isChecked());
} else {
ui->expiryCB->setEnabled(false);
ui->expiryCB->setChecked(true);
}
ui->emailLW->setDefaultValue(i18n("new email"));
ui->dnsLW->setDefaultValue(i18n("new dns name"));
ui->uriLW->setDefaultValue(i18n("new uri"));
fillKeySizeComboBoxen();
connect(ui->rsaRB, &QAbstractButton::toggled, ui->rsaKeyStrengthCB, &QWidget::setEnabled);
connect(ui->rsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->rsaSubCB, &QAbstractButton::toggled, ui->rsaKeyStrengthSubCB, &QWidget::setEnabled);
connect(ui->rsaSubCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->dsaRB, &QAbstractButton::toggled, ui->dsaKeyStrengthCB, &QWidget::setEnabled);
connect(ui->dsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->elgCB, &QAbstractButton::toggled, ui->elgKeyStrengthCB, &QWidget::setEnabled);
connect(ui->elgCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->ecdsaRB, &QAbstractButton::toggled, ui->ecdsaKeyCurvesCB, &QWidget::setEnabled);
connect(ui->ecdsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->ecdhCB, &QAbstractButton::toggled, ui->ecdhKeyCurvesCB, &QWidget::setEnabled);
connect(ui->ecdhCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged);
connect(ui->signingCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotSigningAllowedToggled);
connect(ui->encryptionCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotEncryptionAllowedToggled);
connect(ui->expiryCB, &QAbstractButton::toggled,
this, [this](bool checked) {
ui->expiryDE->setEnabled(checked);
if (checked && !ui->expiryDE->isValid()) {
setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
}
});
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &AdvancedSettingsDialog::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
AdvancedSettingsDialog::~AdvancedSettingsDialog() = default;
bool AdvancedSettingsDialog::unlimitedValidityIsAllowed() const
{
return !Kleo::maximumExpirationDate().isValid();
}
void AdvancedSettingsDialog::setProtocol(GpgME::Protocol proto)
{
if (protocol == proto) {
return;
}
protocol = proto;
loadDefaults();
}
void AdvancedSettingsDialog::setAdditionalUserIDs(const QStringList &items)
{
ui->uidLW->setItems(items);
}
QStringList AdvancedSettingsDialog::additionalUserIDs() const
{
return ui->uidLW->items();
}
void AdvancedSettingsDialog::setAdditionalEMailAddresses(const QStringList &items)
{
ui->emailLW->setItems(items);
}
QStringList AdvancedSettingsDialog::additionalEMailAddresses() const
{
return ui->emailLW->items();
}
void AdvancedSettingsDialog::setDnsNames(const QStringList &items)
{
ui->dnsLW->setItems(items);
}
QStringList AdvancedSettingsDialog::dnsNames() const
{
return ui->dnsLW->items();
}
void AdvancedSettingsDialog::setUris(const QStringList &items)
{
ui->uriLW->setItems(items);
}
QStringList AdvancedSettingsDialog::uris() const
{
return ui->uriLW->items();
}
void AdvancedSettingsDialog::setKeyStrength(unsigned int strength)
{
set_keysize(ui->rsaKeyStrengthCB, strength);
set_keysize(ui->dsaKeyStrengthCB, strength);
}
unsigned int AdvancedSettingsDialog::keyStrength() const
{
return
ui->dsaRB->isChecked() ? get_keysize(ui->dsaKeyStrengthCB) :
ui->rsaRB->isChecked() ? get_keysize(ui->rsaKeyStrengthCB) : 0;
}
void AdvancedSettingsDialog::setKeyType(Subkey::PubkeyAlgo algo)
{
QRadioButton *const rb =
is_rsa(algo) ? ui->rsaRB :
is_dsa(algo) ? ui->dsaRB :
is_ecdsa(algo) || is_eddsa(algo) ? ui->ecdsaRB : nullptr;
if (rb) {
rb->setChecked(true);
}
}
Subkey::PubkeyAlgo AdvancedSettingsDialog::keyType() const
{
return
ui->dsaRB->isChecked() ? Subkey::AlgoDSA :
ui->rsaRB->isChecked() ? Subkey::AlgoRSA :
ui->ecdsaRB->isChecked() ?
ui->ecdsaKeyCurvesCB->currentText() == QLatin1String("ed25519") ? Subkey::AlgoEDDSA :
Subkey::AlgoECDSA :
Subkey::AlgoUnknown;
}
void AdvancedSettingsDialog::setKeyCurve(const QString &curve)
{
set_curve(ui->ecdsaKeyCurvesCB, curve);
}
QString AdvancedSettingsDialog::keyCurve() const
{
return get_curve(ui->ecdsaKeyCurvesCB);
}
void AdvancedSettingsDialog::setSubkeyType(Subkey::PubkeyAlgo algo)
{
ui->elgCB->setChecked(is_elg(algo));
ui->rsaSubCB->setChecked(is_rsa(algo));
ui->ecdhCB->setChecked(is_ecdh(algo));
}
Subkey::PubkeyAlgo AdvancedSettingsDialog::subkeyType() const
{
if (ui->elgCB->isChecked()) {
return Subkey::AlgoELG_E;
} else if (ui->rsaSubCB->isChecked()) {
return Subkey::AlgoRSA;
} else if (ui->ecdhCB->isChecked()) {
return Subkey::AlgoECDH;
}
return Subkey::AlgoUnknown;
}
void AdvancedSettingsDialog::setSubkeyCurve(const QString &curve)
{
set_curve(ui->ecdhKeyCurvesCB, curve);
}
QString AdvancedSettingsDialog::subkeyCurve() const
{
return get_curve(ui->ecdhKeyCurvesCB);
}
void AdvancedSettingsDialog::setSubkeyStrength(unsigned int strength)
{
if (subkeyType() == Subkey::AlgoRSA) {
set_keysize(ui->rsaKeyStrengthSubCB, strength);
} else {
set_keysize(ui->elgKeyStrengthCB, strength);
}
}
unsigned int AdvancedSettingsDialog::subkeyStrength() const
{
if (subkeyType() == Subkey::AlgoRSA) {
return get_keysize(ui->rsaKeyStrengthSubCB);
}
return get_keysize(ui->elgKeyStrengthCB);
}
void AdvancedSettingsDialog::setSigningAllowed(bool on)
{
ui->signingCB->setChecked(on);
}
bool AdvancedSettingsDialog::signingAllowed() const
{
return ui->signingCB->isChecked();
}
void AdvancedSettingsDialog::setEncryptionAllowed(bool on)
{
ui->encryptionCB->setChecked(on);
}
bool AdvancedSettingsDialog::encryptionAllowed() const
{
return ui->encryptionCB->isChecked();
}
void AdvancedSettingsDialog::setCertificationAllowed(bool on)
{
ui->certificationCB->setChecked(on);
}
bool AdvancedSettingsDialog::certificationAllowed() const
{
return ui->certificationCB->isChecked();
}
void AdvancedSettingsDialog::setAuthenticationAllowed(bool on)
{
ui->authenticationCB->setChecked(on);
}
bool AdvancedSettingsDialog::authenticationAllowed() const
{
return ui->authenticationCB->isChecked();
}
QDate AdvancedSettingsDialog::forceDateIntoAllowedRange(QDate date) const
{
const auto minDate = ui->expiryDE->minimumDate();
if (minDate.isValid() && date < minDate) {
date = minDate;
}
const auto maxDate = ui->expiryDE->maximumDate();
if (maxDate.isValid() && date > maxDate) {
date = maxDate;
}
return date;
}
void AdvancedSettingsDialog::setExpiryDate(QDate date)
{
if (date.isValid()) {
ui->expiryDE->setDate(forceDateIntoAllowedRange(date));
} else {
// check if unlimited validity is allowed
if (unlimitedValidityIsAllowed()) {
ui->expiryDE->setDate(date);
}
}
if (ui->expiryCB->isEnabled()) {
ui->expiryCB->setChecked(ui->expiryDE->isValid());
}
}
QDate AdvancedSettingsDialog::expiryDate() const
{
return ui->expiryCB->isChecked() ? ui->expiryDE->date() : QDate{};
}
void AdvancedSettingsDialog::slotKeyMaterialSelectionChanged()
{
const unsigned int algo = keyType();
const unsigned int sk_algo = subkeyType();
if (protocol == OpenPGP) {
// first update the enabled state, but only if key type is not forced
if (!keyTypeImmutable) {
ui->elgCB->setEnabled(is_dsa(algo));
ui->rsaSubCB->setEnabled(is_rsa(algo));
ui->ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo));
if (is_rsa(algo)) {
ui->encryptionCB->setEnabled(true);
ui->signingCB->setEnabled(true);
ui->authenticationCB->setEnabled(true);
if (is_rsa(sk_algo)) {
ui->encryptionCB->setEnabled(false);
} else {
ui->encryptionCB->setEnabled(true);
}
} else if (is_dsa(algo)) {
ui->encryptionCB->setEnabled(false);
} else if (is_ecdsa(algo) || is_eddsa(algo)) {
ui->signingCB->setEnabled(true);
ui->authenticationCB->setEnabled(true);
ui->encryptionCB->setEnabled(false);
}
}
// then update the checked state
if (sender() == ui->dsaRB || sender() == ui->rsaRB || sender() == ui->ecdsaRB) {
ui->elgCB->setChecked(is_dsa(algo));
ui->ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo));
ui->rsaSubCB->setChecked(is_rsa(algo));
}
if (is_rsa(algo)) {
ui->encryptionCB->setChecked(true);
ui->signingCB->setChecked(true);
if (is_rsa(sk_algo)) {
ui->encryptionCB->setChecked(true);
}
} else if (is_dsa(algo)) {
if (is_elg(sk_algo)) {
ui->encryptionCB->setChecked(true);
} else {
ui->encryptionCB->setChecked(false);
}
} else if (is_ecdsa(algo) || is_eddsa(algo)) {
ui->signingCB->setChecked(true);
ui->encryptionCB->setChecked(is_ecdh(sk_algo));
}
} else {
//assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all
}
}
void AdvancedSettingsDialog::slotSigningAllowedToggled(bool on)
{
if (!on && protocol == CMS && !encryptionAllowed()) {
setEncryptionAllowed(true);
}
}
void AdvancedSettingsDialog::slotEncryptionAllowedToggled(bool on)
{
if (!on && protocol == CMS && !signingAllowed()) {
setSigningAllowed(true);
}
}
static void fill_combobox(QComboBox &cb, const QList<int> &sizes, const QStringList &labels)
{
cb.clear();
for (int i = 0, end = sizes.size(); i != end; ++i) {
const int size = std::abs(sizes[i]);
/* As we respect the defaults configurable in GnuPG, and we also have configurable
* defaults in Kleopatra its difficult to print out "default" here. To avoid confusion
* about that its better not to show any default indication. */
cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty()
? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", size, labels[i].trimmed())
: i18ncp("%1: key size in bits", "1 bit", "%1 bits", size),
size);
if (sizes[i] < 0) {
cb.setCurrentIndex(cb.count() - 1);
}
}
}
void AdvancedSettingsDialog::fillKeySizeComboBoxen()
{
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
QList<int> rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList<int>() << 2048 << -3072 << 4096);
if (DeVSCompliance::isActive()) {
rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList<int>() << -3072 << 4096);
}
const QList<int> dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList<int>() << -2048);
const QList<int> elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList<int>() << -2048 << 3072 << 4096);
const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList());
const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList());
const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList());
fill_combobox(*ui->rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels);
fill_combobox(*ui->rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels);
fill_combobox(*ui->dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels);
fill_combobox(*ui->elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels);
if (mEdDSASupported) {
// If supported we recommend cv25519
ui->ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519"));
ui->ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519"));
}
ui->ecdhKeyCurvesCB->addItems(curveNames);
ui->ecdsaKeyCurvesCB->addItems(curveNames);
}
// Try to load the default key type from GnuPG
void AdvancedSettingsDialog::loadDefaultGnuPGKeyType()
{
const auto conf = QGpgME::cryptoConfig();
if (!conf) {
qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig.";
return;
}
const auto entry = getCryptoConfigEntry(conf, protocol == CMS ? "gpgsm" : "gpg", "default_pubkey_algo");
if (!entry) {
qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA";
setKeyType(Subkey::AlgoRSA);
setSubkeyType(Subkey::AlgoRSA);
return;
}
qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue();
// Format is <primarytype>[/usage]+<subkeytype>[/usage]
const auto split = entry->stringValue().split(QLatin1Char('+'));
int size = 0;
Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown;
QString curve;
parseAlgoString(split[0], &size, &algo, curve);
if (algo == Subkey::AlgoUnknown) {
setSubkeyType(Subkey::AlgoRSA);
return;
}
setKeyType(algo);
if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) {
setKeyStrength(size);
} else {
setKeyCurve(curve);
}
{
auto algoString = (split.size() == 2) ? split[1] : split[0];
// If it has no usage we assume encrypt subkey
if (!algoString.contains(QLatin1Char('/'))) {
algoString += QStringLiteral("/enc");
}
parseAlgoString(algoString, &size, &algo, curve);
if (algo == Subkey::AlgoUnknown) {
setSubkeyType(Subkey::AlgoRSA);
return;
}
setSubkeyType(algo);
if (is_rsa(algo) || is_elg(algo)) {
setSubkeyStrength(size);
} else {
setSubkeyCurve(curve);
}
}
}
void AdvancedSettingsDialog::loadDefaultKeyType()
{
if (protocol != CMS && protocol != OpenPGP) {
return;
}
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY);
const QString keyType = config.readEntry(entry).trimmed().toUpper();
if (protocol == OpenPGP && keyType == QLatin1String("DSA")) {
setKeyType(Subkey::AlgoDSA);
setSubkeyType(Subkey::AlgoUnknown);
} else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) {
setKeyType(Subkey::AlgoDSA);
setSubkeyType(Subkey::AlgoELG_E);
} else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) {
loadDefaultGnuPGKeyType();
} else {
if (!keyType.isEmpty() && keyType != QLatin1String("RSA"))
qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType)
<< "\" for entry \"[CertificateCreationWizard]"
<< qPrintable(entry) << "\"";
setKeyType(Subkey::AlgoRSA);
setSubkeyType(Subkey::AlgoRSA);
}
keyTypeImmutable = config.isEntryImmutable(entry);
}
void AdvancedSettingsDialog::loadDefaultExpiration()
{
if (protocol != OpenPGP) {
return;
}
if (unlimitedValidityIsAllowed()) {
setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::NoExpiration));
} else {
setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration));
}
}
void AdvancedSettingsDialog::loadDefaults()
{
loadDefaultKeyType();
loadDefaultExpiration();
updateWidgetVisibility();
}
void AdvancedSettingsDialog::updateWidgetVisibility()
{
// Personal Details Page
if (protocol == OpenPGP) { // ### hide until multi-uid is implemented
if (ui->tabWidget->indexOf(ui->personalTab) != -1) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->personalTab));
}
} else {
if (ui->tabWidget->indexOf(ui->personalTab) == -1) {
ui->tabWidget->addTab(ui->personalTab, i18nc("@title:tab", "Personal Details"));
}
}
ui->uidGB->setVisible(protocol == OpenPGP);
ui->uidGB->setEnabled(false);
ui->uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one user ID is not yet implemented."));
ui->emailGB->setVisible(protocol == CMS);
ui->dnsGB->setVisible(protocol == CMS);
ui->uriGB->setVisible(protocol == CMS);
// Technical Details Page
ui->ecdhCB->setVisible(mECCSupported);
ui->ecdhKeyCurvesCB->setVisible(mECCSupported);
ui->ecdsaKeyCurvesCB->setVisible(mECCSupported);
ui->ecdsaRB->setVisible(mECCSupported);
if (mEdDSASupported) {
// We use the same radio button for EdDSA as we use for
// ECDSA GnuPG does the same and this is really super technical
// land.
ui->ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA"));
}
const bool deVsHack = DeVSCompliance::isActive();
if (deVsHack) {
// GnuPG Provides no API to query which keys are compliant for
// a mode. If we request a different one it will error out so
// we have to remove the options.
//
// Does anyone want to use NIST anyway?
int i;
while ((i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 ||
(i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) {
ui->ecdsaKeyCurvesCB->removeItem(i);
}
while ((i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 ||
(i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) {
ui->ecdhKeyCurvesCB->removeItem(i);
}
}
ui->certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation?
ui->authenticationCB->setVisible(protocol == OpenPGP);
if (keyTypeImmutable) {
ui->rsaRB->setEnabled(false);
ui->rsaSubCB->setEnabled(false);
ui->dsaRB->setEnabled(false);
ui->elgCB->setEnabled(false);
ui->ecdsaRB->setEnabled(false);
ui->ecdhCB->setEnabled(false);
// force usage if key type is forced
ui->certificationCB->setEnabled(false);
ui->signingCB->setEnabled(false);
ui->encryptionCB->setEnabled(false);
ui->authenticationCB->setEnabled(false);
} else {
ui->rsaRB->setEnabled(true);
ui->rsaSubCB->setEnabled(protocol == OpenPGP);
ui->dsaRB->setEnabled(protocol == OpenPGP && !deVsHack);
ui->elgCB->setEnabled(protocol == OpenPGP && !deVsHack);
ui->ecdsaRB->setEnabled(protocol == OpenPGP);
ui->ecdhCB->setEnabled(protocol == OpenPGP);
if (protocol == OpenPGP) {
// OpenPGP keys must have certify capability
ui->certificationCB->setEnabled(false);
}
if (protocol == CMS) {
ui->encryptionCB->setEnabled(true);
ui->rsaKeyStrengthSubCB->setEnabled(false);
}
}
if (protocol == OpenPGP) {
// OpenPGP keys must have certify capability
ui->certificationCB->setChecked(true);
}
if (protocol == CMS) {
ui->rsaSubCB->setChecked(false);
}
ui->expiryDE->setVisible(protocol == OpenPGP);
ui->expiryCB->setVisible(protocol == OpenPGP);
slotKeyMaterialSelectionChanged();
}
void AdvancedSettingsDialog::setInitialFocus()
{
// first try the key type radio buttons
if (focusFirstCheckedButton({ui->rsaRB, ui->dsaRB, ui->ecdsaRB})) {
return;
}
// then try the usage check boxes and the expiration check box
if (focusFirstEnabledButton({ui->signingCB, ui->certificationCB, ui->encryptionCB, ui->authenticationCB, ui->expiryCB})) {
return;
}
// finally, focus the OK button
ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
}
void AdvancedSettingsDialog::accept()
{
if (!Kleo::isValidExpirationDate(expiryDate())) {
KMessageBox::error(this, i18nc("@info", "Error: %1", Kleo::validityPeriodHint()));
return;
}
QDialog::accept();
}
void AdvancedSettingsDialog::showEvent(QShowEvent *event)
{
if (isFirstShowEvent) {
setInitialFocus();
isFirstShowEvent = false;
}
QDialog::showEvent(event);
}
diff --git a/src/smartcard/openpgpcard.cpp b/src/smartcard/openpgpcard.cpp
index 8652fcbfc..5e24b1076 100644
--- a/src/smartcard/openpgpcard.cpp
+++ b/src/smartcard/openpgpcard.cpp
@@ -1,207 +1,207 @@
/* smartcard/openpgpcard.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-FileCopyrightText: 2020, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/* Code in this file is partly based on the GNU Privacy Assistant
* (cm-openpgp.c) git rev. 0a78795146661234070681737b3e08228616441f
*
* Whis is:
* SPDX-FileCopyrightText: 2008, 2009 g 10 Code GmbH
*
* And may be licensed under the GNU General Public License
* as published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*/
#include "openpgpcard.h"
#include "algorithminfo.h"
#include <Libkleo/Algorithm>
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
static QDebug operator<<(QDebug s, const std::string &string)
{
return s << QString::fromStdString(string);
}
#endif
// static
const std::string OpenPGPCard::AppName = "openpgp";
OpenPGPCard::OpenPGPCard(const Card &card)
: Card(card)
{
setAppName(AppName);
setInitialKeyInfos(OpenPGPCard::supportedKeys());
}
// static
std::string OpenPGPCard::pgpSigKeyRef()
{
return std::string("OPENPGP.1");
}
// static
std::string OpenPGPCard::pgpEncKeyRef()
{
return std::string("OPENPGP.2");
}
// static
std::string OpenPGPCard::pgpAuthKeyRef()
{
return std::string("OPENPGP.3");
}
// static
std::string OpenPGPCard::pinKeyRef()
{
return std::string("OPENPGP.1");
}
// static
std::string OpenPGPCard::adminPinKeyRef()
{
return std::string("OPENPGP.3");
}
// static
std::string OpenPGPCard::resetCodeKeyRef()
{
return std::string("OPENPGP.2");
}
// static
const std::vector<KeyPairInfo> & OpenPGPCard::supportedKeys()
{
static const std::vector<KeyPairInfo> keyInfos = {
{OpenPGPCard::pgpSigKeyRef(), "", "sc", "", ""},
{OpenPGPCard::pgpEncKeyRef(), "", "e", "", ""},
- {OpenPGPCard::pgpAuthKeyRef(), "", "a", "", ""}
+ {OpenPGPCard::pgpAuthKeyRef(), "", "a", "", ""},
};
return keyInfos;
}
// static
QString OpenPGPCard::keyDisplayName(const std::string &keyRef)
{
static const QMap<std::string, QString> displayNames = {
{ OpenPGPCard::pgpSigKeyRef(), i18n("Signature") },
{ OpenPGPCard::pgpEncKeyRef(), i18n("Encryption") },
- { OpenPGPCard::pgpAuthKeyRef(), i18n("Authentication") }
+ { OpenPGPCard::pgpAuthKeyRef(), i18n("Authentication") },
};
return displayNames.value(keyRef);
}
// static
std::string OpenPGPCard::getAlgorithmName(const std::string &algorithm, const std::string &keyRef)
{
static const std::map<std::string, std::string> ecdhAlgorithmMapping = {
{ "curve25519", "cv25519" },
{ "curve448", "cv448" },
};
static const std::map<std::string, std::string> eddsaAlgorithmMapping = {
{ "curve25519", "ed25519" },
{ "curve448", "ed448" },
};
if (keyRef == OpenPGPCard::pgpEncKeyRef()) {
const auto it = ecdhAlgorithmMapping.find(algorithm);
if (it != ecdhAlgorithmMapping.end()) {
return it->second;
}
} else {
const auto it = eddsaAlgorithmMapping.find(algorithm);
if (it != eddsaAlgorithmMapping.end()) {
return it->second;
}
}
return algorithm;
}
void OpenPGPCard::setSupportedAlgorithms(const std::vector<std::string> &algorithms)
{
static const std::vector<std::string> allowedAlgorithms = {
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"curve25519",
"curve448",
"nistp256",
"nistp384",
"nistp521",
"rsa2048",
"rsa3072",
"rsa4096",
};
// Curve secp256k1 is explicitly ignored
static const std::vector<std::string> ignoredAlgorithms = {
"secp256k1",
};
mAlgorithms.clear();
std::copy_if(algorithms.begin(), algorithms.end(), std::back_inserter(mAlgorithms), [](const auto &algo) {
return Kleo::contains(allowedAlgorithms, algo);
});
if (mAlgorithms.size() < algorithms.size()) {
std::vector<std::string> unsupportedAlgos;
std::copy_if(algorithms.begin(), algorithms.end(), std::back_inserter(unsupportedAlgos), [](const auto &algo) {
return !Kleo::contains(ignoredAlgorithms, algo) && !Kleo::contains(allowedAlgorithms, algo);
});
if (unsupportedAlgos.size() > 0) {
qWarning(KLEOPATRA_LOG).nospace() << "OpenPGPCard::" << __func__ << " Unsupported algorithms: " << unsupportedAlgos
<< " (supported: " << allowedAlgorithms << ")";
}
}
}
std::string OpenPGPCard::pubkeyUrl() const
{
return cardInfo("PUBKEY-URL");
}
std::vector<AlgorithmInfo> OpenPGPCard::supportedAlgorithms() const
{
static const std::map<std::string, QString> displayNames = {
{ "brainpoolP256r1", i18nc("@info", "ECC (Brainpool P-256)") },
{ "brainpoolP384r1", i18nc("@info", "ECC (Brainpool P-384)") },
{ "brainpoolP512r1", i18nc("@info", "ECC (Brainpool P-512)") },
{ "curve25519", i18nc("@info", "ECC (Curve25519)") },
{ "curve448", i18nc("@info", "ECC (Curve448)") },
{ "nistp256", i18nc("@info", "ECC (NIST P-256)") },
{ "nistp384", i18nc("@info", "ECC (NIST P-384)") },
{ "nistp521", i18nc("@info", "ECC (NIST P-521)") },
{ "rsa2048", i18nc("@info", "RSA 2048") },
{ "rsa3072", i18nc("@info", "RSA 3072") },
{ "rsa4096", i18nc("@info", "RSA 4096") },
};
std::vector<AlgorithmInfo> algos;
std::transform(mAlgorithms.cbegin(), mAlgorithms.cend(), std::back_inserter(algos), [](const auto &algo) {
return AlgorithmInfo{algo, displayNames.at(algo)};
});
return algos;
}
std::string OpenPGPCard::defaultAlgorithm() const
{
if (Kleo::contains(mAlgorithms, "curve25519")) {
return "curve25519";
}
// we assume that all OpenPGP smart cards support at least RSA with 2048 bits
return "rsa2048";
}
diff --git a/src/smartcard/pivcard.cpp b/src/smartcard/pivcard.cpp
index 95a44732d..f6c8fc0b4 100644
--- a/src/smartcard/pivcard.cpp
+++ b/src/smartcard/pivcard.cpp
@@ -1,126 +1,126 @@
/* smartcard/pivcard.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pivcard.h"
#include "algorithminfo.h"
#include "keypairinfo.h"
#include <KLocalizedString>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
// static
const std::string PIVCard::AppName = "piv";
PIVCard::PIVCard(const Card &card)
: Card(card)
{
setAppName(AppName);
setInitialKeyInfos(PIVCard::supportedKeys());
}
// static
std::string PIVCard::pivAuthenticationKeyRef()
{
return std::string("PIV.9A");
}
// static
std::string PIVCard::cardAuthenticationKeyRef()
{
return std::string("PIV.9E");
}
// static
std::string PIVCard::digitalSignatureKeyRef()
{
return std::string("PIV.9C");
}
// static
std::string PIVCard::keyManagementKeyRef()
{
return std::string("PIV.9D");
}
// static
std::string PIVCard::pinKeyRef()
{
return std::string("PIV.80");
}
// static
std::string PIVCard::pukKeyRef()
{
return std::string("PIV.81");
}
// static
const std::vector<KeyPairInfo> & PIVCard::supportedKeys()
{
static const std::vector<KeyPairInfo> keyInfos = {
{PIVCard::pivAuthenticationKeyRef(), "", "a", "", ""},
{PIVCard::cardAuthenticationKeyRef(), "", "a", "", ""},
{PIVCard::digitalSignatureKeyRef(), "", "sc", "", ""},
- {PIVCard::keyManagementKeyRef(), "", "e", "", ""}
+ {PIVCard::keyManagementKeyRef(), "", "e", "", ""},
};
return keyInfos;
}
// static
QString PIVCard::keyDisplayName(const std::string &keyRef)
{
static const QMap<std::string, QString> displayNames = {
{ PIVCard::pivAuthenticationKeyRef(), i18n("PIV Authentication Key") },
{ PIVCard::cardAuthenticationKeyRef(), i18n("Card Authentication Key") },
{ PIVCard::digitalSignatureKeyRef(), i18n("Digital Signature Key") },
{ PIVCard::keyManagementKeyRef(), i18n("Key Management Key") },
};
return displayNames.value(keyRef);
}
// static
std::vector<AlgorithmInfo> PIVCard::supportedAlgorithms(const std::string &keyRef)
{
if (keyRef == PIVCard::keyManagementKeyRef()) {
return {
{ "rsa2048", i18n("RSA key transport (2048 bits)") },
{ "nistp256", i18n("ECDH (Curve P-256)") },
- { "nistp384", i18n("ECDH (Curve P-384)") }
+ { "nistp384", i18n("ECDH (Curve P-384)") },
};
} else if (keyRef == PIVCard::digitalSignatureKeyRef()) {
return {
{ "rsa2048", i18n("RSA (2048 bits)") },
{ "nistp256", i18n("ECDSA (Curve P-256)") },
- { "nistp384", i18n("ECDSA (Curve P-384)") }
+ { "nistp384", i18n("ECDSA (Curve P-384)") },
};
}
// NIST SP 800-78-4 does not allow Curve P-384 for PIV Authentication key or Card Authentication key
return {
{ "rsa2048", i18n("RSA (2048 bits)") },
{ "nistp256", i18n("ECDSA (Curve P-256)") },
};
}
std::string PIVCard::certificateData(const std::string &keyRef) const
{
return cardInfo("KLEO-CERTIFICATE-" + keyRef);
}
void PIVCard::setCertificateData(const std::string &keyRef, const std::string &data)
{
addCardInfo("KLEO-CERTIFICATE-" + keyRef, data);
}
diff --git a/src/uiserver/assuancommand.h b/src/uiserver/assuancommand.h
index a0296930d..7ff0b5aff 100644
--- a/src/uiserver/assuancommand.h
+++ b/src/uiserver/assuancommand.h
@@ -1,370 +1,370 @@
/* -*- mode: c++; c-basic-offset:4 -*-
uiserver/assuancommand.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 <utils/pimpl_ptr.h>
#include <utils/types.h>
#include <gpgme++/global.h>
#include <gpgme++/error.h>
#include <gpg-error.h>
#include <KMime/HeaderParsing>
#include <qwindowdefs.h> // for WId
#include <memory>
#include <string>
#include <map>
#include <vector>
class QVariant;
class QObject;
#include <QStringList>
struct assuan_context_s;
namespace Kleo
{
class Input;
class Output;
class AssuanCommandFactory;
/*!
\brief Base class for GnuPG UI Server commands
\note large parts of this are outdated by now!
<h3>Implementing a new AssuanCommand</h3>
You do not directly inherit AssuanCommand, unless you want to
deal with implementing low-level, repetitive things like name()
in terms of staticName(). Assuming you don't, then you inherit
your command class from AssuanCommandMixin, passing your class
as the template argument to AssuanCommandMixin, like this:
\code
class MyFooCommand : public AssuanCommandMixin<MyFooCommand> {
\endcode
(http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
You then choose a command name, and return that from the static
method staticName(), which is by convention queried by both
AssuanCommandMixin<> and GenericAssuanCommandFactory<>:
\code
static const char * staticName() { return "MYFOO"; }
\endcode
The string should be all-uppercase by convention, but the
UiServer implementation doesn't enforce this.
The next step is to implement start(), the starting point of
command execution:
<h3>Executing the command</h3>
\code
int start( const std::string & line ) {
\endcode
This should set everything up and check the parameters in \a
line and any options this command understands. If there's an
error, choose one of the gpg-error codes and create a
gpg_error_t from it using the protected makeError() function:
\code
return makeError( GPG_ERR_NOT_IMPLEMENTED );
\endcode
But usually, you will want to create a dialog, or call some
GpgME function from here. In case of errors from GpgME, you
shouldn't pipe them through makeError(), but return them
as-is. This will preserve the error source. Error created using
makeError() will have Kleopatra as their error source, so watch
out what you're doing :)
In addition to options and the command line, your command might
require <em>bulk data</em> input or output. That's what the bulk
input and output channels are for. You can check whether the
client handed you an input channel by checking that
bulkInputDevice() isn't NULL, likewise for bulkOutputDevice().
If everything is ok, you return 0. This indicates to the client
that the command has been accepted and is now in progress.
In this mode (start() returned 0), there are a bunch of options
for your command to do. Some commands may require additional
information from the client. The options passed to start() are
designed to be persistent across commands, and rather limited in
length (there's a strict line length limit in the assuan
protocol with no line continuation mechanism). The same is true
for command line arguments, which, in addition, you have to
parse yourself. Those usually apply only to this command, and
not to following ones.
If you need data that might be larger than the line length
limit, you can either expect it on the bulkInputDevice(), or, if
you have the need for more than one such data channel, or the
data is optional or conditional on some condition that can only
be determined during command execution, you can \em inquire the
missing information from the client.
As an example, a VERIFY command would expect the signed data on
the bulkInputDevice(). But if the input stream doesn't contain
an embedded (opaque) signature, indicating a \em detached
signature, it would go and inquire that data from the
client. Here's how it works:
\code
const int err = inquire( "DETACHED_SIGNATURE",
this, SLOT(slotDetachedSignature(int,QByteArray,QByteArray)) );
if ( err )
done( err );
\endcode
This should be self-explanatory: You give a slot to call when
the data has arrived. The slot's first argument is an error
code. The second the data (if any), and the third is just
repeating what you gave as inquire()'s first argument. As usual,
you can leave argument off of the end, if you are not interested
in them.
You can do as many inquiries as you want, but only one at a
time.
You should periodically send status updates to the client. You do
that by calling sendStatus().
Once your command has finished executing, call done(). If it's
with an error code, call done(err) like above. <b>Do not
forget to call done() when done!</b>. It will close
bulkInputDevice(), bulkOutputDevice(), and send an OK or ERR
message back to the client.
At that point, your command has finished executing, and a new
one can be accepted, or the connection closed.
Apropos connection closed. The only way for the client to cancel
an operation is to shut down the connection. In this case, the
canceled() function will be called. At that point, the
connection to the client will have been broken already, and all
you can do is pack your things and go down gracefully.
If _you_ detect that the user has canceled (your dialog contains
a cancel button, doesn't it?), then you should instead call
done( GPG_ERR_CANCELED ), like for normal operation.
<h3>Registering the command with UiServer</h3>
To register a command, you implement a AssuanCommandFactory for
your AssuanCommand subclass, and register it with the
UiServer. This can be made considerably easier using
GenericAssuanCommandFactory:
\code
UiServer server;
server.registerCommandFactory( shared_ptr<AssuanCommandFactory>( new GenericAssuanCommandFactory<MyFooCommand> ) );
// more registerCommandFactory calls...
server.start();
\endcode
*/
class AssuanCommand : public ExecutionContext, public std::enable_shared_from_this<AssuanCommand>
{
// defined in assuanserverconnection.cpp!
public:
AssuanCommand();
~AssuanCommand() override;
int start();
void canceled();
virtual const char *name() const = 0;
class Memento
{
public:
virtual ~Memento() {}
};
template <typename T>
class TypedMemento : public Memento
{
T m_t;
public:
explicit TypedMemento(const T &t) : m_t(t) {}
const T &get() const
{
return m_t;
}
T &get()
{
return m_t;
}
};
template <typename T>
static std::shared_ptr< TypedMemento<T> > make_typed_memento(const T &t)
{
return std::shared_ptr< TypedMemento<T> >(new TypedMemento<T>(t));
}
static int makeError(int code);
// convenience methods:
enum Mode { NoMode, EMail, FileManager };
Mode checkMode() const;
enum CheckProtocolOption {
- AllowProtocolMissing = 0x01
+ AllowProtocolMissing = 0x01,
};
GpgME::Protocol checkProtocol(Mode mode, int options = 0) const;
void applyWindowID(QWidget *w) const override
{
doApplyWindowID(w);
}
WId parentWId() const;
void setNohup(bool on);
bool isNohup() const;
bool isDone() const;
QString sessionTitle() const;
unsigned int sessionId() const;
bool informativeRecipients() const;
bool informativeSenders() const;
const std::vector<KMime::Types::Mailbox> &recipients() const;
const std::vector<KMime::Types::Mailbox> &senders() const;
bool hasMemento(const QByteArray &tag) const;
std::shared_ptr<Memento> memento(const QByteArray &tag) const;
template <typename T>
std::shared_ptr<T> mementoAs(const QByteArray &tag) const
{
return std::dynamic_pointer_cast<T>(this->memento(tag));
}
QByteArray registerMemento(const std::shared_ptr<Memento> &mem);
QByteArray registerMemento(const QByteArray &tag, const std::shared_ptr<Memento> &mem);
void removeMemento(const QByteArray &tag);
template <typename T>
T mementoContent(const QByteArray &tag) const
{
if (std::shared_ptr< TypedMemento<T> > m = mementoAs< TypedMemento<T> >(tag)) {
return m->get();
} else {
return T();
}
}
bool hasOption(const char *opt) const;
QVariant option(const char *opt) const;
const std::map<std::string, QVariant> &options() const;
const std::vector< std::shared_ptr<Input> > &inputs() const;
const std::vector< std::shared_ptr<Input> > &messages() const;
const std::vector< std::shared_ptr<Output> > &outputs() const;
QStringList fileNames() const;
unsigned int numFiles() const;
void sendStatus(const char *keyword, const QString &text);
void sendStatusEncoded(const char *keyword, const std::string &text);
void sendData(const QByteArray &data, bool moreToCome = false);
int inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize = 0);
void done(const GpgME::Error &err = GpgME::Error());
void done(const GpgME::Error &err, const QString &details);
void done(int err)
{
done(GpgME::Error(err));
}
void done(int err, const QString &details)
{
done(GpgME::Error(err), details);
}
private:
virtual void doCanceled() = 0;
virtual int doStart() = 0;
private:
void doApplyWindowID(QWidget *w) const;
private:
const std::map< QByteArray, std::shared_ptr<Memento> > &mementos() const;
private:
friend class ::Kleo::AssuanCommandFactory;
class Private;
kdtools::pimpl_ptr<Private> d;
};
class AssuanCommandFactory
{
public:
virtual ~AssuanCommandFactory() {}
virtual std::shared_ptr<AssuanCommand> create() const = 0;
virtual const char *name() const = 0;
using _Handler = gpg_error_t (*)(assuan_context_s *, char *);
virtual _Handler _handler() const = 0;
static gpg_error_t _handle(assuan_context_s *, char *, const char *);
};
template <typename Command>
class GenericAssuanCommandFactory : public AssuanCommandFactory
{
AssuanCommandFactory::_Handler _handler() const override
{
return &GenericAssuanCommandFactory::_handle;
}
static gpg_error_t _handle(assuan_context_s *_ctx, char *_line)
{
return AssuanCommandFactory::_handle(_ctx, _line, Command::staticName());
}
std::shared_ptr<AssuanCommand> create() const override
{
return make();
}
const char *name() const override
{
return Command::staticName();
}
public:
static std::shared_ptr<Command> make()
{
return std::shared_ptr<Command>(new Command);
}
};
template <typename Derived, typename Base = AssuanCommand>
class AssuanCommandMixin : public Base
{
protected:
/* reimp */ const char *name() const override
{
return Derived::staticName();
}
};
}
diff --git a/src/utils/clipboardmenu.cpp b/src/utils/clipboardmenu.cpp
index 05d2b92ac..412279119 100644
--- a/src/utils/clipboardmenu.cpp
+++ b/src/utils/clipboardmenu.cpp
@@ -1,150 +1,150 @@
/*
SPDX-FileCopyrightText: 2014-2021 Laurent Montel <montel@kde.org>
SPDX-License-Identifier: GPL-2.0-only
*/
#include <config-kleopatra.h>
#include "clipboardmenu.h"
#include "kdtoolsglobal.h"
#include "mainwindow.h"
#include <settings.h>
#include <commands/importcertificatefromclipboardcommand.h>
#include <commands/encryptclipboardcommand.h>
#include <commands/signclipboardcommand.h>
#include <commands/decryptverifyclipboardcommand.h>
#include <Libkleo/Algorithm>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <KActionMenu>
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QSignalBlocker>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Commands;
ClipboardMenu::ClipboardMenu(QObject *parent)
: QObject{parent}
{
mClipboardMenu = new KActionMenu(i18n("Clipboard"), this);
mImportClipboardAction = new QAction(i18n("Certificate Import"), this);
mEncryptClipboardAction = new QAction(i18n("Encrypt..."), this);
const Kleo::Settings settings{};
if (settings.cmsEnabled() && settings.cmsSigningAllowed()) {
mSmimeSignClipboardAction = new QAction(i18n("S/MIME-Sign..."), this);
}
mOpenPGPSignClipboardAction = new QAction(i18n("OpenPGP-Sign..."), this);
mDecryptVerifyClipboardAction = new QAction(i18n("Decrypt/Verify..."), this);
KDAB_SET_OBJECT_NAME(mClipboardMenu);
KDAB_SET_OBJECT_NAME(mImportClipboardAction);
KDAB_SET_OBJECT_NAME(mEncryptClipboardAction);
KDAB_SET_OBJECT_NAME(mSmimeSignClipboardAction);
KDAB_SET_OBJECT_NAME(mOpenPGPSignClipboardAction);
KDAB_SET_OBJECT_NAME(mDecryptVerifyClipboardAction);
connect(mImportClipboardAction, &QAction::triggered, this, &ClipboardMenu::slotImportClipboard);
connect(mEncryptClipboardAction, &QAction::triggered, this, &ClipboardMenu::slotEncryptClipboard);
if (mSmimeSignClipboardAction) {
connect(mSmimeSignClipboardAction, &QAction::triggered, this, &ClipboardMenu::slotSMIMESignClipboard);
}
connect(mOpenPGPSignClipboardAction, &QAction::triggered, this, &ClipboardMenu::slotOpenPGPSignClipboard);
connect(mDecryptVerifyClipboardAction, &QAction::triggered, this, &ClipboardMenu::slotDecryptVerifyClipboard);
mClipboardMenu->addAction(mImportClipboardAction);
mClipboardMenu->addAction(mEncryptClipboardAction);
if (mSmimeSignClipboardAction) {
mClipboardMenu->addAction(mSmimeSignClipboardAction);
}
mClipboardMenu->addAction(mOpenPGPSignClipboardAction);
mClipboardMenu->addAction(mDecryptVerifyClipboardAction);
connect(QApplication::clipboard(), &QClipboard::changed, this, &ClipboardMenu::slotEnableDisableActions);
connect(KeyCache::instance().get(), &KeyCache::keyListingDone, this, &ClipboardMenu::slotEnableDisableActions);
slotEnableDisableActions();
}
ClipboardMenu::~ClipboardMenu() = default;
void ClipboardMenu::setMainWindow(MainWindow *window)
{
mWindow = window;
}
KActionMenu *ClipboardMenu::clipboardMenu() const
{
return mClipboardMenu;
}
void ClipboardMenu::startCommand(Command *cmd)
{
Q_ASSERT(cmd);
cmd->setParent(mWindow);
cmd->start();
}
void ClipboardMenu::slotImportClipboard()
{
startCommand(new ImportCertificateFromClipboardCommand(nullptr));
}
void ClipboardMenu::slotEncryptClipboard()
{
startCommand(new EncryptClipboardCommand(nullptr));
}
void ClipboardMenu::slotOpenPGPSignClipboard()
{
startCommand(new SignClipboardCommand(GpgME::OpenPGP, nullptr));
}
void ClipboardMenu::slotSMIMESignClipboard()
{
startCommand(new SignClipboardCommand(GpgME::CMS, nullptr));
}
void ClipboardMenu::slotDecryptVerifyClipboard()
{
startCommand(new DecryptVerifyClipboardCommand(nullptr));
}
namespace
{
bool hasSigningKeys(GpgME::Protocol protocol) {
if (!KeyCache::instance()->initialized()) {
return false;
}
return Kleo::any_of(KeyCache::instance()->keys(),
[protocol](const auto &k) {
#if GPGMEPP_KEY_CANSIGN_IS_FIXED
- return k.hasSecret() && k.canSign() && (k.protocol() == protocol);
+ return k.hasSecret() && k.canSign() && (k.protocol() == protocol);
#else
- return k.hasSecret() && k.canReallySign() && (k.protocol() == protocol);
+ return k.hasSecret() && k.canReallySign() && (k.protocol() == protocol);
#endif
});
}
}
void ClipboardMenu::slotEnableDisableActions()
{
const QSignalBlocker blocker(QApplication::clipboard());
mImportClipboardAction->setEnabled(ImportCertificateFromClipboardCommand::canImportCurrentClipboard());
mEncryptClipboardAction->setEnabled(EncryptClipboardCommand::canEncryptCurrentClipboard());
mOpenPGPSignClipboardAction->setEnabled(SignClipboardCommand::canSignCurrentClipboard() && hasSigningKeys(GpgME::OpenPGP));
if (mSmimeSignClipboardAction) {
mSmimeSignClipboardAction->setEnabled(SignClipboardCommand::canSignCurrentClipboard() && hasSigningKeys(GpgME::CMS));
}
mDecryptVerifyClipboardAction->setEnabled(DecryptVerifyClipboardCommand::canDecryptVerifyCurrentClipboard());
}
diff --git a/src/utils/debug-helpers.cpp b/src/utils/debug-helpers.cpp
index 7128e0bfb..c8c861757 100644
--- a/src/utils/debug-helpers.cpp
+++ b/src/utils/debug-helpers.cpp
@@ -1,145 +1,144 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/debug-helpers.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 "debug-helpers.h"
#include <QWidget>
#include <kleopatra_debug.h>
static QString indentByWidgetDepth(const QWidget *w)
{
int indent = 0;
for (; !w->isWindow(); w = w->parentWidget()) {
indent += 2;
}
return QString{indent, QLatin1Char(' ')};
}
static QWidget *deepestFocusProxy(const QWidget *w)
{
QWidget *focusProxy = w->focusProxy();
if (!focusProxy)
return nullptr;
while (QWidget *nextFocusProxy = focusProxy->focusProxy())
focusProxy = nextFocusProxy;
return focusProxy;
}
void Kleo::dumpFocusChain(QWidget *window)
{
if (!window) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Error: window is NULL";
return;
}
qCDebug(KLEOPATRA_LOG) << __func__ << "=====";
qCDebug(KLEOPATRA_LOG).noquote().nospace() << indentByWidgetDepth(window) << window;
for (auto w = window->nextInFocusChain(); w != window; w = w->nextInFocusChain()) {
if (const auto *focusProxy = deepestFocusProxy(w)) {
qCDebug(KLEOPATRA_LOG).noquote() << indentByWidgetDepth(w) << w << w->focusPolicy() << "proxy:" << focusProxy << focusProxy->focusPolicy();
} else {
qCDebug(KLEOPATRA_LOG).noquote() << indentByWidgetDepth(w) << w << w->focusPolicy();
}
}
qCDebug(KLEOPATRA_LOG) << __func__ << "=====";
}
/// returns the widget that QWidget::focusNextPrevChild() would give focus to
static QWidget *simulateFocusNextPrevChild(QWidget *focusWidget, bool next)
{
// taken from QApplicationPrivate::focusNextPrevChild_helper:
-// uint focus_flag = qt_tab_all_widgets() ? Qt::TabFocus : Qt::StrongFocus;
+ // uint focus_flag = qt_tab_all_widgets() ? Qt::TabFocus : Qt::StrongFocus;
uint focus_flag = Qt::TabFocus;
-// QWidget *f = toplevel->focusWidget();
+ // QWidget *f = toplevel->focusWidget();
QWidget *f = focusWidget;
QWidget *toplevel = focusWidget->window();
if (!f)
f = toplevel;
QWidget *w = f;
QWidget *test = f->nextInFocusChain();
-// bool seenWindow = false;
-// bool focusWidgetAfterWindow = false;
+ // bool seenWindow = false;
+ // bool focusWidgetAfterWindow = false;
while (test && test != f) {
-// if (test->isWindow())
-// seenWindow = true;
+ // if (test->isWindow())
+ // seenWindow = true;
// If the next focus widget has a focus proxy, we need to check to ensure
// that the proxy is in the correct parent-child direction (according to
// \a next). This is to ensure that we can tab in and out of compound widgets
// without getting stuck in a tab-loop between parent and child.
QWidget *focusProxy = deepestFocusProxy(test);
const bool canTakeFocus = ((focusProxy ? focusProxy->focusPolicy() : test->focusPolicy())
& focus_flag) == focus_flag;
- const bool composites = focusProxy ? (next ? focusProxy->isAncestorOf(test)
- : test->isAncestorOf(focusProxy))
+ const bool composites = focusProxy ? (next ? focusProxy->isAncestorOf(test) : test->isAncestorOf(focusProxy)) //
: false;
- if (canTakeFocus && !composites
- && test->isVisibleTo(toplevel) && test->isEnabled()
- && !(w->windowType() == Qt::SubWindow && !w->isAncestorOf(test))
- && (toplevel->windowType() != Qt::SubWindow || toplevel->isAncestorOf(test))
+ if (canTakeFocus && !composites //
+ && test->isVisibleTo(toplevel) && test->isEnabled() //
+ && !(w->windowType() == Qt::SubWindow && !w->isAncestorOf(test)) //
+ && (toplevel->windowType() != Qt::SubWindow || toplevel->isAncestorOf(test)) //
&& f != focusProxy) {
w = test;
-// if (seenWindow)
-// focusWidgetAfterWindow = true;
+ // if (seenWindow)
+ // focusWidgetAfterWindow = true;
if (next)
break;
}
test = test->nextInFocusChain();
}
-// if (wrappingOccurred != nullptr)
-// *wrappingOccurred = next ? focusWidgetAfterWindow : !focusWidgetAfterWindow;
+ // if (wrappingOccurred != nullptr)
+ // *wrappingOccurred = next ? focusWidgetAfterWindow : !focusWidgetAfterWindow;
if (w == f) {
-// if (qt_in_tab_key_event) {
-// w->window()->setAttribute(Qt::WA_KeyboardFocusChange);
-// w->update();
-// }
+ // if (qt_in_tab_key_event) {
+ // w->window()->setAttribute(Qt::WA_KeyboardFocusChange);
+ // w->update();
+ // }
return nullptr;
}
// taken from QWidget::setFocus:
if (auto focusProxy = deepestFocusProxy(w)) {
w = focusProxy;
}
return w;
}
void Kleo::dumpTabOrder(QWidget *widget)
{
if (!widget) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Error: widget is NULL";
return;
}
qCDebug(KLEOPATRA_LOG) << __func__ << "=====";
// simulate Tab, Tab, Tab, ...
QSet<QWidget*> seen;
qCDebug(KLEOPATRA_LOG).noquote().nospace() << indentByWidgetDepth(widget) << widget;
for (auto w = simulateFocusNextPrevChild(widget, true); w && !seen.contains(w); w = simulateFocusNextPrevChild(w, true)) {
qCDebug(KLEOPATRA_LOG).noquote().nospace() << indentByWidgetDepth(w) << w;
seen.insert(w);
}
qCDebug(KLEOPATRA_LOG) << __func__ << "=====";
// simulate Shift+Tab, Shift+Tab, Shift+Tab, ...
seen.clear();
qCDebug(KLEOPATRA_LOG).noquote().nospace() << indentByWidgetDepth(widget) << widget;
for (auto w = simulateFocusNextPrevChild(widget, false); w && !seen.contains(w); w = simulateFocusNextPrevChild(w, false)) {
qCDebug(KLEOPATRA_LOG).noquote().nospace() << indentByWidgetDepth(w) << w;
seen.insert(w);
}
qCDebug(KLEOPATRA_LOG) << __func__ << "=====";
}
diff --git a/src/utils/dragqueen.cpp b/src/utils/dragqueen.cpp
index 386437441..2d18e77bd 100644
--- a/src/utils/dragqueen.cpp
+++ b/src/utils/dragqueen.cpp
@@ -1,227 +1,227 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/dragqueen.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 <config-kleopatra.h>
#include "dragqueen.h"
#include <QDrag>
#include <QMouseEvent>
#include <QStringList>
#include <QVariant>
#include <QApplication>
#include <QUrl>
#include <QStyle>
#include <algorithm>
using namespace Kleo;
namespace
{
class MimeDataProxy : public QMimeData
{
Q_OBJECT
public:
explicit MimeDataProxy(QMimeData *source)
: QMimeData(), m_source(source)
{
}
QStringList formats() const override
{
if (m_source) {
return m_source->formats();
} else {
return QStringList();
}
}
bool hasFormat(const QString &format) const override
{
return m_source && m_source->hasFormat(format);
}
protected:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QVariant retrieveData(const QString &format, QVariant::Type type) const override
#else
QVariant retrieveData(const QString &format, QMetaType type) const override
#endif
{
if (!m_source) {
return QVariant();
}
// Doesn't work, is protected:
// return m_source->retrieveData( format, type );
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
switch (type) {
#else
switch (type.id()) {
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
case QVariant::String:
#else
case QMetaType::QString:
#endif
if (format == QLatin1String("text/plain")) {
return m_source->text();
}
if (format == QLatin1String("text/html")) {
return m_source->html();
}
break;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
case QVariant::Color:
#else
case QMetaType::QColor:
#endif
if (format == QLatin1String("application/x-color")) {
return m_source->colorData();
}
break;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
case QVariant::Image:
#else
case QMetaType::QImage:
#endif
if (format == QLatin1String("application/x-qt-image")) {
return m_source->imageData();
}
break;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
case QVariant::List:
case QVariant::Url:
#else
case QMetaType::QVariantList:
case QMetaType::QUrl:
#endif
if (format == QLatin1String("text/uri-list")) {
const QList<QUrl> urls = m_source->urls();
if (urls.size() == 1) {
return urls.front();
}
QList<QVariant> result;
std::copy(urls.begin(), urls.end(),
std::back_inserter(result));
return result;
}
break;
default:
break;
}
QVariant v = m_source->data(format);
v.convert(type);
return v;
}
private:
QPointer<QMimeData> m_source;
};
}
DragQueen::DragQueen(QWidget *p, Qt::WindowFlags f)
: QLabel(p, f),
m_data(),
m_dragStartPosition()
{
}
DragQueen::DragQueen(const QString &t, QWidget *p, Qt::WindowFlags f)
: QLabel(t, p, f),
m_data(),
m_dragStartPosition()
{
}
DragQueen::~DragQueen()
{
delete m_data;
}
void DragQueen::setUrl(const QString &url)
{
auto data = new QMimeData;
QList<QUrl> urls;
urls.push_back(QUrl(url));
data->setUrls(urls);
setMimeData(data);
}
QString DragQueen::url() const
{
if (!m_data || !m_data->hasUrls()) {
return QString();
}
const QList<QUrl> urls = m_data->urls();
if (urls.empty()) {
return QString();
}
return urls.front().toString();
}
void DragQueen::setMimeData(QMimeData *data)
{
if (data == m_data) {
return;
}
delete m_data;
m_data = data;
}
QMimeData *DragQueen::mimeData() const
{
return m_data;
}
void DragQueen::mousePressEvent(QMouseEvent *e)
{
#ifndef QT_NO_DRAGANDDROP
if (m_data && e->button() == Qt::LeftButton) {
m_dragStartPosition = e->pos();
}
#endif
QLabel::mousePressEvent(e);
}
static QPoint calculate_hot_spot(const QPoint &mouse, const QSize &pix, const QLabel *label)
{
const Qt::Alignment align = label->alignment();
const int margin = label->margin();
const QRect cr = label->contentsRect().adjusted(margin, margin, -margin, -margin);
const QRect rect = QStyle::alignedRect(QApplication::layoutDirection(), align, pix, cr);
return mouse - rect.topLeft();
}
void DragQueen::mouseMoveEvent(QMouseEvent *e)
{
#ifndef QT_NO_DRAGANDDROP
- if (m_data &&
- (e->buttons() & Qt::LeftButton) &&
- (m_dragStartPosition - e->pos()).manhattanLength() > QApplication::startDragDistance()) {
+ if (m_data //
+ && (e->buttons() & Qt::LeftButton) //
+ && (m_dragStartPosition - e->pos()).manhattanLength() > QApplication::startDragDistance()) {
auto drag = new QDrag(this);
const QPixmap pix = pixmap(Qt::ReturnByValue);
if (!pix.isNull()) {
drag->setPixmap(pix);
drag->setHotSpot(calculate_hot_spot(e->pos(), pix.size(), this));
}
drag->setMimeData(new MimeDataProxy(m_data));
drag->exec();
} else {
#endif
QLabel::mouseMoveEvent(e);
#ifndef QT_NO_DRAGANDDROP
}
#endif
}
#include "dragqueen.moc"
diff --git a/src/utils/expiration.cpp b/src/utils/expiration.cpp
index 0313678ec..01443abf8 100644
--- a/src/utils/expiration.cpp
+++ b/src/utils/expiration.cpp
@@ -1,138 +1,138 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/expiration.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2023 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 "expiration.h"
#include <settings.h>
#include <KDateComboBox>
#include <KLocalizedString>
QDate Kleo::maximumAllowedDate()
{
static const QDate maxAllowedDate{2106, 2, 5};
return maxAllowedDate;
}
QDate Kleo::minimumExpirationDate()
{
return expirationDateRange().minimum;
}
QDate Kleo::maximumExpirationDate()
{
return expirationDateRange().maximum;
}
Kleo::DateRange Kleo::expirationDateRange()
{
Kleo::DateRange range;
const auto settings = Kleo::Settings{};
const auto today = QDate::currentDate();
const auto minimumExpiry = std::max(1, settings.validityPeriodInDaysMin());
range.minimum = std::min(today.addDays(minimumExpiry), maximumAllowedDate());
const auto maximumExpiry = settings.validityPeriodInDaysMax();
if (maximumExpiry >= 0) {
range.maximum = std::min(std::max(today.addDays(maximumExpiry), range.minimum), maximumAllowedDate());
}
return range;
}
QDate Kleo::defaultExpirationDate(Kleo::ExpirationOnUnlimitedValidity onUnlimitedValidity)
{
QDate expirationDate;
const auto settings = Kleo::Settings{};
const auto defaultExpirationInDays = settings.validityPeriodInDays();
if (defaultExpirationInDays > 0) {
expirationDate = QDate::currentDate().addDays(defaultExpirationInDays);
} else if (defaultExpirationInDays < 0 || onUnlimitedValidity == ExpirationOnUnlimitedValidity::InternalDefaultExpiration) {
expirationDate = QDate::currentDate().addYears(3);
}
const auto allowedRange = expirationDateRange();
expirationDate = std::max(expirationDate, allowedRange.minimum);
if (allowedRange.maximum.isValid()) {
expirationDate = std::min(expirationDate, allowedRange.maximum);
}
return expirationDate;
}
bool Kleo::isValidExpirationDate(const QDate &date)
{
const auto allowedRange = expirationDateRange();
if (date.isValid()) {
return (date >= allowedRange.minimum //
- && ((allowedRange.maximum.isValid() && date <= allowedRange.maximum)
+ && ((allowedRange.maximum.isValid() && date <= allowedRange.maximum) //
|| (!allowedRange.maximum.isValid() && date <= maximumAllowedDate())));
} else {
return !allowedRange.maximum.isValid();
}
}
static QString dateToString(const QDate &date, QWidget *widget)
{
// workaround for QLocale using "yy" way too often for years
// stolen from KDateComboBox
auto locale = widget ? widget->locale() : QLocale{};
const auto dateFormat = (locale.dateFormat(QLocale::ShortFormat) //
.replace(QLatin1String{"yy"}, QLatin1String{"yyyy"})
.replace(QLatin1String{"yyyyyyyy"}, QLatin1String{"yyyy"}));
return locale.toString(date, dateFormat);
}
static QString validityPeriodHint(const Kleo::DateRange &dateRange, QWidget *widget)
{
// the minimum date is always valid
if (dateRange.maximum.isValid()) {
if (dateRange.maximum == dateRange.minimum) {
return i18nc("@info", "The date cannot be changed.");
} else {
return i18nc("@info ... between <a date> and <another date>.", "Enter a date between %1 and %2.",
dateToString(dateRange.minimum, widget), dateToString(dateRange.maximum, widget));
}
} else {
return i18nc("@info ... between <a date> and <another date>.", "Enter a date between %1 and %2.",
dateToString(dateRange.minimum, widget), dateToString(Kleo::maximumAllowedDate(), widget));
}
}
QString Kleo::validityPeriodHint()
{
return ::validityPeriodHint(expirationDateRange(), nullptr);
}
void Kleo::setUpExpirationDateComboBox(KDateComboBox *dateCB, const Kleo::DateRange &range)
{
const auto dateRange = range.minimum.isValid() ? range : expirationDateRange();
// enable warning on invalid or not allowed dates
dateCB->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker |
KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid);
const auto hintAndErrorMessage = validityPeriodHint(dateRange, dateCB);
dateCB->setDateRange(dateRange.minimum, dateRange.maximum.isValid() ? dateRange.maximum : maximumAllowedDate(), hintAndErrorMessage, hintAndErrorMessage);
if (dateRange.minimum == dateRange.maximum) {
// only one date is allowed, so that changing it no sense
dateCB->setEnabled(false);
}
dateCB->setToolTip(hintAndErrorMessage);
const QDate today = QDate::currentDate();
dateCB->setDateMap({
{today.addYears(3), i18nc("@item:inlistbox", "Three years from now")},
{today.addYears(2), i18nc("@item:inlistbox", "Two years from now")},
{today.addYears(1), i18nc("@item:inlistbox", "One year from now")},
});
}
diff --git a/src/utils/kdpipeiodevice.h b/src/utils/kdpipeiodevice.h
index a7b3b2d09..4b36c2c6e 100644
--- a/src/utils/kdpipeiodevice.h
+++ b/src/utils/kdpipeiodevice.h
@@ -1,64 +1,64 @@
/*
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <QIODevice>
#include <utility>
class KDPipeIODevice : public QIODevice
{
Q_OBJECT
//KDAB_MAKE_CHECKABLE( KDPipeIODevice )
public:
enum DebugLevel {
NoDebug,
- Debug
+ Debug,
};
static DebugLevel debugLevel();
static void setDebugLevel(DebugLevel level);
explicit KDPipeIODevice(QObject *parent = nullptr);
explicit KDPipeIODevice(int fd, OpenMode = ReadOnly, QObject *parent = nullptr);
explicit KDPipeIODevice(Qt::HANDLE handle, OpenMode = ReadOnly, QObject *parent = nullptr);
~KDPipeIODevice() override;
static std::pair<KDPipeIODevice *, KDPipeIODevice *> makePairOfConnectedPipes();
bool open(int fd, OpenMode mode = ReadOnly);
bool open(Qt::HANDLE handle, OpenMode mode = ReadOnly);
Qt::HANDLE handle() const;
int descriptor() const;
bool readWouldBlock() const;
bool writeWouldBlock() const;
qint64 bytesAvailable() const override;
qint64 bytesToWrite() const override;
bool canReadLine() const override;
void close() override;
bool isSequential() const override;
bool atEnd() const override;
bool waitForBytesWritten(int msecs) override;
bool waitForReadyRead(int msecs) override;
protected:
qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
private:
using QIODevice::open;
private:
class Private;
Private *d;
};
diff --git a/src/utils/keys.cpp b/src/utils/keys.cpp
index 80b10ff6a..1017465f7 100644
--- a/src/utils/keys.cpp
+++ b/src/utils/keys.cpp
@@ -1,164 +1,164 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keys.h"
#include <kleopatra_debug.h>
#include <settings.h>
#include <Libkleo/Algorithm>
#include <Libkleo/KeyCache>
#include <QDate>
// needed for GPGME_VERSION_NUMBER
#include <gpgme.h>
#include <algorithm>
#include <iterator>
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<GpgME::UserID::Signature> 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
+ && 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());
}
static time_t creationDate(const GpgME::UserID &uid)
{
// returns the date of the first self-signature
for (unsigned int i = 0, numSignatures = uid.numSignatures(); i < numSignatures; ++i) {
const auto sig = uid.signature(i);
if (Kleo::isSelfSignature(sig)) {
return sig.creationTime();
}
}
return 0;
}
bool Kleo::userIDsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs)
{
- return (qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0
- && qstrcmp(lhs.id(), rhs.id()) == 0
+ return (qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0 //
+ && qstrcmp(lhs.id(), rhs.id()) == 0 //
&& creationDate(lhs) == creationDate(rhs));
}
diff --git a/src/utils/log.h b/src/utils/log.h
index 4689eae90..d6f869c2f 100644
--- a/src/utils/log.h
+++ b/src/utils/log.h
@@ -1,62 +1,62 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/log.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 <utils/pimpl_ptr.h>
#include <memory>
#include <cstdio>
class QIODevice;
class QString;
namespace Kleo
{
class Log
{
public:
enum OpenMode {
Read = 0x1,
- Write = 0x2
+ Write = 0x2,
};
static void messageHandler(QtMsgType type, const QMessageLogContext &ctx,
const QString &msg);
static std::shared_ptr<const Log> instance();
static std::shared_ptr<Log> mutableInstance();
~Log();
bool ioLoggingEnabled() const;
void setIOLoggingEnabled(bool enabled);
QString outputDirectory() const;
void setOutputDirectory(const QString &path);
std::shared_ptr<QIODevice> createIOLogger(const std::shared_ptr<QIODevice> &wrapped, const QString &prefix, OpenMode mode) const;
FILE *logFile() const;
private:
Log();
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_DISABLE_COPY(Log)
};
}
diff --git a/src/utils/path-helper.cpp b/src/utils/path-helper.cpp
index da92faf0d..629dd85ad 100644
--- a/src/utils/path-helper.cpp
+++ b/src/utils/path-helper.cpp
@@ -1,219 +1,220 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/path-helper.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "path-helper.h"
#include <Libkleo/Stl_Util>
#include <Libkleo/KleoException>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <QStandardPaths>
#include <QString>
#include <QStorageInfo>
#include <QFileInfo>
#include <QDir>
#include <algorithm>
#ifdef Q_OS_WIN
extern Q_CORE_EXPORT int qt_ntfs_permission_lookup;
#endif
using namespace Kleo;
static QString commonPrefix(const QString &s1, const QString &s2)
{
return QString(s1.data(), std::mismatch(s1.data(), s1.data() + std::min(s1.size(), s2.size()), s2.data()).first - s1.data());
}
static QString longestCommonPrefix(const QStringList &sl)
{
if (sl.empty()) {
return QString();
}
QString result = sl.front();
for (const QString &s : sl) {
result = commonPrefix(s, result);
}
return result;
}
QString Kleo::heuristicBaseDirectory(const QStringList &fileNames)
{
QStringList dirs;
for (const QString &fileName : fileNames) {
dirs.push_back(QFileInfo(fileName).path() + QLatin1Char('/'));
}
qCDebug(KLEOPATRA_LOG) << "dirs" << dirs;
const QString candidate = longestCommonPrefix(dirs);
/* Special case handling for Outlook and KMail attachment temporary path.
* This is otherwise something like:
* c:\users\username\AppData\Local\Microsoft\Windows\INetCache\
* Content.Outlook\ADSDFG9\foo.txt
*
* For KMail it is usually /tmp/messageviewer/foo
*
* Both are paths that are unlikely to be the target path to save the
* decrypted attachment.
*
* This is very common when encrypted attachments are opened
* within Outlook or KMail.
*/
- if (candidate.contains(QStringLiteral("Content.Outlook")) ||
- candidate.contains(QStringLiteral("messageviewer"))) {
+ if (candidate.contains(QStringLiteral("Content.Outlook")) //
+ || candidate.contains(QStringLiteral("messageviewer"))) {
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
}
const int idx = candidate.lastIndexOf(QLatin1Char('/'));
return candidate.left(idx);
}
QStringList Kleo::makeRelativeTo(const QString &base, const QStringList &fileNames)
{
if (base.isEmpty()) {
return fileNames;
} else {
return makeRelativeTo(QDir(base), fileNames);
}
}
QStringList Kleo::makeRelativeTo(const QDir &baseDir, const QStringList &fileNames)
{
QStringList rv;
rv.reserve(fileNames.size());
std::transform(fileNames.cbegin(), fileNames.cend(),
std::back_inserter(rv),
[&baseDir](const QString &file) {
return baseDir.relativeFilePath(file);
});
return rv;
}
QString Kleo::stripSuffix(const QString &fileName)
{
const QFileInfo fi(fileName);
return fi.dir().filePath(fi.completeBaseName());
}
#ifdef Q_OS_WIN
namespace
{
class NTFSPermissionsCheck
{
public:
NTFSPermissionsCheck()
{
// enable the NTFS permissions check
qt_ntfs_permission_lookup++;
qCDebug(KLEOPATRA_LOG) << __func__ << "NTFS permissions check" << (qt_ntfs_permission_lookup ? "enabled" : "disabled");
}
~NTFSPermissionsCheck()
{
// disable the NTFS permissions check
qt_ntfs_permission_lookup--;
qCDebug(KLEOPATRA_LOG) << __func__ << "NTFS permissions check" << (qt_ntfs_permission_lookup ? "enabled" : "disabled");
}
};
}
#endif
bool Kleo::isWritable(const QFileInfo &fi)
{
#ifdef Q_OS_WIN
NTFSPermissionsCheck withExpensiveNTFSPermissionsCheck;
const auto result = fi.isWritable();
qCDebug(KLEOPATRA_LOG) << __func__ << fi.absoluteFilePath() << (result ? "is writable" : "is not writable");
return result;
#else
return fi.isWritable();
#endif
}
#ifdef Q_OS_WIN
void Kleo::recursivelyRemovePath(const QString &path)
{
const QFileInfo fi(path);
if (fi.isDir()) {
QDir dir(path);
const auto dirs{dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden)};
for (const QString &fname : dirs) {
recursivelyRemovePath(dir.filePath(fname));
}
const QString dirName = fi.fileName();
dir.cdUp();
if (!dir.rmdir(dirName)) {
throw Exception(GPG_ERR_EPERM, i18n("Cannot remove directory %1", path));
}
} else {
QFile file(path);
if (!file.remove()) {
throw Exception(GPG_ERR_EPERM, i18n("Cannot remove file %1: %2", path, file.errorString()));
}
}
}
bool Kleo::recursivelyCopy(const QString &src,const QString &dest)
{
QDir srcDir(src);
if(!srcDir.exists()) {
return false;
}
QDir destDir(dest);
if(!destDir.exists() && !destDir.mkdir(dest)) {
return false;
}
for(const auto &file: srcDir.entryList(QDir::Files | QDir::Hidden)) {
const QString srcName = src + QLatin1Char('/') + file;
const QString destName = dest + QLatin1Char('/') + file;
if(!QFile::copy(srcName, destName)) {
return false;
}
}
for (const auto &dir: srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden)) {
const QString srcName = src + QLatin1Char('/') + dir;
const QString destName = dest + QLatin1Char('/') + dir;
if (!recursivelyCopy(srcName, destName)) {
return false;
}
}
return true;
}
bool Kleo::moveDir(const QString &src, const QString &dest)
{
// Need an existing path to query the device
const QString parentDest = QFileInfo(dest).dir().absolutePath();
const auto srcDevice = QStorageInfo(src).device();
- if (!srcDevice.isEmpty() && srcDevice == QStorageInfo(parentDest).device() &&
- QFile::rename(src, dest)) {
+ if (!srcDevice.isEmpty() //
+ && srcDevice == QStorageInfo(parentDest).device() //
+ && QFile::rename(src, dest)) {
qCDebug(KLEOPATRA_LOG) << "Renamed" << src << "to" << dest;
return true;
}
// first copy
if (!recursivelyCopy(src, dest)) {
return false;
}
// Then delete original
recursivelyRemovePath(src);
return true;
}
#endif
diff --git a/src/utils/types.h b/src/utils/types.h
index 0bc747f92..9b501d163 100644
--- a/src/utils/types.h
+++ b/src/utils/types.h
@@ -1,66 +1,66 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/types.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 <utils/pimpl_ptr.h>
#include <memory>
class QWidget;
namespace Kleo
{
enum DecryptVerifyOperation {
Decrypt,
Verify,
- DecryptVerify/*,
- VerifyOpaque,
- VerifyDetached*/
+ DecryptVerify,
+ // VerifyOpaque,
+ // VerifyDetached,
};
enum VerificationMode {
Opaque,
- Detached
+ Detached,
};
enum Policy {
NoPolicy,
Allow,
Force,
- Deny
+ Deny,
};
class ExecutionContext
{
public:
virtual ~ExecutionContext() {}
virtual void applyWindowID(QWidget *widget) const = 0;
};
class ExecutionContextUser
{
public:
ExecutionContextUser();
explicit ExecutionContextUser(const std::shared_ptr<const ExecutionContext> &ec);
virtual ~ExecutionContextUser();
void setExecutionContext(const std::shared_ptr<const ExecutionContext> &ec);
std::shared_ptr<const ExecutionContext> executionContext() const;
protected:
void bringToForeground(QWidget *wid, bool stayOnTop = false);
void applyWindowID(QWidget *wid);
private:
class Private;
kdtools::pimpl_ptr<Private> d;
};
}
diff --git a/src/utils/userinfo.cpp b/src/utils/userinfo.cpp
index b66b60146..acfea487a 100644
--- a/src/utils/userinfo.cpp
+++ b/src/utils/userinfo.cpp
@@ -1,85 +1,85 @@
/*
utils/userinfo.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "userinfo.h"
// Needed for global defines
#include <QtGlobal>
#ifdef Q_OS_WIN
#include "userinfo_win_p.h"
#endif
#include <KEmailAddress>
#include <KEMailSettings>
namespace
{
enum UserInfoDetail {
UserInfoName,
- UserInfoEmailAddress
+ 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/utils/validation.h b/src/utils/validation.h
index c7406c2c3..8f27ff58d 100644
--- a/src/utils/validation.h
+++ b/src/utils/validation.h
@@ -1,56 +1,56 @@
/* -*- mode: c++; c-basic-offset:4 -*-
utils/validation.h
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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <memory>
class QString;
class QValidator;
namespace Kleo
{
namespace Validation
{
enum Flags {
Optional,
- Required
+ Required,
};
std::shared_ptr<QValidator> email(Flags flags = Required);
/**
* Creates a validator for the name part of the user ID of an OpenPGP key with
* restrictions that are necessary for usage with the edit-key interface.
*/
std::shared_ptr<QValidator> pgpName(Flags flags = Required);
/**
* Creates a validator for the name part of the user ID of an OpenPGP key with
* less restrictions than \ref pgpName.
*/
std::shared_ptr<QValidator> simpleName(Flags flags = Required);
std::shared_ptr<QValidator> email(const QString &additionalRegExp, Flags flags = Required);
/**
* Creates a validator for the name part of the user ID of an OpenPGP key with
* restrictions that are necessary for usage with the edit-key interface, and
* with additional restrictions imposed by \p additionalRegExp.
*/
std::shared_ptr<QValidator> pgpName(const QString &additionalRegExp, Flags flags = Required);
/**
* Creates a validator for the name part of the user ID of an OpenPGP key with
* less restrictions than \ref pgpName, but with additional restrictions imposed
* by \p additionalRegExp.
*/
std::shared_ptr<QValidator> simpleName(const QString &additionalRegExp, Flags flags = Required);
}
}
diff --git a/src/utils/windowsprocessdevice.cpp b/src/utils/windowsprocessdevice.cpp
index 3484df743..523962e7c 100644
--- a/src/utils/windowsprocessdevice.cpp
+++ b/src/utils/windowsprocessdevice.cpp
@@ -1,460 +1,459 @@
/* utils/windowsprocessdevice.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2019 g 10code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifdef WIN32
#include "windowsprocessdevice.h"
#include "kleopatra_debug.h"
#include <Libkleo/GnuPG>
#include <windows.h>
#include <QDir>
#include <QRegularExpression>
/* This is the amount of data GPGME reads at once */
#define PIPEBUF_SIZE 16384
using namespace Kleo;
static void CloseHandleX(HANDLE &h)
{
if (h && h != INVALID_HANDLE_VALUE) {
if (!CloseHandle(h)) {
qCWarning(KLEOPATRA_LOG) << "CloseHandle failed!";
}
h = nullptr;
}
}
class WindowsProcessDevice::Private
{
public:
~Private();
Private(const QString &path, const QStringList &args, const QString &wd):
mPath(path),
mArgs(args),
mWorkingDirectory(wd),
mStdInRd(nullptr),
mStdInWr(nullptr),
mStdOutRd(nullptr),
mStdOutWr(nullptr),
mStdErrRd(nullptr),
mStdErrWr(nullptr),
mProc(nullptr),
mThread(nullptr),
mEnded(false)
{
}
bool start (QIODevice::OpenMode mode);
qint64 write(const char *data, qint64 size)
{
if (size < 0 || (size >> 32)) {
qCDebug (KLEOPATRA_LOG) << "Invalid write";
return -1;
}
if (!mStdInWr) {
qCDebug (KLEOPATRA_LOG) << "Write to closed or read only device";
return -1;
}
DWORD dwWritten;
if (!WriteFile(mStdInWr, data, (DWORD) size, &dwWritten, nullptr)) {
qCDebug(KLEOPATRA_LOG) << "Failed to write";
return -1;
}
if (dwWritten != size) {
qCDebug(KLEOPATRA_LOG) << "Failed to write everything";
return -1;
}
return size;
}
qint64 read(char *data, qint64 maxSize)
{
if (!mStdOutRd) {
qCDebug (KLEOPATRA_LOG) << "Read of closed or write only device";
return -1;
}
if (!maxSize) {
return 0;
}
DWORD exitCode = 0;
if (GetExitCodeProcess (mProc, &exitCode)) {
if (exitCode != STILL_ACTIVE) {
if (exitCode) {
qCDebug(KLEOPATRA_LOG) << "Non zero exit code";
mError = readAllStdErr();
return -1;
}
mEnded = true;
qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode;
}
} else {
qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed";
}
if (mEnded) {
DWORD avail = 0;
if (!PeekNamedPipe(mStdOutRd,
nullptr,
0,
nullptr,
&avail,
nullptr)) {
qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe";
return -1;
}
if (!avail) {
qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe";
return 0;
}
}
DWORD dwRead;
if (!ReadFile(mStdOutRd, data, (DWORD) maxSize, &dwRead, nullptr)) {
qCDebug(KLEOPATRA_LOG) << "Failed to read";
return -1;
}
return dwRead;
}
QString readAllStdErr()
{
QString ret;
if (!mStdErrRd) {
qCDebug (KLEOPATRA_LOG) << "Read of closed stderr";
}
DWORD dwRead = 0;
do {
char buf[4096];
DWORD avail;
if (!PeekNamedPipe(mStdErrRd,
nullptr,
0,
nullptr,
&avail,
nullptr)) {
qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe";
return ret;
}
if (!avail) {
return ret;
}
ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr);
if (dwRead) {
QByteArray ba (buf, dwRead);
ret += QString::fromLocal8Bit (ba);
}
} while (dwRead);
return ret;
}
void close()
{
if (mProc && mProc != INVALID_HANDLE_VALUE) {
TerminateProcess(mProc, 0xf291);
CloseHandleX(mProc);
}
}
QString errorString()
{
return mError;
}
void closeWriteChannel()
{
CloseHandleX(mStdInWr);
}
private:
QString mPath;
QStringList mArgs;
QString mWorkingDirectory;
QString mError;
HANDLE mStdInRd;
HANDLE mStdInWr;
HANDLE mStdOutRd;
HANDLE mStdOutWr;
HANDLE mStdErrRd;
HANDLE mStdErrWr;
HANDLE mProc;
HANDLE mThread;
bool mEnded;
};
WindowsProcessDevice::WindowsProcessDevice(const QString &path,
const QStringList &args,
const QString &wd):
d(new Private(path, args, wd))
{
}
bool WindowsProcessDevice::open(QIODevice::OpenMode mode)
{
bool ret = d->start(mode);
if (ret) {
setOpenMode(mode);
}
return ret;
}
qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize)
{
return d->read(data, maxSize);
}
qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize)
{
return d->write(data, maxSize);
}
bool WindowsProcessDevice::isSequential() const
{
return true;
}
void WindowsProcessDevice::closeWriteChannel()
{
d->closeWriteChannel();
}
void WindowsProcessDevice::close()
{
d->close();
QIODevice::close();
}
QString getLastErrorString()
{
wchar_t *lpMsgBuf = nullptr;
DWORD dw = GetLastError();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(wchar_t *) &lpMsgBuf,
0, NULL);
QString ret = QString::fromWCharArray(lpMsgBuf);
LocalFree(lpMsgBuf);
return ret;
}
WindowsProcessDevice::Private::~Private()
{
if (mProc && mProc != INVALID_HANDLE_VALUE) {
close();
}
CloseHandleX (mThread);
CloseHandleX (mStdInRd);
CloseHandleX (mStdInWr);
CloseHandleX (mStdOutRd);
CloseHandleX (mStdOutWr);
CloseHandleX (mStdErrRd);
CloseHandleX (mStdErrWr);
}
static QString qt_create_commandline(const QString &program, const QStringList &arguments,
const QString &nativeArguments)
{
QString args;
if (!program.isEmpty()) {
QString programName = program;
if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' ')))
programName = QLatin1Char('\"') + programName + QLatin1Char('\"');
programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
// add the prgram as the first arg ... it works better
args = programName + QLatin1Char(' ');
}
for (int i=0; i<arguments.size(); ++i) {
QString tmp = arguments.at(i);
// Quotes are escaped and their preceding backslashes are doubled.
tmp.replace(QRegularExpression(QLatin1String(R"--((\\*)")--")), QLatin1String(R"--(\1\1\")--"));
if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
int i = tmp.length();
while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\'))
--i;
tmp.insert(i, QLatin1Char('"'));
tmp.prepend(QLatin1Char('"'));
}
args += QLatin1Char(' ') + tmp;
}
if (!nativeArguments.isEmpty()) {
if (!args.isEmpty())
args += QLatin1Char(' ');
args += nativeArguments;
}
return args;
}
bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode)
{
- if (mode != QIODevice::ReadOnly &&
- mode != QIODevice::WriteOnly &&
- mode != QIODevice::ReadWrite) {
+ if (mode != QIODevice::ReadOnly //
+ && mode != QIODevice::WriteOnly //
+ && mode != QIODevice::ReadWrite) {
qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode;
return false;
}
SECURITY_ATTRIBUTES saAttr;
ZeroMemory (&saAttr, sizeof (SECURITY_ATTRIBUTES));
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create the pipes
- if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) ||
- !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) ||
- !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) {
+ if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) //
+ || !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) //
+ || !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) {
qCDebug(KLEOPATRA_LOG) << "Failed to create pipes";
mError = getLastErrorString();
return false;
}
// Ensure only the proper handles are inherited
- if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) ||
- !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) ||
- !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) {
-
+ if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) //
+ || !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) //
+ || !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) {
qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag";
mError = getLastErrorString();
return false;
}
PROCESS_INFORMATION piProcInfo;
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
STARTUPINFO siStartInfo;
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = mStdErrWr;
siStartInfo.hStdOutput = mStdOutWr;
siStartInfo.hStdInput = mStdInRd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
const auto args = qt_create_commandline(mPath, mArgs, QString());
wchar_t *cmdLine = wcsdup (reinterpret_cast<const wchar_t *>(args.utf16()));
const wchar_t *proc = reinterpret_cast<const wchar_t *>(mPath.utf16());
const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory);
const wchar_t *wd = reinterpret_cast<const wchar_t *>(nativeWorkingDirectory.utf16());
/* Filter out the handles to inherit only the three handles which we want to
* inherit. As a Qt Application we have a multitude of open handles which
* may or may not be inheritable. Testing has shown that this reduced about
* thirty handles. Open File handles in our child application can also cause the
* read pipe not to be closed correcly on exit. */
SIZE_T size;
bool suc = InitializeProcThreadAttributeList(NULL, 1, 0, &size) ||
GetLastError() == ERROR_INSUFFICIENT_BUFFER;
if (!suc) {
qCDebug(KLEOPATRA_LOG) << "Failed to get Attribute List size";
mError = getLastErrorString();
return false;
}
LPPROC_THREAD_ATTRIBUTE_LIST attributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST> (HeapAlloc(GetProcessHeap(), 0, size));
if (!attributeList) {
qCDebug(KLEOPATRA_LOG) << "Failed to Allocate Attribute List";
return false;
}
suc = InitializeProcThreadAttributeList(attributeList, 1, 0, &size);
if (!suc) {
qCDebug(KLEOPATRA_LOG) << "Failed to Initalize Attribute List";
mError = getLastErrorString();
return false;
}
HANDLE handles[3];
handles[0] = mStdOutWr;
handles[1] = mStdErrWr;
handles[2] = mStdInRd;
suc = UpdateProcThreadAttribute(attributeList,
0,
PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
handles,
3 * sizeof(HANDLE),
NULL,
NULL);
if (!suc) {
qCDebug(KLEOPATRA_LOG) << "Failed to Update Attribute List";
mError = getLastErrorString();
return false;
}
STARTUPINFOEX info;
ZeroMemory(&info, sizeof(info));
info.StartupInfo = siStartInfo;
info.StartupInfo.cb = sizeof(info); // You have to know this,..
info.lpAttributeList = attributeList;
// Now lets start
qCDebug(KLEOPATRA_LOG) << "Spawning:" << args;
suc = CreateProcessW(proc,
cmdLine, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,// creation flags
NULL, // use parent's environment
wd, // use parent's current directory
&info.StartupInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
DeleteProcThreadAttributeList(attributeList);
HeapFree(GetProcessHeap(), 0, attributeList);
CloseHandleX (mStdOutWr);
CloseHandleX (mStdErrWr);
CloseHandleX (mStdInRd);
free(cmdLine);
if (!suc) {
qCDebug(KLEOPATRA_LOG) << "Failed to create process";
mError = getLastErrorString();
return false;
}
mProc = piProcInfo.hProcess;
mThread = piProcInfo.hThread;
if (mode == QIODevice::WriteOnly) {
CloseHandleX (mStdOutRd);
}
if (mode == QIODevice::ReadOnly) {
CloseHandleX (mStdInWr);
}
return true;
}
QString WindowsProcessDevice::errorString()
{
return d->errorString();
}
#endif
diff --git a/src/view/formtextinput.cpp b/src/view/formtextinput.cpp
index db614c7c7..77f054e5b 100644
--- a/src/view/formtextinput.cpp
+++ b/src/view/formtextinput.cpp
@@ -1,418 +1,417 @@
/* view/formtextinput.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 "formtextinput.h"
#include "errorlabel.h"
#include "utils/accessibility.h"
#include <KLocalizedString>
#include <QLabel>
#include <QLineEdit>
#include <QPointer>
#include <QValidator>
#include "kleopatra_debug.h"
namespace
{
auto defaultValueRequiredErrorMessage()
{
return i18n("Error: Enter a value.");
}
auto defaultInvalidEntryErrorMessage()
{
return i18n("Error: Enter a value in the correct format.");
}
}
namespace Kleo::_detail
{
class FormTextInputBase::Private
{
FormTextInputBase *q;
public:
enum Error
{
EntryOK,
EntryMissing, // a required entry is missing
InvalidEntry // the validator doesn't accept the entry
};
Private(FormTextInputBase *q)
: q{q}
, mValueRequiredErrorMessage{defaultValueRequiredErrorMessage()}
, mInvalidEntryErrorMessage{defaultInvalidEntryErrorMessage()}
{}
QString annotatedIfRequired(const QString &text) const;
void updateLabel();
void setLabelText(const QString &text, const QString &accessibleName);
void setHint(const QString &text, const QString &accessibleDescription);
QString errorMessage(Error error) const;
QString accessibleErrorMessage(Error error) const;
void updateError();
QString accessibleDescription() const;
void updateAccessibleNameAndDescription();
QPointer<QLabel> mLabel;
QPointer<QLabel> mHintLabel;
QPointer<QWidget> mWidget;
QPointer<ErrorLabel> mErrorLabel;
std::shared_ptr<QValidator> mValidator;
QString mLabelText;
QString mAccessibleName;
QString mValueRequiredErrorMessage;
QString mAccessibleValueRequiredErrorMessage;
QString mInvalidEntryErrorMessage;
QString mAccessibleInvalidEntryErrorMessage;
Error mError = EntryOK;
bool mRequired = false;
bool mEditingInProgress = false;
};
QString FormTextInputBase::Private::annotatedIfRequired(const QString &text) const
{
- return mRequired
- ? i18nc("@label label text (required)", "%1 (required)", text)
- : text;
+ return mRequired ? i18nc("@label label text (required)", "%1 (required)", text) //
+ : text;
}
void FormTextInputBase::Private::updateLabel()
{
if (mLabel) {
mLabel->setText(annotatedIfRequired(mLabelText));
}
}
void FormTextInputBase::Private::setLabelText(const QString &text, const QString &accessibleName)
{
mLabelText = text;
mAccessibleName = accessibleName.isEmpty() ? text : accessibleName;
updateLabel();
updateAccessibleNameAndDescription();
}
void FormTextInputBase::Private::setHint(const QString &text, const QString &accessibleDescription)
{
if (!mHintLabel) {
return;
}
mHintLabel->setVisible(!text.isEmpty());
mHintLabel->setText(text);
mHintLabel->setAccessibleName(accessibleDescription.isEmpty() ? text : accessibleDescription);
updateAccessibleNameAndDescription();
}
namespace
{
QString decoratedError(const QString &text)
{
return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text);
}
}
QString FormTextInputBase::Private::errorMessage(Error error) const
{
switch (error) {
case EntryOK:
return {};
case EntryMissing:
return mValueRequiredErrorMessage;
case InvalidEntry:
return mInvalidEntryErrorMessage;
}
return {};
}
QString FormTextInputBase::Private::accessibleErrorMessage(Error error) const
{
switch (error) {
case EntryOK:
return {};
case EntryMissing:
return mAccessibleValueRequiredErrorMessage;
case InvalidEntry:
return mAccessibleInvalidEntryErrorMessage;
}
return {};
}
void FormTextInputBase::Private::updateError()
{
if (!mErrorLabel) {
return;
}
if (mRequired && !q->hasValue()) {
mError = EntryMissing;
} else if (!q->hasAcceptableInput()) {
mError = InvalidEntry;
} else {
mError = EntryOK;
}
const auto currentErrorMessage = mErrorLabel->text();
const auto newErrorMessage = decoratedError(errorMessage(mError));
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && mEditingInProgress) {
// 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;
}
mErrorLabel->setVisible(!newErrorMessage.isEmpty());
mErrorLabel->setText(newErrorMessage);
mErrorLabel->setAccessibleName(decoratedError(accessibleErrorMessage(mError)));
updateAccessibleNameAndDescription();
}
QString FormTextInputBase::Private::accessibleDescription() const
{
QString description;
if (mHintLabel) {
// get the explicitly set accessible hint text
description = mHintLabel->accessibleName();
}
if (description.isEmpty()) {
// fall back to the default accessible description of the input widget
description = getAccessibleDescription(mWidget);
}
return description;
}
void FormTextInputBase::Private::updateAccessibleNameAndDescription()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if (mAccessibleName.isEmpty()) {
mAccessibleName = getAccessibleName(mWidget);
}
const bool errorShown = mErrorLabel && mErrorLabel->isVisible();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by setting the hint text and, if the error is shown, the error message as accessible
// description of the input field
- const auto description = errorShown ? accessibleDescription() + QLatin1String{" "} + mErrorLabel->accessibleName()
+ const auto description = errorShown ? accessibleDescription() + QLatin1String{" "} + mErrorLabel->accessibleName() //
: accessibleDescription();
if (mWidget && mWidget->accessibleDescription() != description) {
mWidget->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
QString name = annotatedIfRequired(mAccessibleName);
if (errorShown) {
name += QLatin1String{", "} + invalidEntryText();
};
if (mLabel && mLabel->accessibleName() != name) {
mLabel->setAccessibleName(name);
}
if (mWidget && mWidget->accessibleName() != name) {
mWidget->setAccessibleName(name);
}
}
FormTextInputBase::FormTextInputBase()
: d{new Private{this}}
{
}
FormTextInputBase::~FormTextInputBase() = default;
QWidget *FormTextInputBase::widget() const
{
return d->mWidget;
}
QLabel *FormTextInputBase::label() const
{
return d->mLabel;
}
QLabel *FormTextInputBase::hintLabel() const
{
return d->mHintLabel;
}
ErrorLabel *FormTextInputBase::errorLabel() const
{
return d->mErrorLabel;
}
void FormTextInputBase::setLabelText(const QString &text, const QString &accessibleName)
{
d->setLabelText(text, accessibleName);
}
void FormTextInputBase::setHint(const QString &text, const QString &accessibleDescription)
{
d->setHint(text, accessibleDescription);
}
void FormTextInputBase::setIsRequired(bool required)
{
d->mRequired = required;
d->updateLabel();
d->updateAccessibleNameAndDescription();
}
bool FormTextInputBase::isRequired() const
{
return d->mRequired;
}
void FormTextInputBase::setValidator(const std::shared_ptr<QValidator> &validator)
{
Q_ASSERT(!validator || !validator->parent());
d->mValidator = validator;
}
void FormTextInputBase::setValueRequiredErrorMessage(const QString &text, const QString &accessibleText)
{
if (text.isEmpty()) {
d->mValueRequiredErrorMessage = defaultValueRequiredErrorMessage();
} else {
d->mValueRequiredErrorMessage = text;
}
if (accessibleText.isEmpty()) {
d->mAccessibleValueRequiredErrorMessage = d->mValueRequiredErrorMessage;
} else {
d->mAccessibleValueRequiredErrorMessage = accessibleText;
}
}
void FormTextInputBase::setInvalidEntryErrorMessage(const QString &text, const QString &accessibleText)
{
if (text.isEmpty()) {
d->mInvalidEntryErrorMessage = defaultInvalidEntryErrorMessage();
} else {
d->mInvalidEntryErrorMessage = text;
}
if (accessibleText.isEmpty()) {
d->mAccessibleInvalidEntryErrorMessage = d->mInvalidEntryErrorMessage;
} else {
d->mAccessibleInvalidEntryErrorMessage = accessibleText;
}
}
void FormTextInputBase::setToolTip(const QString &toolTip)
{
if (d->mLabel) {
d->mLabel->setToolTip(toolTip);
}
if (d->mWidget) {
d->mWidget->setToolTip(toolTip);
}
}
void FormTextInputBase::setWidget(QWidget *widget)
{
auto parent = widget ? widget->parentWidget() : nullptr;
d->mWidget = widget;
d->mLabel = new QLabel{parent};
d->mLabel->setTextFormat(Qt::PlainText);
d->mLabel->setWordWrap(true);
QFont font = d->mLabel->font();
font.setBold(true);
d->mLabel->setFont(font);
d->mLabel->setBuddy(d->mWidget);
d->mHintLabel = new QLabel{parent};
d->mHintLabel->setWordWrap(true);
d->mHintLabel->setTextFormat(Qt::PlainText);
// set widget as buddy of hint label, so that the label isn't considered unrelated
d->mHintLabel->setBuddy(d->mWidget);
d->mHintLabel->setVisible(false);
d->mErrorLabel = new ErrorLabel{parent};
d->mErrorLabel->setWordWrap(true);
d->mErrorLabel->setTextFormat(Qt::PlainText);
// set widget as buddy of error label, so that the label isn't considered unrelated
d->mErrorLabel->setBuddy(d->mWidget);
d->mErrorLabel->setVisible(false);
connectWidget();
}
void FormTextInputBase::setEnabled(bool enabled)
{
if (d->mLabel) {
d->mLabel->setEnabled(enabled);
}
if (d->mWidget) {
d->mWidget->setEnabled(enabled);
}
if (d->mErrorLabel) {
d->mErrorLabel->setVisible(enabled && !d->mErrorLabel->text().isEmpty());
}
}
QString FormTextInputBase::currentError() const
{
if (d->mError) {
return d->errorMessage(d->mError);
}
return {};
}
bool FormTextInputBase::validate(const QString &text, int pos) const
{
QString textCopy = text;
if (d->mValidator && d->mValidator->validate(textCopy, pos) != QValidator::Acceptable)
{
return false;
}
return true;
}
void FormTextInputBase::onTextChanged()
{
d->mEditingInProgress = true;
d->updateError();
}
void FormTextInputBase::onEditingFinished()
{
d->mEditingInProgress = false;
d->updateError();
}
}
template<>
bool Kleo::FormTextInput<QLineEdit>::hasValue() const
{
const auto w = widget();
return w && !w->text().trimmed().isEmpty();
}
template<>
bool Kleo::FormTextInput<QLineEdit>::hasAcceptableInput() const
{
const auto w = widget();
return w && validate(w->text(), w->cursorPosition());
}
template<>
void Kleo::FormTextInput<QLineEdit>::connectWidget()
{
const auto w = widget();
QObject::connect(w, &QLineEdit::editingFinished,
w, [this]() { onEditingFinished(); });
QObject::connect(w, &QLineEdit::textChanged,
w, [this]() { onTextChanged(); });
}
diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp
index 28adbf571..59d2d37bb 100644
--- a/src/view/keylistcontroller.cpp
+++ b/src/view/keylistcontroller.cpp
@@ -1,861 +1,861 @@
/* -*- mode: c++; c-basic-offset:4 -*-
controllers/keylistcontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 Felix Tiede
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keylistcontroller.h"
#include "tabwidget.h"
#include <smartcard/readerstatus.h>
#include <utils/action_data.h>
#include <settings.h>
#include "tooltippreferences.h"
#include "kleopatra_debug.h"
#include "commands/exportcertificatecommand.h"
#include "commands/exportopenpgpcertstoservercommand.h"
#ifdef MAILAKONADI_ENABLED
#include "commands/exportopenpgpcerttoprovidercommand.h"
#endif // MAILAKONADI_ENABLED
#if QGPGME_SUPPORTS_SECRET_KEY_EXPORT
# include "commands/exportsecretkeycommand.h"
#else
# include "commands/exportsecretkeycommand_old.h"
#endif
#include "commands/importcertificatefromfilecommand.h"
#include "commands/changepassphrasecommand.h"
#include "commands/lookupcertificatescommand.h"
#include "commands/reloadkeyscommand.h"
#include "commands/refreshx509certscommand.h"
#include "commands/refreshopenpgpcertscommand.h"
#include "commands/detailscommand.h"
#include "commands/deletecertificatescommand.h"
#include "commands/decryptverifyfilescommand.h"
#include "commands/signencryptfilescommand.h"
#include "commands/signencryptfoldercommand.h"
#include "commands/clearcrlcachecommand.h"
#include "commands/dumpcrlcachecommand.h"
#include "commands/dumpcertificatecommand.h"
#include "commands/importcrlcommand.h"
#include "commands/changeexpirycommand.h"
#include "commands/changeownertrustcommand.h"
#include "commands/changeroottrustcommand.h"
#include "commands/certifycertificatecommand.h"
#include "commands/revokecertificationcommand.h"
#include "commands/adduseridcommand.h"
#include "commands/newcertificatesigningrequestcommand.h"
#include "commands/newopenpgpcertificatecommand.h"
#include "commands/checksumverifyfilescommand.h"
#include "commands/checksumcreatefilescommand.h"
#include "commands/exportpaperkeycommand.h"
#include "commands/revokekeycommand.h"
#include <Libkleo/Algorithm>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyListModel>
#include <Libkleo/Formatting>
#include <gpgme++/key.h>
#include <KActionCollection>
#include <KLocalizedString>
#include <QAbstractItemView>
#include <QPointer>
#include <QItemSelectionModel>
#include <QAction>
#include <algorithm>
#include <iterator>
// needed for GPGME_VERSION_NUMBER
#include <gpgme.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
using namespace GpgME;
#if !QGPGME_SUPPORTS_SECRET_KEY_EXPORT
using Kleo::Commands::Compat::ExportSecretKeyCommand;
#endif
class KeyListController::Private
{
friend class ::Kleo::KeyListController;
KeyListController *const q;
public:
explicit Private(KeyListController *qq);
~Private();
void connectView(QAbstractItemView *view);
void connectCommand(Command *cmd);
void connectTabWidget();
void disconnectTabWidget();
void addCommand(Command *cmd)
{
connectCommand(cmd);
commands.insert(std::lower_bound(commands.begin(), commands.end(), cmd), cmd);
}
void addView(QAbstractItemView *view)
{
connectView(view);
views.insert(std::lower_bound(views.begin(), views.end(), view), view);
}
void removeView(QAbstractItemView *view)
{
view->disconnect(q);
view->selectionModel()->disconnect(q);
views.erase(std::remove(views.begin(), views.end(), view), views.end());
}
public:
void slotDestroyed(QObject *o)
{
qCDebug(KLEOPATRA_LOG) << (void *)o;
views.erase(std::remove(views.begin(), views.end(), o), views.end());
commands.erase(std::remove(commands.begin(), commands.end(), o), commands.end());
}
void slotDoubleClicked(const QModelIndex &idx);
void slotActivated(const QModelIndex &idx);
void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_);
void slotContextMenu(const QPoint &pos);
void slotCommandFinished();
void slotAddKey(const Key &key);
void slotAboutToRemoveKey(const Key &key);
void slotActionTriggered(QAction *action);
void slotCurrentViewChanged(QAbstractItemView *view)
{
if (view && !std::binary_search(views.cbegin(), views.cend(), view)) {
qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!";
addView(view);
}
currentView = view;
q->enableDisableActions(view ? view->selectionModel() : nullptr);
}
private:
int toolTipOptions() const;
private:
static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm);
private:
struct action_item {
QPointer<QAction> action;
Command::Restrictions restrictions;
Command *(*createCommand)(QAbstractItemView *, KeyListController *);
};
std::vector<action_item> actions;
std::vector<QAbstractItemView *> views;
std::vector<Command *> commands;
QPointer<QWidget> parentWidget;
QPointer<TabWidget> tabWidget;
QPointer<QAbstractItemView> currentView;
QPointer<AbstractKeyListModel> flatModel, hierarchicalModel;
std::vector<QMetaObject::Connection> m_connections;
};
KeyListController::Private::Private(KeyListController *qq)
: q(qq),
actions(),
views(),
commands(),
parentWidget(),
tabWidget(),
flatModel(),
hierarchicalModel()
{
connect(KeyCache::instance().get(), &KeyCache::added, q, [this](const GpgME::Key &key) { slotAddKey(key); });
connect(KeyCache::instance().get(), &KeyCache::aboutToRemove, q, [this](const GpgME::Key &key) { slotAboutToRemoveKey(key); });
}
KeyListController::Private::~Private() {}
KeyListController::KeyListController(QObject *p)
: QObject(p), d(new Private(this))
{
}
KeyListController::~KeyListController() {}
void KeyListController::Private::slotAddKey(const Key &key)
{
// ### make model act on keycache directly...
if (flatModel) {
flatModel->addKey(key);
}
if (hierarchicalModel) {
hierarchicalModel->addKey(key);
}
}
void KeyListController::Private::slotAboutToRemoveKey(const Key &key)
{
// ### make model act on keycache directly...
if (flatModel) {
flatModel->removeKey(key);
}
if (hierarchicalModel) {
hierarchicalModel->removeKey(key);
}
}
void KeyListController::addView(QAbstractItemView *view)
{
if (!view || std::binary_search(d->views.cbegin(), d->views.cend(), view)) {
return;
}
d->addView(view);
}
void KeyListController::removeView(QAbstractItemView *view)
{
if (!view || !std::binary_search(d->views.cbegin(), d->views.cend(), view)) {
return;
}
d->removeView(view);
}
void KeyListController::setCurrentView(QAbstractItemView *view)
{
d->slotCurrentViewChanged(view);
}
std::vector<QAbstractItemView *> KeyListController::views() const
{
return d->views;
}
void KeyListController::setFlatModel(AbstractKeyListModel *model)
{
if (model == d->flatModel) {
return;
}
d->flatModel = model;
if (model) {
model->clear();
if (KeyCache::instance()->initialized()) {
model->addKeys(KeyCache::instance()->keys());
}
model->setToolTipOptions(d->toolTipOptions());
}
}
void KeyListController::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == d->hierarchicalModel) {
return;
}
d->hierarchicalModel = model;
if (model) {
model->clear();
if (KeyCache::instance()->initialized()) {
model->addKeys(KeyCache::instance()->keys());
}
model->setToolTipOptions(d->toolTipOptions());
}
}
void KeyListController::setTabWidget(TabWidget *tabWidget)
{
if (tabWidget == d->tabWidget) {
return;
}
d->disconnectTabWidget();
d->tabWidget = tabWidget;
d->connectTabWidget();
d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr);
}
void KeyListController::setParentWidget(QWidget *parent)
{
d->parentWidget = parent;
}
QWidget *KeyListController::parentWidget() const
{
return d->parentWidget;
}
void KeyListController::Private::connectTabWidget()
{
if (!tabWidget) {
return;
}
const auto views = tabWidget->views();
std::for_each(views.cbegin(), views.cend(),
[this](QAbstractItemView *view) { addView(view); });
m_connections.reserve(3);
m_connections.push_back(connect(tabWidget, &TabWidget::viewAdded, q, &KeyListController::addView));
m_connections.push_back(connect(tabWidget, &TabWidget::viewAboutToBeRemoved, q, &KeyListController::removeView));
m_connections.push_back(connect(tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) { slotCurrentViewChanged(view); }));
}
void KeyListController::Private::disconnectTabWidget()
{
if (!tabWidget) {
return;
}
for (const auto &connection : m_connections) {
disconnect(connection);
}
m_connections.clear();
const auto views = tabWidget->views();
std::for_each(views.cbegin(), views.cend(),
[this](QAbstractItemView *view) { removeView(view); });
}
AbstractKeyListModel *KeyListController::flatModel() const
{
return d->flatModel;
}
AbstractKeyListModel *KeyListController::hierarchicalModel() const
{
return d->hierarchicalModel;
}
QAbstractItemView *KeyListController::currentView() const
{
return d->currentView;
}
TabWidget *KeyListController::tabWidget() const
{
return d->tabWidget;
}
void KeyListController::createActions(KActionCollection *coll)
{
const std::vector<action_data> common_and_openpgp_action_data = {
// File menu
{
"file_new_certificate", i18n("New OpenPGP Key Pair..."), i18n("Create a new OpenPGP certificate"),
- "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N")
+ "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"),
},
{
"file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"),
- "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E")
+ "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"),
},
{
"file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"),
- "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E")
+ "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"),
},
#ifdef MAILAKONADI_ENABLED
{
"file_export_certificate_to_provider", i18n("Publish at Mail Provider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"),
- "view-certificate-export", nullptr, nullptr, QString()
+ "view-certificate-export", nullptr, nullptr, QString(),
},
#endif // MAILAKONADI_ENABLED
{
"file_export_secret_keys", i18n("Backup Secret Keys..."), QString(),
- "view-certificate-export-secret", nullptr, nullptr, QString()
+ "view-certificate-export-secret", nullptr, nullptr, QString(),
},
{
"file_export_paper_key", i18n("Print Secret Key..."), QString(),
- "document-print", nullptr, nullptr, QString()
+ "document-print", nullptr, nullptr, QString(),
},
{
"file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"),
- "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I")
+ "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"),
},
{
"file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"),
- "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I")
+ "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"),
},
{
"file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"),
- "document-edit-decrypt-verify", nullptr, nullptr, QString()
+ "document-edit-decrypt-verify", nullptr, nullptr, QString(),
},
{
"file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"),
- "document-edit-sign-encrypt", nullptr, nullptr, QString()
+ "document-edit-sign-encrypt", nullptr, nullptr, QString(),
},
{
"file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"),
- nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString()
+ nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString(),
},
{
"file_checksum_create_files", i18n("Create Checksum Files..."), QString(),
- nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString()
+ nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString(),
},
{
"file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(),
- nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString()
+ nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString(),
},
// View menu
{
"view_redisplay", i18n("Redisplay"), QString(),
- "view-refresh", nullptr, nullptr, QStringLiteral("F5")
+ "view-refresh", nullptr, nullptr, QStringLiteral("F5"),
},
{
"view_stop_operations", i18n("Stop Operation"), QString(),
- "process-stop", this, [this](bool) { cancelCommands(); }, QStringLiteral("Escape"), RegularQAction, Disabled
+ "process-stop", this, [this](bool) { cancelCommands(); }, QStringLiteral("Escape"), RegularQAction, Disabled,
},
{
"view_certificate_details", i18n("Details"), QString(),
- "dialog-information", nullptr, nullptr, QString()
+ "dialog-information", nullptr, nullptr, QString(),
},
// Certificate menu
#if QGPGME_SUPPORTS_KEY_REVOCATION
{
"certificates_revoke", i18n("Revoke Certificate..."), i18n("Revoke the selected OpenPGP certificate"),
- "view-certificate-revoke", nullptr, nullptr, {}
+ "view-certificate-revoke", nullptr, nullptr, {},
},
#endif
{
"certificates_delete", i18n("Delete"), i18n("Delete selected certificates"),
- "edit-delete", nullptr, nullptr, QStringLiteral("Delete")
+ "edit-delete", nullptr, nullptr, QStringLiteral("Delete"),
},
{
"certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"),
- "view-certificate-sign", nullptr, nullptr, QString()
+ "view-certificate-sign", nullptr, nullptr, QString(),
},
{
"certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"),
- "view-certificate-revoke", nullptr, nullptr, QString()
+ "view-certificate-revoke", nullptr, nullptr, QString(),
},
{
"certificates_change_expiry", i18n("Change End of Validity Period..."), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"certificates_change_owner_trust", i18nc("@action:inmenu", "Change Certification Power..."),
i18nc("@info:tooltip", "Grant or revoke the certification power of the selected certificate"),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"certificates_change_passphrase", i18n("Change Passphrase..."), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"certificates_add_userid", i18n("Add User ID..."), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
// Tools menu
{
"tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(),
- "view-refresh", nullptr, nullptr, QString()
+ "view-refresh", nullptr, nullptr, QString(),
},
// Window menu
// (come from TabWidget)
// Help menu
// (come from MainWindow)
};
static const action_data cms_create_csr_action_data = {
"file_new_certificate_signing_request", i18n("New S/MIME Certification Request..."), i18n("Create a new S/MIME certificate signing request (CSR)"),
"view-certificate-add", nullptr, nullptr, {},
};
static const std::vector<action_data> cms_action_data = {
// Certificate menu
{
"certificates_trust_root", i18n("Trust Root Certificate"), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"certificates_distrust_root", i18n("Distrust Root Certificate"), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"certificates_dump_certificate", i18n("Technical Details"), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
// Tools menu
{
"tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(),
- "view-refresh", nullptr, nullptr, QString()
+ "view-refresh", nullptr, nullptr, QString(),
},
{
"crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
{
"crl_import_crl", i18n("Import CRL From File..."), QString(),
- nullptr, nullptr, nullptr, QString()
+ nullptr, nullptr, nullptr, QString(),
},
};
std::vector<action_data> action_data = common_and_openpgp_action_data;
if (const Kleo::Settings settings{}; settings.cmsEnabled()) {
if (settings.cmsCertificateCreationAllowed()) {
action_data.push_back(cms_create_csr_action_data);
}
action_data.reserve(action_data.size() + cms_action_data.size());
std::copy(std::begin(cms_action_data), std::end(cms_action_data),
std::back_inserter(action_data));
}
make_actions_from_data(action_data, coll);
if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) {
connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled);
}
// ### somehow make this better...
registerActionForCommand<NewOpenPGPCertificateCommand>(coll->action(QStringLiteral("file_new_certificate")));
registerActionForCommand<NewCertificateSigningRequestCommand>(coll->action(QStringLiteral("file_new_certificate_signing_request")));
//---
registerActionForCommand<LookupCertificatesCommand>(coll->action(QStringLiteral("file_lookup_certificates")));
registerActionForCommand<ImportCertificateFromFileCommand>(coll->action(QStringLiteral("file_import_certificates")));
//---
registerActionForCommand<ExportCertificateCommand>(coll->action(QStringLiteral("file_export_certificates")));
registerActionForCommand<ExportSecretKeyCommand>(coll->action(QStringLiteral("file_export_secret_keys")));
registerActionForCommand<ExportPaperKeyCommand>(coll->action(QStringLiteral("file_export_paper_key")));
registerActionForCommand<ExportOpenPGPCertsToServerCommand>(coll->action(QStringLiteral("file_export_certificates_to_server")));
#ifdef MAILAKONADI_ENABLED
registerActionForCommand<ExportOpenPGPCertToProviderCommand>(coll->action(QStringLiteral("file_export_certificate_to_provider")));
#endif // MAILAKONADI_ENABLED
//---
registerActionForCommand<DecryptVerifyFilesCommand>(coll->action(QStringLiteral("file_decrypt_verify_files")));
registerActionForCommand<SignEncryptFilesCommand>(coll->action(QStringLiteral("file_sign_encrypt_files")));
registerActionForCommand<SignEncryptFolderCommand>(coll->action(QStringLiteral("file_sign_encrypt_folder")));
//---
registerActionForCommand<ChecksumCreateFilesCommand>(coll->action(QStringLiteral("file_checksum_create_files")));
registerActionForCommand<ChecksumVerifyFilesCommand>(coll->action(QStringLiteral("file_checksum_verify_files")));
registerActionForCommand<ReloadKeysCommand>(coll->action(QStringLiteral("view_redisplay")));
//coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data()
registerActionForCommand<DetailsCommand>(coll->action(QStringLiteral("view_certificate_details")));
registerActionForCommand<ChangeOwnerTrustCommand>(coll->action(QStringLiteral("certificates_change_owner_trust")));
registerActionForCommand<TrustRootCommand>(coll->action(QStringLiteral("certificates_trust_root")));
registerActionForCommand<DistrustRootCommand>(coll->action(QStringLiteral("certificates_distrust_root")));
//---
registerActionForCommand<CertifyCertificateCommand>(coll->action(QStringLiteral("certificates_certify_certificate")));
if (RevokeCertificationCommand::isSupported()) {
registerActionForCommand<RevokeCertificationCommand>(coll->action(QStringLiteral("certificates_revoke_certification")));
}
//---
registerActionForCommand<ChangeExpiryCommand>(coll->action(QStringLiteral("certificates_change_expiry")));
registerActionForCommand<ChangePassphraseCommand>(coll->action(QStringLiteral("certificates_change_passphrase")));
registerActionForCommand<AddUserIDCommand>(coll->action(QStringLiteral("certificates_add_userid")));
//---
#if QGPGME_SUPPORTS_KEY_REVOCATION
registerActionForCommand<RevokeKeyCommand>(coll->action(QStringLiteral("certificates_revoke")));
#endif
registerActionForCommand<DeleteCertificatesCommand>(coll->action(QStringLiteral("certificates_delete")));
//---
registerActionForCommand<DumpCertificateCommand>(coll->action(QStringLiteral("certificates_dump_certificate")));
registerActionForCommand<RefreshX509CertsCommand>(coll->action(QStringLiteral("tools_refresh_x509_certificates")));
registerActionForCommand<RefreshOpenPGPCertsCommand>(coll->action(QStringLiteral("tools_refresh_openpgp_certificates")));
//---
registerActionForCommand<ImportCrlCommand>(coll->action(QStringLiteral("crl_import_crl")));
//---
registerActionForCommand<ClearCrlCacheCommand>(coll->action(QStringLiteral("crl_clear_crl_cache")));
registerActionForCommand<DumpCrlCacheCommand>(coll->action(QStringLiteral("crl_dump_crl_cache")));
enableDisableActions(nullptr);
}
void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command * (*create)(QAbstractItemView *, KeyListController *))
{
if (!action) {
return;
}
Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow
const Private::action_item ai = {
action, restrictions, create
};
connect(action, &QAction::triggered, this, [this, action]() { d->slotActionTriggered(action); });
d->actions.push_back(ai);
}
void KeyListController::registerCommand(Command *cmd)
{
if (!cmd || std::binary_search(d->commands.cbegin(), d->commands.cend(), cmd)) {
return;
}
d->addCommand(cmd);
qCDebug(KLEOPATRA_LOG) << (void *)cmd;
if (d->commands.size() == 1) {
Q_EMIT commandsExecuting(true);
}
}
bool KeyListController::hasRunningCommands() const
{
return !d->commands.empty();
}
bool KeyListController::shutdownWarningRequired() const
{
return std::any_of(d->commands.cbegin(), d->commands.cend(), std::mem_fn(&Command::warnWhenRunningAtShutdown));
}
// slot
void KeyListController::cancelCommands()
{
std::for_each(d->commands.begin(), d->commands.end(), std::mem_fn(&Command::cancel));
}
void KeyListController::Private::connectView(QAbstractItemView *view)
{
connect(view, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); });
connect(view, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) { slotDoubleClicked(index); });
connect(view, &QAbstractItemView::activated, q, [this](const QModelIndex &index) { slotActivated(index); });
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged,
q, [this](const QItemSelection &oldSel, const QItemSelection &newSel) {
slotSelectionChanged(oldSel, newSel);
});
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) { slotContextMenu(pos); });
}
void KeyListController::Private::connectCommand(Command *cmd)
{
if (!cmd) {
return;
}
connect(cmd, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); });
connect(cmd, &Command::finished, q, [this] { slotCommandFinished(); });
//connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) );
connect(cmd, &Command::progress, q, &KeyListController::progress);
}
void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx)
{
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) {
return;
}
if (const auto *const keyListModel = dynamic_cast<KeyListModelInterface *>(view->model())) {
DetailsCommand *const c = new DetailsCommand{keyListModel->key(idx)};
c->setParentWidget(parentWidget ? parentWidget : view);
c->start();
}
}
void KeyListController::Private::slotActivated(const QModelIndex &idx)
{
Q_UNUSED(idx)
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) {
return;
}
}
void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_)
{
Q_UNUSED(old)
Q_UNUSED(new_)
const QItemSelectionModel *const sm = qobject_cast<QItemSelectionModel *>(q->sender());
if (!sm) {
return;
}
q->enableDisableActions(sm);
}
void KeyListController::Private::slotContextMenu(const QPoint &p)
{
QAbstractItemView *const view = qobject_cast<QAbstractItemView *>(q->sender());
if (view && std::binary_search(views.cbegin(), views.cend(), view)) {
Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p));
} else {
qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!";
}
}
void KeyListController::Private::slotCommandFinished()
{
Command *const cmd = qobject_cast<Command *>(q->sender());
if (!cmd || !std::binary_search(commands.cbegin(), commands.cend(), cmd)) {
return;
}
qCDebug(KLEOPATRA_LOG) << (void *)cmd;
if (commands.size() == 1) {
Q_EMIT q->commandsExecuting(false);
}
}
void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const
{
const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm);
for (const Private::action_item &ai : std::as_const(d->actions))
if (ai.action) {
ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask));
}
}
static bool all_secret_are_not_owner_trust_ultimate(const std::vector<Key> &keys)
{
for (const Key &key : keys)
if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) {
return false;
}
return true;
}
Command::Restrictions find_root_restrictions(const std::vector<Key> &keys)
{
bool trusted = false, untrusted = false;
for (const Key &key : keys)
if (key.isRoot())
if (key.userID(0).validity() == UserID::Ultimate) {
trusted = true;
} else {
untrusted = true;
}
else {
return Command::NoRestriction;
}
if (trusted)
if (untrusted) {
return Command::NoRestriction;
} else {
return Command::MustBeTrustedRoot;
}
else if (untrusted) {
return Command::MustBeUntrustedRoot;
} else {
return Command::NoRestriction;
}
}
Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm)
{
if (!sm) {
return Command::NoRestriction;
}
const KeyListModelInterface *const m = dynamic_cast<const KeyListModelInterface *>(sm->model());
if (!m) {
return Command::NoRestriction;
}
const std::vector<Key> keys = m->keys(sm->selectedRows());
if (keys.empty()) {
return Command::NoRestriction;
}
Command::Restrictions result = Command::NeedSelection;
if (keys.size() == 1) {
result |= Command::OnlyOneKey;
}
#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
const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret(); };
#else
// older versions of GpgME did not always set the secret flag for card keys
const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret() || k.subkey(0).isCardKey(); };
#endif
if (std::all_of(keys.cbegin(), keys.cend(), primaryKeyCanBeUsedForSecretKeyOperations)) {
result |= Command::NeedSecretKey;
}
if (std::all_of(std::begin(keys), std::end(keys), [](const auto &k) { return k.subkey(0).isSecret() && !k.subkey(0).isCardKey(); })) {
result |= Command::NeedSecretKeyData;
}
if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == OpenPGP; })) {
result |= Command::MustBeOpenPGP;
} else if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == CMS; })) {
result |= Command::MustBeCMS;
}
if (Kleo::all_of(keys, [](const auto &key) {
return !key.isBad();
})) {
result |= Command::MustBeValid;
}
if (all_secret_are_not_owner_trust_ultimate(keys)) {
result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate;
}
result |= find_root_restrictions(keys);
if (const ReaderStatus *rs = ReaderStatus::instance()) {
if (!rs->firstCardWithNullPin().empty()) {
result |= Command::AnyCardHasNullPin;
}
if (rs->anyCardCanLearnKeys()) {
result |= Command::AnyCardCanLearnKeys;
}
}
return result;
}
void KeyListController::Private::slotActionTriggered(QAction *sender)
{
const auto it = std::find_if(actions.cbegin(), actions.cend(),
[sender](const action_item &item) { return item.action == sender; });
if (it != actions.end())
if (Command *const c = it->createCommand(this->currentView, q)) {
if (parentWidget) {
c->setParentWidget(parentWidget);
}
c->start();
} else
qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \""
<< qPrintable(sender->objectName()) << "\"";
else {
qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(sender->objectName());
}
}
int KeyListController::Private::toolTipOptions() const
{
using namespace Kleo::Formatting;
static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage;
static const int ownerFlags = Subject | UserIDs | OwnerTrust;
static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint;
const TooltipPreferences prefs;
int flags = KeyID;
flags |= prefs.showValidity() ? validityFlags : 0;
flags |= prefs.showOwnerInformation() ? ownerFlags : 0;
flags |= prefs.showCertificateDetails() ? detailsFlags : 0;
return flags;
}
void KeyListController::updateConfig()
{
const int opts = d->toolTipOptions();
if (d->flatModel) {
d->flatModel->setToolTipOptions(opts);
}
if (d->hierarchicalModel) {
d->hierarchicalModel->setToolTipOptions(opts);
}
}
#include "moc_keylistcontroller.cpp"
diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp
index 71bc77cbf..98bb6ec19 100644
--- a/src/view/keytreeview.cpp
+++ b/src/view/keytreeview.cpp
@@ -1,728 +1,727 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/keytreeview.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "keytreeview.h"
#include "searchbar.h"
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/KeyRearrangeColumnsProxyModel>
#include <Libkleo/NavigatableTreeView>
#include <Libkleo/Predicates>
#include "utils/headerview.h"
#include "utils/tags.h"
#include <Libkleo/Stl_Util>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyCache>
#include <gpgme++/key.h>
#include "kleopatra_debug.h"
#include <QTimer>
#include <QHeaderView>
#include <QItemSelectionModel>
#include <QItemSelection>
#include <QLayout>
#include <QList>
#include <QMenu>
#include <QAction>
#include <QEvent>
#include <QContextMenuEvent>
#include <KSharedConfig>
#include <KLocalizedString>
#define TAGS_COLUMN 13
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
namespace
{
class TreeView : public NavigatableTreeView
{
public:
explicit TreeView(QWidget *parent = nullptr)
: NavigatableTreeView{parent}
{
header()->installEventFilter(this);
}
QSize minimumSizeHint() const override
{
const QSize min = QTreeView::minimumSizeHint();
return QSize(min.width(), min.height() + 5 * fontMetrics().height());
}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched)
if (event->type() == QEvent::ContextMenu) {
auto e = static_cast<QContextMenuEvent *>(event);
if (!mHeaderPopup) {
mHeaderPopup = new QMenu(this);
mHeaderPopup->setTitle(i18n("View Columns"));
for (int i = 0; i < model()->columnCount(); ++i) {
QAction *tmp
= mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
tmp->setData(QVariant(i));
tmp->setCheckable(true);
mColumnActions << tmp;
}
connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) {
const int col = action->data().toInt();
if ((col == TAGS_COLUMN) && action->isChecked()) {
Tags::enableTags();
}
if (action->isChecked()) {
showColumn(col);
} else {
hideColumn(col);
}
auto tv = qobject_cast<KeyTreeView *> (parent());
if (tv) {
tv->resizeColumns();
}
});
}
for (QAction *action : std::as_const(mColumnActions)) {
const int column = action->data().toInt();
action->setChecked(!isColumnHidden(column));
}
mHeaderPopup->popup(mapToGlobal(e->pos()));
return true;
}
return false;
}
void focusInEvent(QFocusEvent *event) override
{
QTreeView::focusInEvent(event);
// queue the invokation, so that it happens after the widget itself got focus
QMetaObject::invokeMethod(this, &TreeView::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection);
}
private:
void forceAccessibleFocusEventForCurrentItem()
{
// force Qt to send a focus event for the current item to accessibility
// tools; otherwise, the user has no idea which item is selected when the
// list gets keyboard input focus
const auto current = currentIndex();
setCurrentIndex({});
setCurrentIndex(current);
}
private:
QMenu *mHeaderPopup = nullptr;
QList<QAction *> mColumnActions;
};
const KeyListModelInterface * keyListModel(const QTreeView &view)
{
const KeyListModelInterface *const klmi = dynamic_cast<KeyListModelInterface *>(view.model());
Q_ASSERT(klmi);
return klmi;
}
} // anon namespace
KeyTreeView::KeyTreeView(QWidget *parent)
: QWidget(parent),
m_proxy(new KeyListSortFilterProxyModel(this)),
m_additionalProxy(nullptr),
m_view(new TreeView(this)),
m_flatModel(nullptr),
m_hierarchicalModel(nullptr),
m_stringFilter(),
m_keyFilter(),
m_isHierarchical(true)
{
init();
}
KeyTreeView::KeyTreeView(const KeyTreeView &other)
: QWidget(nullptr),
m_proxy(new KeyListSortFilterProxyModel(this)),
m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr),
m_view(new TreeView(this)),
m_flatModel(other.m_flatModel),
m_hierarchicalModel(other.m_hierarchicalModel),
m_stringFilter(other.m_stringFilter),
m_keyFilter(other.m_keyFilter),
m_group(other.m_group),
m_isHierarchical(other.m_isHierarchical)
{
init();
setColumnSizes(other.columnSizes());
setSortColumn(other.sortColumn(), other.sortOrder());
}
KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr<KeyFilter> &kf,
AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent,
const KConfigGroup &group)
: QWidget(parent),
m_proxy(new KeyListSortFilterProxyModel(this)),
m_additionalProxy(proxy),
m_view(new TreeView(this)),
m_flatModel(nullptr),
m_hierarchicalModel(nullptr),
m_stringFilter(text),
m_keyFilter(kf),
m_group(group),
m_isHierarchical(true),
m_onceResized(false)
{
init();
}
void KeyTreeView::setColumnSizes(const std::vector<int> &sizes)
{
if (sizes.empty()) {
return;
}
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
hv->setSectionSizes(sizes);
}
}
void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder)
{
Q_ASSERT(m_view);
m_view->sortByColumn(sortColumn, sortOrder);
}
int KeyTreeView::sortColumn() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorSection();
}
Qt::SortOrder KeyTreeView::sortOrder() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
return m_view->header()->sortIndicatorOrder();
}
std::vector<int> KeyTreeView::columnSizes() const
{
Q_ASSERT(m_view);
Q_ASSERT(m_view->header());
Q_ASSERT(qobject_cast<HeaderView *>(m_view->header()) == static_cast<HeaderView *>(m_view->header()));
if (auto const hv = static_cast<HeaderView *>(m_view->header())) {
return hv->sectionSizes();
} else {
return std::vector<int>();
}
}
void KeyTreeView::init()
{
KDAB_SET_OBJECT_NAME(m_proxy);
KDAB_SET_OBJECT_NAME(m_view);
if (m_group.isValid()) {
// Reopen as non const
KConfig *conf = m_group.config();
m_group = conf->group(m_group.name());
}
if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) {
KDAB_SET_OBJECT_NAME(m_additionalProxy);
}
QLayout *layout = new QVBoxLayout(this);
KDAB_SET_OBJECT_NAME(layout);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_view);
auto headerView = new HeaderView(Qt::Horizontal);
KDAB_SET_OBJECT_NAME(headerView);
headerView->installEventFilter(m_view);
headerView->setSectionsMovable(true);
m_view->setHeader(headerView);
m_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_view->setAllColumnsShowFocus(false);
m_view->setSortingEnabled(true);
m_view->setAccessibleName(i18n("Certificates"));
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
// we show details on double-click
m_view->setExpandsOnDoubleClick(false);
if (model()) {
if (m_additionalProxy) {
m_additionalProxy->setSourceModel(model());
} else {
m_proxy->setSourceModel(model());
}
}
if (m_additionalProxy) {
m_proxy->setSourceModel(m_additionalProxy);
if (!m_additionalProxy->parent()) {
m_additionalProxy->setParent(this);
}
}
m_proxy->setFilterRegularExpression(QRegularExpression::escape(m_stringFilter));
m_proxy->setKeyFilter(m_keyFilter);
m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
auto rearangingModel = new KeyRearrangeColumnsProxyModel(this);
rearangingModel->setSourceModel(m_proxy);
- rearangingModel->setSourceColumns(QVector<int>() << KeyList::PrettyName
- << KeyList::PrettyEMail
- << KeyList::Validity
- << KeyList::ValidFrom
- << KeyList::ValidUntil
- << KeyList::TechnicalDetails
- << KeyList::KeyID
- << KeyList::Fingerprint
- << KeyList::OwnerTrust
- << KeyList::Origin
- << KeyList::LastUpdate
- << KeyList::Issuer
- << KeyList::SerialNumber
- // If a column is added before this TAGS_COLUMN define has to be updated accordingly
- << KeyList::Remarks
- );
+ rearangingModel->setSourceColumns(QVector<int>() << KeyList::PrettyName //
+ << KeyList::PrettyEMail //
+ << KeyList::Validity //
+ << KeyList::ValidFrom //
+ << KeyList::ValidUntil //
+ << KeyList::TechnicalDetails //
+ << KeyList::KeyID //
+ << KeyList::Fingerprint //
+ << KeyList::OwnerTrust //
+ << KeyList::Origin //
+ << KeyList::LastUpdate //
+ << KeyList::Issuer //
+ << KeyList::SerialNumber //
+ // If a column is added before this TAGS_COLUMN define has to be updated accordingly
+ << KeyList::Remarks);
m_view->setModel(rearangingModel);
/* Handle expansion state */
if (m_group.isValid()) {
m_expandedKeys = m_group.readEntry("Expanded", QStringList());
}
connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
const auto fpr = QString::fromLatin1(key.primaryFingerprint());
if (m_expandedKeys.contains(fpr)) {
return;
}
m_expandedKeys << fpr;
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) {
if (!index.isValid()) {
return;
}
const auto &key = index.data(KeyList::KeyRole).value<GpgME::Key>();
if (key.isNull()) {
return;
}
m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint()));
if (m_group.isValid()) {
m_group.writeEntry("Expanded", m_expandedKeys);
}
});
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () {
/* We use a single shot timer here to ensure that the keysMayHaveChanged
* handlers are all handled before we restore the expand state so that
* the model is already populated. */
QTimer::singleShot(0, [this] () {
restoreExpandState();
setUpTagKeys();
if (!m_onceResized) {
m_onceResized = true;
resizeColumns();
}
});
});
resizeColumns();
if (m_group.isValid()) {
restoreLayout(m_group);
}
}
void KeyTreeView::restoreExpandState()
{
if (!KeyCache::instance()->initialized()) {
qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting.";
return;
}
for (const auto &fpr: std::as_const(m_expandedKeys)) {
const KeyListModelInterface *const km = keyListModel(*m_view);
if (!km) {
qCWarning(KLEOPATRA_LOG) << "invalid model";
return;
}
const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData());
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache";
m_expandedKeys.removeAll(fpr);
return;
}
const auto idx = km->index(key);
if (!idx.isValid()) {
qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model";
m_expandedKeys.removeAll(fpr);
return;
}
m_view->expand(idx);
}
}
void KeyTreeView::setUpTagKeys()
{
const auto tagKeys = Tags::tagKeys();
if (m_hierarchicalModel) {
m_hierarchicalModel->setRemarkKeys(tagKeys);
}
if (m_flatModel) {
m_flatModel->setRemarkKeys(tagKeys);
}
}
void KeyTreeView::saveLayout(KConfigGroup &group)
{
QHeaderView *header = m_view->header();
QVariantList columnVisibility;
QVariantList columnOrder;
QVariantList columnWidths;
const int headerCount = header->count();
columnVisibility.reserve(headerCount);
columnWidths.reserve(headerCount);
columnOrder.reserve(headerCount);
for (int i = 0; i < headerCount; ++i) {
columnVisibility << QVariant(!m_view->isColumnHidden(i));
columnWidths << QVariant(header->sectionSize(i));
columnOrder << QVariant(header->visualIndex(i));
}
group.writeEntry("ColumnVisibility", columnVisibility);
group.writeEntry("ColumnOrder", columnOrder);
group.writeEntry("ColumnWidths", columnWidths);
group.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
if (header->isSortIndicatorShown()) {
group.writeEntry("SortColumn", header->sortIndicatorSection());
} else {
group.writeEntry("SortColumn", -1);
}
}
void KeyTreeView::restoreLayout(const KConfigGroup &group)
{
QHeaderView *header = m_view->header();
QVariantList columnVisibility = group.readEntry("ColumnVisibility", QVariantList());
QVariantList columnOrder = group.readEntry("ColumnOrder", QVariantList());
QVariantList columnWidths = group.readEntry("ColumnWidths", QVariantList());
if (columnVisibility.isEmpty()) {
// if config is empty then use default settings
// The numbers have to be in line with the order in
// setsSourceColumns above
m_view->hideColumn(5);
for (int i = 7; i < m_view->model()->columnCount(); ++i) {
m_view->hideColumn(i);
}
if (KeyCache::instance()->initialized()) {
QTimer::singleShot(0, this, &KeyTreeView::resizeColumns);
}
} else {
for (int i = 0; i < header->count(); ++i) {
if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) {
// An additional column that was not around last time we saved.
// We default to hidden.
m_view->hideColumn(i);
continue;
}
bool visible = columnVisibility[i].toBool();
int width = columnWidths[i].toInt();
int order = columnOrder[i].toInt();
header->resizeSection(i, width ? width : 100);
header->moveSection(header->visualIndex(i), order);
if ((i == TAGS_COLUMN) && visible) {
Tags::enableTags();
}
if (!visible) {
m_view->hideColumn(i);
}
}
m_onceResized = true;
}
int sortOrder = group.readEntry("SortAscending", (int)Qt::AscendingOrder);
int sortColumn = group.readEntry("SortColumn", 0);
if (sortColumn >= 0) {
m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
}
}
KeyTreeView::~KeyTreeView()
{
if (m_group.isValid()) {
saveLayout(m_group);
}
}
static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm)
{
Q_ASSERT(pm);
while (auto const sm = qobject_cast<QAbstractProxyModel *>(pm->sourceModel())) {
pm = sm;
}
return pm;
}
void KeyTreeView::setFlatModel(AbstractKeyListModel *model)
{
if (model == m_flatModel) {
return;
}
m_flatModel = model;
if (!m_isHierarchical)
// TODO: this fails when called after setHierarchicalView( false )...
{
find_last_proxy(m_proxy)->setSourceModel(model);
}
}
void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == m_hierarchicalModel) {
return;
}
m_hierarchicalModel = model;
if (m_isHierarchical) {
find_last_proxy(m_proxy)->setSourceModel(model);
m_view->expandAll();
for (int column = 0; column < m_view->header()->count(); ++column) {
m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column)));
}
}
}
void KeyTreeView::setStringFilter(const QString &filter)
{
if (filter == m_stringFilter) {
return;
}
m_stringFilter = filter;
m_proxy->setFilterRegularExpression(QRegularExpression::escape(filter));
Q_EMIT stringFilterChanged(filter);
}
void KeyTreeView::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) {
return;
}
m_keyFilter = filter;
m_proxy->setKeyFilter(filter);
Q_EMIT keyFilterChanged(filter);
}
namespace
{
QItemSelection itemSelectionFromKeys(const std::vector<Key> &keys, const QTreeView &view)
{
const QModelIndexList indexes = keyListModel(view)->indexes(keys);
return std::accumulate(
indexes.cbegin(), indexes.cend(),
QItemSelection(),
[] (QItemSelection selection, const QModelIndex &index) {
if (index.isValid()) {
selection.merge(QItemSelection(index, index), QItemSelectionModel::Select);
}
return selection;
});
}
}
void KeyTreeView::selectKeys(const std::vector<Key> &keys)
{
m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
std::vector<Key> KeyTreeView::selectedKeys() const
{
return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows());
}
void KeyTreeView::setHierarchicalView(bool on)
{
if (on == m_isHierarchical) {
return;
}
if (on && !hierarchicalModel()) {
qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set";
return;
}
if (!on && !flatModel()) {
qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set";
return;
}
const std::vector<Key> selectedKeys = this->selectedKeys();
const Key currentKey = keyListModel(*m_view)->key(m_view->currentIndex());
m_isHierarchical = on;
find_last_proxy(m_proxy)->setSourceModel(model());
if (on) {
m_view->expandAll();
}
selectKeys(selectedKeys);
if (!currentKey.isNull()) {
const QModelIndex currentIndex = keyListModel(*m_view)->index(currentKey);
if (currentIndex.isValid()) {
m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
m_view->scrollTo(currentIndex);
}
}
m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates"));
Q_EMIT hierarchicalChanged(on);
}
void KeyTreeView::setKeys(const std::vector<Key> &keys)
{
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
m_keys = sorted;
if (m_flatModel) {
m_flatModel->setKeys(sorted);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->setKeys(sorted);
}
}
void KeyTreeView::addKeysImpl(const std::vector<Key> &keys, bool select)
{
if (keys.empty()) {
return;
}
if (m_keys.empty()) {
setKeys(keys);
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys = _detail::union_by_fpr(sorted, m_keys);
m_keys.swap(newKeys);
if (m_flatModel) {
m_flatModel->addKeys(sorted);
}
if (m_hierarchicalModel) {
m_hierarchicalModel->addKeys(sorted);
}
if (select) {
selectKeys(sorted);
}
}
void KeyTreeView::addKeysSelected(const std::vector<Key> &keys)
{
addKeysImpl(keys, true);
}
void KeyTreeView::addKeysUnselected(const std::vector<Key> &keys)
{
addKeysImpl(keys, false);
}
void KeyTreeView::removeKeys(const std::vector<Key> &keys)
{
if (keys.empty()) {
return;
}
std::vector<Key> sorted = keys;
_detail::sort_by_fpr(sorted);
_detail::remove_duplicates_by_fpr(sorted);
std::vector<Key> newKeys;
newKeys.reserve(m_keys.size());
std::set_difference(m_keys.begin(), m_keys.end(),
sorted.begin(), sorted.end(),
std::back_inserter(newKeys),
_detail::ByFingerprint<std::less>());
m_keys.swap(newKeys);
if (m_flatModel) {
std::for_each(sorted.cbegin(), sorted.cend(),
[this](const Key &key) { m_flatModel->removeKey(key); });
}
if (m_hierarchicalModel) {
std::for_each(sorted.cbegin(), sorted.cend(),
[this](const Key &key) { m_hierarchicalModel->removeKey(key); });
}
}
void KeyTreeView::disconnectSearchBar()
{
for (const auto &connection : m_connections) {
disconnect(connection);
}
m_connections.clear();
}
bool KeyTreeView::connectSearchBar(const SearchBar *bar)
{
m_connections.reserve(4);
m_connections.push_back(connect(this, &KeyTreeView::stringFilterChanged, bar, &SearchBar::setStringFilter));
m_connections.push_back(connect(bar, &SearchBar::stringFilterChanged, this, &KeyTreeView::setStringFilter));
m_connections.push_back(connect(this, &KeyTreeView::keyFilterChanged, bar, &SearchBar::setKeyFilter));
m_connections.push_back(connect(bar, &SearchBar::keyFilterChanged, this, &KeyTreeView::setKeyFilter));
return std::all_of(m_connections.cbegin(), m_connections.cend(), [](const QMetaObject::Connection &conn) {
return conn;
});
}
void KeyTreeView::resizeColumns()
{
m_view->setColumnWidth(KeyList::PrettyName, 260);
m_view->setColumnWidth(KeyList::PrettyEMail, 260);
for (int i = 2; i < m_view->model()->columnCount(); ++i) {
m_view->resizeColumnToContents(i);
}
}
diff --git a/src/view/netkeywidget.cpp b/src/view/netkeywidget.cpp
index 31480f7eb..5d944207f 100644
--- a/src/view/netkeywidget.cpp
+++ b/src/view/netkeywidget.cpp
@@ -1,339 +1,339 @@
/* view/netkeywidget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2017 Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "netkeywidget.h"
#include "nullpinwidget.h"
#include "keytreeview.h"
#include "kleopatraapplication.h"
#include "systrayicon.h"
#include "kleopatra_debug.h"
#include "smartcard/netkeycard.h"
#include "smartcard/readerstatus.h"
#include "commands/changepincommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/learncardkeyscommand.h"
#include "commands/detailscommand.h"
#include <Libkleo/Algorithm>
#include <Libkleo/Compliance>
#include <Libkleo/KeyListModel>
#include <KConfigGroup>
#include <KSharedConfig>
#include <QInputDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QPushButton>
#include <QTreeView>
#include <KLocalizedString>
#include <KMessageBox>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
using namespace Kleo;
using namespace Kleo::SmartCard;
using namespace Kleo::Commands;
NetKeyWidget::NetKeyWidget(QWidget *parent) :
QWidget(parent),
mSerialNumberLabel(new QLabel(this)),
mVersionLabel(new QLabel(this)),
mLearnKeysLabel(new QLabel(this)),
mErrorLabel(new QLabel(this)),
mNullPinWidget(new NullPinWidget(this)),
mLearnKeysBtn(new QPushButton(this)),
mChangeNKSPINBtn(new QPushButton(this)),
mChangeSigGPINBtn(new QPushButton(this)),
mTreeView(new KeyTreeView(this)),
mArea(new QScrollArea)
{
auto vLay = new QVBoxLayout;
// Set up the scroll are
mArea->setFrameShape(QFrame::NoFrame);
mArea->setWidgetResizable(true);
auto mAreaWidget = new QWidget;
mAreaWidget->setLayout(vLay);
mArea->setWidget(mAreaWidget);
auto scrollLay = new QVBoxLayout(this);
scrollLay->setContentsMargins(0, 0, 0, 0);
scrollLay->addWidget(mArea);
// Add general widgets
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft);
mSerialNumberLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
auto hLay1 = new QHBoxLayout;
hLay1->addWidget(new QLabel(i18n("Serial number:")));
hLay1->addWidget(mSerialNumberLabel);
hLay1->addStretch(1);
vLay->addLayout(hLay1);
vLay->addWidget(mNullPinWidget);
auto line1 = new QFrame();
line1->setFrameShape(QFrame::HLine);
vLay->addWidget(line1);
vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Certificates:"))), 0, Qt::AlignLeft);
mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card."));
mLearnKeysBtn->setText(i18nc("@action", "Load Certificates"));
connect(mLearnKeysBtn, &QPushButton::clicked, this, [this] () {
mLearnKeysBtn->setEnabled(false);
auto cmd = new LearnCardKeysCommand(GpgME::CMS);
cmd->setParentWidget(this);
cmd->start();
auto icon = KleopatraApplication::instance()->sysTrayIcon();
if (icon) {
icon->setLearningInProgress(true);
}
connect(cmd, &Command::finished, this, [icon] () {
ReaderStatus::mutableInstance()->updateStatus();
icon->setLearningInProgress(false);
});
});
auto hLay2 = new QHBoxLayout;
hLay2->addWidget(mLearnKeysLabel);
hLay2->addWidget(mLearnKeysBtn);
hLay2->addStretch(1);
vLay->addLayout(hLay2);
mErrorLabel->setVisible(false);
vLay->addWidget(mErrorLabel);
// The certificate view
mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView));
mTreeView->setHierarchicalView(true);
connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this] (const QModelIndex &idx) {
const auto klm = dynamic_cast<KeyListModelInterface *> (mTreeView->view()->model());
if (!klm) {
qCDebug(KLEOPATRA_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className();
return;
}
auto cmd = new DetailsCommand(klm->key(idx));
cmd->setParentWidget(this);
cmd->start();
});
vLay->addWidget(mTreeView);
// The action area
auto line2 = new QFrame();
line2->setFrameShape(QFrame::HLine);
vLay->addWidget(line2);
vLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))), 0, Qt::AlignLeft);
auto actionLayout = new QHBoxLayout();
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &NetKeyWidget::createKeyFromCardKeys);
}
if (!(engineInfo(GpgME::GpgSMEngine).engineVersion() < "2.2.26")) { // see https://dev.gnupg.org/T5184
mCreateCSRButton = new QPushButton(this);
mCreateCSRButton->setText(i18nc("@action:button", "Create CSR"));
mCreateCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for a key stored on the card."));
mCreateCSRButton->setEnabled(false);
actionLayout->addWidget(mCreateCSRButton);
connect(mCreateCSRButton, &QPushButton::clicked, this, [this] () { createCSR(); });
}
mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN"));
mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN"));
connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::nksPinKeyRef()); });
connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this] () { doChangePin(NetKeyCard::sigGPinKeyRef()); });
actionLayout->addWidget(mChangeNKSPINBtn);
actionLayout->addWidget(mChangeSigGPINBtn);
actionLayout->addStretch(1);
vLay->addLayout(actionLayout);
vLay->addStretch(1);
const KConfigGroup configGroup(KSharedConfig::openConfig(), "NetKeyCardView");
mTreeView->restoreLayout(configGroup);
}
NetKeyWidget::~NetKeyWidget()
{
KConfigGroup configGroup(KSharedConfig::openConfig(), "NetKeyCardView");
mTreeView->saveLayout(configGroup);
}
namespace
{
std::vector<KeyPairInfo> getKeysSuitableForCSRCreation(const NetKeyCard *netKeyCard)
{
if (netKeyCard->hasNKSNullPin()) {
return {};
}
std::vector<KeyPairInfo> keys;
Kleo::copy_if(netKeyCard->keyInfos(), std::back_inserter(keys), [](const auto &keyInfo) {
if (keyInfo.keyRef.substr(0, 9) == "NKS-SIGG.") {
// SigG certificates for qualified signatures are issued with the physical cards;
// it's not possible to request a certificate for them
return false;
}
- return keyInfo.canSign()
- && (keyInfo.keyRef.substr(0, 9) == "NKS-NKS3.")
+ return keyInfo.canSign() //
+ && (keyInfo.keyRef.substr(0, 9) == "NKS-NKS3.") //
&& DeVSCompliance::algorithmIsCompliant(keyInfo.algorithm);
});
return keys;
}
}
void NetKeyWidget::setCard(const NetKeyCard* card)
{
mSerialNumber = card->serialNumber();
mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion()));
mSerialNumberLabel->setText(card->displaySerialNumber());
mNullPinWidget->setSerialNumber(mSerialNumber);
/* According to users of NetKey Cards it is fairly uncommon
* to use SigG Certificates at all. So it should be optional to set the pins. */
mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/);
mNullPinWidget->setSigGVisible(false/*card->hasSigGNullPin()*/);
mNullPinWidget->setNKSVisible(card->hasNKSNullPin());
mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin());
if (card->hasSigGNullPin()) {
mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card",
"Set SigG PIN"));
} else {
mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card",
"Change SigG PIN"));
}
mLearnKeysBtn->setEnabled(true);
mLearnKeysBtn->setVisible(card->canLearnKeys());
mTreeView->setVisible(!card->canLearnKeys());
mLearnKeysLabel->setVisible(card->canLearnKeys());
const auto errMsg = card->errorMsg();
if (!errMsg.isEmpty()) {
mErrorLabel->setText(QStringLiteral("<b>%1:</b> %2").arg(i18n("Error"), errMsg));
mErrorLabel->setVisible(true);
} else {
mErrorLabel->setVisible(false);
}
const auto keys = card->keys();
mTreeView->setKeys(keys);
if (mKeyForCardKeysButton) {
mKeyForCardKeysButton->setEnabled(!card->hasNKSNullPin()
&& card->hasSigningKey()
&& card->hasEncryptionKey()
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
if (mCreateCSRButton) {
mCreateCSRButton->setEnabled(!getKeysSuitableForCSRCreation(card).empty());
}
}
void NetKeyWidget::doChangePin(const std::string &keyRef)
{
const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
if (!netKeyCard) {
KMessageBox::error(this,
i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
return;
}
auto cmd = new ChangePinCommand(mSerialNumber, NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
- if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin())
+ if ((keyRef == NetKeyCard::nksPinKeyRef() && netKeyCard->hasNKSNullPin()) //
|| (keyRef == NetKeyCard::sigGPinKeyRef() && netKeyCard->hasSigGNullPin())) {
cmd->setMode(ChangePinCommand::NullPinMode);
}
cmd->start();
}
void NetKeyWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mSerialNumber, NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
namespace
{
std::string getKeyRef(const std::vector<KeyPairInfo> &keys, QWidget *parent)
{
QStringList options;
for (const auto &key : keys) {
options << QStringLiteral("%1 - %2").arg(QString::fromStdString(key.keyRef), QString::fromStdString(key.grip));
}
bool ok;
const QString choice = QInputDialog::getItem(parent, i18n("Select Key"),
i18n("Please select the key you want to create a certificate signing request for:"), options, /* current= */ 0, /* editable= */ false, &ok);
return ok ? keys[options.indexOf(choice)].keyRef : std::string();
}
}
void NetKeyWidget::createCSR()
{
const auto netKeyCard = ReaderStatus::instance()->getCard<NetKeyCard>(mSerialNumber);
if (!netKeyCard) {
KMessageBox::error(this,
i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(mSerialNumber)));
return;
}
const auto suitableKeys = getKeysSuitableForCSRCreation(netKeyCard.get());
if (suitableKeys.empty()) {
KMessageBox::error(this,
i18n("Sorry! No keys suitable for creating a certificate signing request found on the smartcard."));
return;
}
const auto keyRef = getKeyRef(suitableKeys, this);
if (keyRef.empty()) {
return;
}
auto cmd = new CreateCSRForCardKeyCommand(keyRef, mSerialNumber, NetKeyCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
diff --git a/src/view/p15cardwidget.cpp b/src/view/p15cardwidget.cpp
index 60d364033..dc2c8dde2 100644
--- a/src/view/p15cardwidget.cpp
+++ b/src/view/p15cardwidget.cpp
@@ -1,195 +1,195 @@
/* view/p15cardwiget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-FileContributor: Andre Heinecke <aheinecke@g10code.com>
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "p15cardwidget.h"
#include "openpgpkeycardwidget.h"
#include "settings.h"
#include "smartcard/p15card.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/readerstatus.h"
#include "commands/learncardkeyscommand.h"
#include <QVBoxLayout>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QStringList>
#include <QPushButton>
#include <KLocalizedString>
#include <KSeparator>
#include <Libkleo/Compat>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <QGpgME/Protocol>
#include <QGpgME/KeyListJob>
#include <QGpgME/ImportFromKeyserverJob>
#include <QGpgME/CryptoConfig>
#include <gpgme++/keylistresult.h>
#include <gpgme++/importresult.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace Kleo::SmartCard;
using namespace Kleo::Commands;
P15CardWidget::P15CardWidget(QWidget *parent)
: QWidget{parent}
, mVersionLabel{new QLabel{this}}
, mSerialNumber{new QLabel{this}}
, mStatusLabel{new QLabel{this}}
, mOpenPGPKeysSection{new QWidget{this}}
, mOpenPGPKeysWidget{new OpenPGPKeyCardWidget{this}}
{
// Set up the scroll area
auto myLayout = new QVBoxLayout(this);
myLayout->setContentsMargins(0, 0, 0, 0);
auto area = new QScrollArea;
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
myLayout->addWidget(area);
auto areaWidget = new QWidget;
area->setWidget(areaWidget);
auto areaVLay = new QVBoxLayout(areaWidget);
auto cardInfoGrid = new QGridLayout;
{
int row = 0;
// Version and Serialnumber
cardInfoGrid->addWidget(mVersionLabel, row++, 0, 1, 2);
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard);
cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
cardInfoGrid->addWidget(mSerialNumber, row++, 1);
mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard);
cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1);
}
areaVLay->addLayout(cardInfoGrid);
areaVLay->addWidget(mStatusLabel);
mStatusLabel->setVisible(false);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
{
auto l = new QVBoxLayout{mOpenPGPKeysSection};
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("OpenPGP keys:"))));
mOpenPGPKeysWidget->setAllowedActions(OpenPGPKeyCardWidget::NoAction);
l->addWidget(mOpenPGPKeysWidget);
l->addWidget(new KSeparator(Qt::Horizontal));
}
mOpenPGPKeysSection->setVisible(false);
areaVLay->addWidget(mOpenPGPKeysSection);
areaVLay->addStretch(1);
}
P15CardWidget::~P15CardWidget() = default;
void P15CardWidget::searchPGPFpr(const std::string &fpr)
{
/* Only do auto import from LDAP */
auto conf = QGpgME::cryptoConfig();
Q_ASSERT (conf);
if (!Settings().alwaysSearchCardOnKeyserver() && !Kleo::keyserver().startsWith(QLatin1String{"ldap"})) {
return;
}
mStatusLabel->setText(i18n("Searching in directory service..."));
mStatusLabel->setVisible(true);
qCDebug(KLEOPATRA_LOG) << "Looking for:" << fpr.c_str() << "on ldap server";
QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(true);
connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this, fpr] () {
qCDebug(KLEOPATRA_LOG) << "Updating key info after changes";
ReaderStatus::mutableInstance()->updateStatus();
mOpenPGPKeysWidget->update(nullptr);
});
connect(job, &QGpgME::KeyListJob::result, job, [this](GpgME::KeyListResult, std::vector<GpgME::Key> keys, QString, GpgME::Error) {
if (keys.size() == 1) {
auto importJob = QGpgME::openpgp()->importFromKeyserverJob();
qCDebug(KLEOPATRA_LOG) << "Importing: " << keys[0].primaryFingerprint();
connect(importJob, &QGpgME::ImportFromKeyserverJob::result, importJob, [this](GpgME::ImportResult, QString, GpgME::Error) {
qCDebug(KLEOPATRA_LOG) << "import job done";
mStatusLabel->setText(i18n("Automatic import finished."));
});
importJob->start(keys);
} else if (keys.size() > 1) {
qCDebug(KLEOPATRA_LOG) << "Multiple keys found on server";
mStatusLabel->setText(i18n("Error multiple keys found on server."));
} else {
qCDebug(KLEOPATRA_LOG) << "No key found";
mStatusLabel->setText(i18n("Key not found in directory service."));
}
});
job->start(QStringList() << QString::fromStdString(fpr));
}
void P15CardWidget::setCard(const P15Card *card)
{
mCardSerialNumber = card->serialNumber();
mVersionLabel->setText(i18nc("%1 is a smartcard manufacturer", "%1 PKCS#15 card",
QString::fromStdString(card->manufacturer())));
mSerialNumber->setText(card->displaySerialNumber());
mSerialNumber->setToolTip(QString::fromStdString(card->serialNumber()));
const auto sigInfo = card->keyInfo(card->signingKeyRef());
if (!sigInfo.grip.empty()) {
const auto key = KeyCache::instance()->findSubkeyByKeyGrip(sigInfo.grip, GpgME::OpenPGP).parent();
if (key.isNull()) {
qCDebug(KLEOPATRA_LOG) << "Failed to find key for grip:" << sigInfo.grip.c_str();
const auto pgpSigFpr = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef());
if (!pgpSigFpr.empty()) {
qCDebug(KLEOPATRA_LOG) << "Should be pgp key:" << pgpSigFpr.c_str();
searchPGPFpr(pgpSigFpr);
}
} else {
mStatusLabel->setVisible(false);
}
}
- const bool cardHasOpenPGPKeys = !card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty()
- || !card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty();
+ const bool cardHasOpenPGPKeys = (!card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() //
+ || !card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty());
mOpenPGPKeysSection->setVisible(cardHasOpenPGPKeys);
if (cardHasOpenPGPKeys) {
mOpenPGPKeysWidget->update(card);
}
/* Check if additional keys could be available */
if (!Settings().autoLoadP15Certs()) {
return;
}
for (const auto &info: card->keyInfos()) {
const auto key = KeyCache::instance()->findSubkeyByKeyGrip(info.grip);
if (key.isNull()) {
auto cmd = new LearnCardKeysCommand(GpgME::CMS);
cmd->setParentWidget(this);
cmd->setShowsOutputWindow(false);
qCDebug(KLEOPATRA_LOG) << "Did not find:" << info.grip.c_str() << "Starting gpgsm --learn.";
cmd->start();
connect(cmd, &Command::finished, this, [] () {
qCDebug(KLEOPATRA_LOG) << "Learn command finished.";
});
return;
}
}
qCDebug(KLEOPATRA_LOG) << "All certificates from card cached - Not learning.";
}
diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp
index bd83bb50e..74b2a5b8f 100644
--- a/src/view/pgpcardwidget.cpp
+++ b/src/view/pgpcardwidget.cpp
@@ -1,580 +1,580 @@
/* view/pgpcardwiget.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-FileCopyrightText: 2020, 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 "pgpcardwidget.h"
#include "openpgpkeycardwidget.h"
#include "kleopatra_debug.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include "commands/openpgpgeneratecardkeycommand.h"
#include "smartcard/algorithminfo.h"
#include "smartcard/openpgpcard.h"
#include "smartcard/readerstatus.h"
#include "dialogs/gencardkeydialog.h"
#include <Libkleo/Compliance>
#include <Libkleo/GnuPG>
#include <QProgressDialog>
#include <QThread>
#include <QScrollArea>
#include <QInputDialog>
#include <QFileDialog>
#include <QFileInfo>
#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSeparator>
#include <Libkleo/KeyCache>
#include <Libkleo/Formatting>
#include <gpgme++/data.h>
#include <gpgme++/context.h>
#include <QGpgME/DataProvider>
#include <gpgme++/gpggencardkeyinteractor.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
static QDebug operator<<(QDebug s, const std::string &string)
{
return s << QString::fromStdString(string);
}
#endif
namespace {
class GenKeyThread: public QThread
{
Q_OBJECT
public:
explicit GenKeyThread(const GenCardKeyDialog::KeyParams &params, const std::string &serial):
mSerial(serial),
mParams(params)
{
}
GpgME::Error error()
{
return mErr;
}
std::string bkpFile()
{
return mBkpFile;
}
protected:
void run() override {
// the index of the curves in this list has to match the enum values
// minus 1 of GpgGenCardKeyInteractor::Curve
static const std::vector<std::string> curves = {
"curve25519",
#if GPGMEPP_SUPPORTS_SET_CURVE
"curve448",
"nistp256",
"nistp384",
"nistp521",
"brainpoolP256r1",
"brainpoolP384r1",
"brainpoolP512r1",
"secp256k1", // keep it, even if we don't support it in Kleopatra
#endif
};
auto ei = std::make_unique<GpgME::GpgGenCardKeyInteractor>(mSerial);
if (mParams.algorithm.starts_with("rsa")) {
ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA);
ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt());
} else {
ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC);
const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm);
if (curveIt != curves.end()) {
#if GPGMEPP_SUPPORTS_SET_CURVE
ei->setCurve(static_cast<GpgME::GpgGenCardKeyInteractor::Curve>(curveIt - curves.cbegin() + 1));
#endif
} else {
qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm;
mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE);
return;
}
}
ei->setNameUtf8(mParams.name.toStdString());
ei->setEmailUtf8(mParams.email.toStdString());
ei->setDoBackup(mParams.backup);
const auto ctx = std::shared_ptr<GpgME::Context> (GpgME::Context::createForProtocol(GpgME::OpenPGP));
ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves
QGpgME::QByteArrayDataProvider dp;
GpgME::Data data(&dp);
mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data);
mBkpFile = static_cast<GpgME::GpgGenCardKeyInteractor*>(ctx->lastCardEditInteractor())->backupFileName();
}
private:
GpgME::Error mErr;
std::string mSerial;
GenCardKeyDialog::KeyParams mParams;
std::string mBkpFile;
};
} // Namespace
PGPCardWidget::PGPCardWidget(QWidget *parent):
QWidget(parent),
mSerialNumber(new QLabel(this)),
mCardHolderLabel(new QLabel(this)),
mVersionLabel(new QLabel(this)),
mUrlLabel(new QLabel(this)),
mCardIsEmpty(false)
{
// Set up the scroll area
auto myLayout = new QVBoxLayout(this);
myLayout->setContentsMargins(0, 0, 0, 0);
auto area = new QScrollArea;
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
myLayout->addWidget(area);
auto areaWidget = new QWidget;
area->setWidget(areaWidget);
auto areaVLay = new QVBoxLayout(areaWidget);
auto cardInfoGrid = new QGridLayout;
{
int row = 0;
// Version and Serialnumber
cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2);
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
row++;
cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
cardInfoGrid->addWidget(mSerialNumber, row, 1);
mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction);
row++;
// Cardholder Row
cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.",
"Cardholder:")), row, 0);
cardInfoGrid->addWidget(mCardHolderLabel, row, 1);
mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
{
auto button = new QPushButton;
button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
button->setAccessibleName(i18nc("@action:button", "Edit"));
button->setToolTip(i18n("Change"));
cardInfoGrid->addWidget(button, row, 2);
connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested);
}
row++;
// URL Row
cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that "
"corresponds to a smartcard can be downloaded",
"Pubkey URL:")), row, 0);
cardInfoGrid->addWidget(mUrlLabel, row, 1);
mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
{
auto button = new QPushButton;
button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit")));
button->setAccessibleName(i18nc("@action:button", "Edit"));
button->setToolTip(i18n("Change"));
cardInfoGrid->addWidget(button, row, 2);
connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested);
}
cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1);
}
areaVLay->addLayout(cardInfoGrid);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
// The keys
areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:"))));
mKeysWidget = new OpenPGPKeyCardWidget{this};
areaVLay->addWidget(mKeysWidget);
connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR);
connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Actions:"))));
auto actionLayout = new QHBoxLayout;
{
auto generateButton = new QPushButton(i18n("Generate New Keys"));
generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card."));
actionLayout->addWidget(generateButton);
connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested);
}
{
auto pinButton = new QPushButton(i18n("Change PIN"));
pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard."));
actionLayout->addWidget(pinButton);
connect(pinButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::pinKeyRef()); });
}
{
auto unblockButton = new QPushButton(i18n("Unblock Card"));
unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN."));
actionLayout->addWidget(unblockButton);
connect(unblockButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef()); });
}
{
auto pukButton = new QPushButton(i18n("Change Admin PIN"));
pukButton->setToolTip(i18n("Change the PIN required for administrative operations."));
actionLayout->addWidget(pukButton);
connect(pukButton, &QPushButton::clicked, this, [this] () { doChangePin(OpenPGPCard::adminPinKeyRef()); });
}
{
auto resetCodeButton = new QPushButton(i18n("Change Reset Code"));
resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN."));
actionLayout->addWidget(resetCodeButton);
connect(resetCodeButton, &QPushButton::clicked,
this, [this] () { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); });
}
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys);
}
actionLayout->addStretch(-1);
areaVLay->addLayout(actionLayout);
areaVLay->addStretch(1);
}
void PGPCardWidget::setCard(const OpenPGPCard *card)
{
const QString version = card->displayAppVersion();
mIs21 = card->appVersion() >= 0x0201;
const QString manufacturer = QString::fromStdString(card->manufacturer());
const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown");
mVersionLabel->setText(manufacturerIsUnknown ?
i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) :
i18nc("First placeholder is manufacturer, second placeholder is a version number",
"%1 OpenPGP v%2 card", manufacturer, version));
mSerialNumber->setText(card->displaySerialNumber());
mRealSerial = card->serialNumber();
const auto holder = card->cardHolder();
const auto url = QString::fromStdString(card->pubkeyUrl());
mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder);
mUrl = url;
mUrlLabel->setText(url.isEmpty() ? i18n("not set") :
QStringLiteral("<a href=\"%1\">%1</a>").arg(url.toHtmlEscaped()));
mUrlLabel->setOpenExternalLinks(true);
mKeysWidget->update(card);
mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty()
&& card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty()
&& card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty();
if (mKeyForCardKeysButton) {
- mKeyForCardKeysButton->setEnabled(card->hasSigningKey()
- && card->hasEncryptionKey()
+ mKeyForCardKeysButton->setEnabled(card->hasSigningKey() //
+ && card->hasEncryptionKey() //
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
}
void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode)
{
auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
cmd->setMode(mode);
cmd->start();
}
void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg)
{
const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName);
if (err) {
return;
}
const auto params = dlg->getKeyParams();
auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog);
progress->setAutoClose(true);
progress->setMinimumDuration(0);
progress->setMaximum(0);
progress->setMinimum(0);
progress->setModal(true);
progress->setCancelButton(nullptr);
progress->setWindowTitle(i18nc("@title:window", "Generating Keys"));
progress->setLabel(new QLabel(i18n("This may take several minutes...")));
auto workerThread = new GenKeyThread(params, mRealSerial);
connect(workerThread, &QThread::finished, this, [this, workerThread, progress] {
progress->accept();
progress->deleteLater();
genKeyDone(workerThread->error(), workerThread->bkpFile());
delete workerThread;
});
workerThread->start();
progress->exec();
}
void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
"Failed to generate new key: %1", Formatting::errorAsString(err)));
return;
}
if (err.isCanceled()) {
return;
}
if (!backup.empty()) {
const auto bkpFile = QString::fromStdString(backup);
QFileInfo fi(bkpFile);
const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"),
fi.fileName(),
QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key")));
if (!target.isEmpty() && !QFile::copy(bkpFile, target)) {
KMessageBox::error(this, i18nc("@info",
"Failed to move backup. The backup key is still stored under: %1", bkpFile));
} else if (!target.isEmpty()) {
QFile::remove(bkpFile);
}
}
KMessageBox::information(this, i18nc("@info",
"Successfully generated a new key for this card."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
void PGPCardWidget::genkeyRequested()
{
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
if (!mCardIsEmpty) {
auto ret = KMessageBox::warningContinueCancel(this,
i18n("The existing keys on this card will be <b>deleted</b> "
"and replaced by new keys.") + QStringLiteral("<br/><br/>") +
i18n("It will no longer be possible to decrypt past communication "
"encrypted for the existing key."),
i18n("Secret Key Deletion"),
KStandardGuiItem::guiItem(KStandardGuiItem::Delete),
KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (ret != KMessageBox::Continue) {
return;
}
}
auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this);
#if GPGMEPP_SUPPORTS_SET_CURVE
dlg->setSupportedAlgorithms(pgpCard->supportedAlgorithms(), pgpCard->defaultAlgorithm());
#else
std::vector<AlgorithmInfo> algos = {
{ "rsa2048", i18nc("@info", "RSA 2048") },
{ "rsa3072", i18nc("@info", "RSA 3072") },
};
// There is probably a better way to check for capabilities
if (mIs21) {
algos.push_back({"rsa4096", i18nc("@info", "RSA 4096")});
}
const auto supportedAlgos = pgpCard->supportedAlgorithms();
if (std::any_of(supportedAlgos.begin(), supportedAlgos.end(), [](const auto &algo) {
return algo.id == "curve25519";
})) {
algos.push_back({"curve25519", i18nc("@info", "ECC (Curve25519)")});
}
dlg->setSupportedAlgorithms(algos, "rsa2048");
#endif
connect(dlg, &QDialog::accepted, this, [this, dlg] () {
doGenKey(dlg);
dlg->deleteLater();
});
dlg->setModal(true);
dlg->show();
}
void PGPCardWidget::changeNameRequested()
{
QString text = mCardHolderLabel->text();
while (true) {
bool ok = false;
text = QInputDialog::getText(this, i18n("Change cardholder"),
i18n("New name:"), QLineEdit::Normal,
text, &ok, Qt::WindowFlags(),
Qt::ImhLatinOnly);
if (!ok) {
return;
}
// Some additional restrictions imposed by gnupg
if (text.contains(QLatin1Char('<'))) {
KMessageBox::error(this, i18nc("@info",
"The \"<\" character may not be used."));
continue;
}
if (text.contains(QLatin1String(" "))) {
KMessageBox::error(this, i18nc("@info",
"Double spaces are not allowed"));
continue;
}
if (text.size() > 38) {
KMessageBox::error(this, i18nc("@info",
"The size of the name may not exceed 38 characters."));
}
break;
}
auto parts = text.split(QLatin1Char(' '));
const auto lastName = parts.takeLast();
const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<'));
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8();
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
changeNameResult(err);
});
}
void PGPCardWidget::changeNameResult(const GpgME::Error &err)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
"Name change failed: %1", Formatting::errorAsString(err)));
return;
}
if (!err.isCanceled()) {
KMessageBox::information(this, i18nc("@info",
"Name successfully changed."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
}
void PGPCardWidget::changeUrlRequested()
{
QString text = mUrl;
while (true) {
bool ok = false;
text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"),
i18n("New pubkey URL:"), QLineEdit::Normal,
text, &ok, Qt::WindowFlags(),
Qt::ImhLatinOnly);
if (!ok) {
return;
}
// Some additional restrictions imposed by gnupg
if (text.size() > 254) {
KMessageBox::error(this, i18nc("@info",
"The size of the URL may not exceed 254 characters."));
}
break;
}
const auto pgpCard = ReaderStatus::instance()->getCard<OpenPGPCard>(mRealSerial);
if (!pgpCard) {
KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial)));
return;
}
const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8();
ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) {
changeUrlResult(err);
});
}
void PGPCardWidget::changeUrlResult(const GpgME::Error &err)
{
if (err) {
KMessageBox::error(this, i18nc("@info",
"URL change failed: %1", Formatting::errorAsString(err)));
return;
}
if (!err.isCanceled()) {
KMessageBox::information(this, i18nc("@info",
"URL successfully changed."),
i18nc("@title", "Success"));
ReaderStatus::mutableInstance()->updateStatus();
}
}
void PGPCardWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PGPCardWidget::createCSR(const std::string &keyref)
{
auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PGPCardWidget::generateKey(const std::string &keyref)
{
auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mRealSerial, this);
this->setEnabled(false);
connect(cmd, &OpenPGPGenerateCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
#include "pgpcardwidget.moc"
diff --git a/src/view/pivcardwidget.cpp b/src/view/pivcardwidget.cpp
index 620744914..79ca5fede 100644
--- a/src/view/pivcardwidget.cpp
+++ b/src/view/pivcardwidget.cpp
@@ -1,390 +1,390 @@
/* view/pivcardwiget.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "pivcardwidget.h"
#include "tooltippreferences.h"
#include "commands/certificatetopivcardcommand.h"
#include "commands/changepincommand.h"
#include "commands/createcsrforcardkeycommand.h"
#include "commands/createopenpgpkeyfromcardkeyscommand.h"
#include "commands/importcertificatefrompivcardcommand.h"
#include "commands/keytocardcommand.h"
#include "commands/pivgeneratecardkeycommand.h"
#include "commands/setpivcardapplicationadministrationkeycommand.h"
#include "smartcard/pivcard.h"
#include "smartcard/readerstatus.h"
#include <Libkleo/Compliance>
#include <Libkleo/Dn>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <KLocalizedString>
#include <KSeparator>
#include <QFrame>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
using namespace GpgME;
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::SmartCard;
namespace {
static void layoutKeyWidgets(QGridLayout *grid, const QString &keyName, const PIVCardWidget::KeyWidgets &keyWidgets)
{
int row = grid->rowCount();
grid->addWidget(new QLabel(keyName), row, 0);
grid->addWidget(keyWidgets.keyGrip, row, 1);
grid->addWidget(keyWidgets.keyAlgorithm, row, 2);
grid->addWidget(keyWidgets.generateButton, row, 3);
if (keyWidgets.writeKeyButton) {
grid->addWidget(keyWidgets.writeKeyButton, row, 4);
}
row++;
grid->addWidget(keyWidgets.certificateInfo, row, 1, 1, 2);
grid->addWidget(keyWidgets.writeCertificateButton, row, 3);
grid->addWidget(keyWidgets.importCertificateButton, row, 4);
if (keyWidgets.createCSRButton) {
grid->addWidget(keyWidgets.createCSRButton, row, 5);
}
}
static int toolTipOptions()
{
using namespace Kleo::Formatting;
static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage;
static const int ownerFlags = Subject | UserIDs | OwnerTrust;
static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint;
const TooltipPreferences prefs;
int flags = KeyID;
flags |= prefs.showValidity() ? validityFlags : 0;
flags |= prefs.showOwnerInformation() ? ownerFlags : 0;
flags |= prefs.showCertificateDetails() ? detailsFlags : 0;
return flags;
}
}
PIVCardWidget::PIVCardWidget(QWidget *parent)
: QWidget(parent)
, mSerialNumber(new QLabel(this))
, mVersionLabel(new QLabel(this))
{
// Set up the scroll area
auto myLayout = new QVBoxLayout(this);
myLayout->setContentsMargins(0, 0, 0, 0);
auto area = new QScrollArea;
area->setFrameShape(QFrame::NoFrame);
area->setWidgetResizable(true);
myLayout->addWidget(area);
auto areaWidget = new QWidget;
area->setWidget(areaWidget);
auto areaVLay = new QVBoxLayout(areaWidget);
auto cardInfoGrid = new QGridLayout;
{
int row = 0;
// Version and Serialnumber
cardInfoGrid->addWidget(mVersionLabel, row++, 0, 1, 2);
mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0);
cardInfoGrid->addWidget(mSerialNumber, row++, 1);
mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction);
cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1);
}
areaVLay->addLayout(cardInfoGrid);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
areaVLay->addWidget(new QLabel(QStringLiteral("<b>%1</b>").arg(i18n("Keys:"))));
auto keysGrid = new QGridLayout;
for (const auto &keyInfo : PIVCard::supportedKeys()) {
KeyWidgets keyWidgets = createKeyWidgets(keyInfo);
layoutKeyWidgets(keysGrid, PIVCard::keyDisplayName(keyInfo.keyRef), keyWidgets);
}
areaVLay->addLayout(keysGrid);
areaVLay->addWidget(new KSeparator(Qt::Horizontal));
auto actionLayout = new QHBoxLayout;
if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) {
mKeyForCardKeysButton = new QPushButton(this);
mKeyForCardKeysButton->setText(i18nc("@action:button", "Create OpenPGP Key"));
mKeyForCardKeysButton->setToolTip(i18nc("@info:tooltip", "Create an OpenPGP key for the keys stored on the card."));
actionLayout->addWidget(mKeyForCardKeysButton);
connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PIVCardWidget::createKeyFromCardKeys);
}
{
auto button = new QPushButton(i18nc("@action:button", "Change PIN"));
button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application PIN that activates the PIV Card "
"and enables private key operations using the stored keys."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pinKeyRef()); });
}
{
auto button = new QPushButton(i18nc("@action:button", "Change PUK"));
button->setToolTip(i18nc("@info:tooltip", "Change the PIN Unblocking Key that enables a reset of the PIN."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this] () { changePin(PIVCard::pukKeyRef()); });
}
{
auto button = new QPushButton(i18nc("@action:button", "Change Admin Key"));
button->setToolTip(i18nc("@info:tooltip", "Change the PIV Card Application Administration Key that is used by the "
"PIV Card Application to authenticate the PIV Card Application Administrator and by the "
"administrator (resp. Kleopatra) to authenticate the PIV Card Application."));
actionLayout->addWidget(button);
connect(button, &QPushButton::clicked, this, [this] () { setAdminKey(); });
}
actionLayout->addStretch(-1);
areaVLay->addLayout(actionLayout);
areaVLay->addStretch(1);
}
PIVCardWidget::KeyWidgets PIVCardWidget::createKeyWidgets(const KeyPairInfo &keyInfo)
{
const std::string keyRef = keyInfo.keyRef;
KeyWidgets keyWidgets;
keyWidgets.keyGrip = new QLabel(this);
keyWidgets.keyGrip->setTextInteractionFlags(Qt::TextBrowserInteraction);
keyWidgets.keyAlgorithm = new QLabel(this);
keyWidgets.keyAlgorithm->setTextInteractionFlags(Qt::TextSelectableByMouse);
keyWidgets.certificateInfo = new QLabel(this);
keyWidgets.certificateInfo->setTextInteractionFlags(Qt::TextBrowserInteraction);
keyWidgets.generateButton = new QPushButton(i18nc("@action:button", "Generate"), this);
keyWidgets.generateButton->setEnabled(false);
connect(keyWidgets.generateButton, &QPushButton::clicked,
this, [this, keyRef] () { generateKey(keyRef); });
if (keyInfo.canSign() || keyInfo.canEncrypt()) {
keyWidgets.createCSRButton = new QPushButton(i18nc("@action:button", "Create CSR"), this);
keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key"));
keyWidgets.createCSRButton->setEnabled(false);
connect(keyWidgets.createCSRButton, &QPushButton::clicked,
this, [this, keyRef] () { createCSR(keyRef); });
}
keyWidgets.writeCertificateButton = new QPushButton(i18nc("@action:button", "Write Certificate"));
keyWidgets.writeCertificateButton->setToolTip(i18nc("@info:tooltip", "Write the certificate corresponding to this key to the card"));
keyWidgets.writeCertificateButton->setEnabled(false);
connect(keyWidgets.writeCertificateButton, &QPushButton::clicked,
this, [this, keyRef] () { writeCertificateToCard(keyRef); });
keyWidgets.importCertificateButton = new QPushButton(i18nc("@action:button", "Import Certificate"));
keyWidgets.importCertificateButton->setToolTip(i18nc("@info:tooltip", "Import the certificate stored on the card"));
keyWidgets.importCertificateButton->setEnabled(false);
connect(keyWidgets.importCertificateButton, &QPushButton::clicked,
this, [this, keyRef] () { importCertificateFromCard(keyRef); });
if (keyRef == PIVCard::cardAuthenticationKeyRef() || keyRef == PIVCard::keyManagementKeyRef()) {
keyWidgets.writeKeyButton = new QPushButton(i18nc("@action:button", "Write Key"));
keyWidgets.writeKeyButton->setToolTip(i18nc("@info:tooltip", "Write the key pair of a certificate to the card"));
keyWidgets.writeKeyButton->setEnabled(true);
connect(keyWidgets.writeKeyButton, &QPushButton::clicked,
this, [this, keyRef] () { writeKeyToCard(keyRef); });
}
mKeyWidgets.insert({keyRef, keyWidgets});
return keyWidgets;
}
PIVCardWidget::~PIVCardWidget()
{
}
void PIVCardWidget::setCard(const PIVCard *card)
{
mCardSerialNumber = card->serialNumber();
mVersionLabel->setText(i18nc("%1 version number", "PIV v%1 card", card->displayAppVersion()));
mSerialNumber->setText(card->displaySerialNumber());
mSerialNumber->setToolTip(QString::fromStdString(card->serialNumber()));
if (card) {
updateCachedValues(PIVCard::pivAuthenticationKeyRef(), card);
updateCachedValues(PIVCard::cardAuthenticationKeyRef(), card);
updateCachedValues(PIVCard::digitalSignatureKeyRef(), card);
updateCachedValues(PIVCard::keyManagementKeyRef(), card);
}
updateKeyWidgets(PIVCard::pivAuthenticationKeyRef());
updateKeyWidgets(PIVCard::cardAuthenticationKeyRef());
updateKeyWidgets(PIVCard::digitalSignatureKeyRef());
updateKeyWidgets(PIVCard::keyManagementKeyRef());
if (mKeyForCardKeysButton) {
- mKeyForCardKeysButton->setEnabled(card->hasSigningKey()
- && card->hasEncryptionKey()
+ mKeyForCardKeysButton->setEnabled(card->hasSigningKey() //
+ && card->hasEncryptionKey() //
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm)
&& DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm));
}
}
void PIVCardWidget::updateCachedValues(const std::string &keyRef, const SmartCard::PIVCard *card)
{
KeyWidgets &widgets = mKeyWidgets.at(keyRef);
widgets.keyInfo = card->keyInfo(keyRef);
widgets.certificateData = card->certificateData(keyRef);
}
void PIVCardWidget::updateKeyWidgets(const std::string &keyRef)
{
const KeyWidgets &widgets = mKeyWidgets.at(keyRef);
const std::string grip = widgets.keyInfo.grip;
if (grip.empty()) {
widgets.certificateInfo->setText(i18nc("@info", "<em>slot empty</em>"));
widgets.certificateInfo->setToolTip(QString());
widgets.keyGrip->setText(QString());
widgets.keyAlgorithm->setText(QString());
widgets.generateButton->setText(i18nc("@action:button", "Generate"));
widgets.generateButton->setToolTip(
i18nc("@info:tooltip %1 display name of a key", "Generate %1", PIVCard::keyDisplayName(keyRef)));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(false);
}
widgets.writeCertificateButton->setEnabled(false);
widgets.importCertificateButton->setEnabled(false);
} else {
const Key certificate = KeyCache::instance()->findSubkeyByKeyGrip(grip, GpgME::CMS).parent();
if (!certificate.isNull()) {
widgets.certificateInfo->setText(
i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)",
DN(certificate.userID(0).id()).prettyDN(),
Formatting::complianceStringShort(certificate),
Formatting::creationDateString(certificate)));
widgets.certificateInfo->setToolTip(Formatting::toolTip(certificate, toolTipOptions()));
widgets.writeCertificateButton->setEnabled(true);
} else {
widgets.certificateInfo->setText(i18nc("@info", "<em>no matching certificate</em>"));
widgets.certificateInfo->setToolTip(QString());
widgets.writeCertificateButton->setEnabled(false);
}
widgets.keyGrip->setText(QString::fromStdString(grip));
const std::string algo = widgets.keyInfo.algorithm;
widgets.keyAlgorithm->setText(algo.empty() ? i18nc("@info unknown key algorithm", "unknown") : QString::fromStdString(algo));
widgets.importCertificateButton->setEnabled(!widgets.certificateData.empty());
widgets.generateButton->setText(i18nc("@action:button", "Replace"));
widgets.generateButton->setToolTip(
i18nc("@info:tooltip %1 display name of a key", "Replace %1 with new key", PIVCard::keyDisplayName(keyRef)));
if (widgets.createCSRButton) {
widgets.createCSRButton->setEnabled(DeVSCompliance::algorithmIsCompliant(algo));
}
}
widgets.generateButton->setEnabled(true);
}
void PIVCardWidget::generateKey(const std::string &keyref)
{
auto cmd = new PIVGenerateCardKeyCommand(mCardSerialNumber, this);
this->setEnabled(false);
connect(cmd, &PIVGenerateCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyref);
cmd->start();
}
void PIVCardWidget::createCSR(const std::string &keyref)
{
auto cmd = new CreateCSRForCardKeyCommand(keyref, mCardSerialNumber, PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateCSRForCardKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PIVCardWidget::writeCertificateToCard(const std::string &keyref)
{
auto cmd = new CertificateToPIVCardCommand(keyref, mCardSerialNumber);
this->setEnabled(false);
connect(cmd, &CertificateToPIVCardCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setParentWidget(this);
cmd->start();
}
void PIVCardWidget::importCertificateFromCard(const std::string &keyref)
{
auto cmd = new ImportCertificateFromPIVCardCommand(keyref, mCardSerialNumber);
this->setEnabled(false);
connect(cmd, &ImportCertificateFromPIVCardCommand::finished,
this, [this, keyref] () {
this->updateKeyWidgets(keyref);
this->setEnabled(true);
});
cmd->setParentWidget(this);
cmd->start();
}
void PIVCardWidget::writeKeyToCard(const std::string &keyref)
{
auto cmd = new KeyToCardCommand(keyref, mCardSerialNumber, PIVCard::AppName);
this->setEnabled(false);
connect(cmd, &KeyToCardCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setParentWidget(this);
cmd->start();
}
void PIVCardWidget::createKeyFromCardKeys()
{
auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mCardSerialNumber, PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
void PIVCardWidget::changePin(const std::string &keyRef)
{
auto cmd = new ChangePinCommand(mCardSerialNumber, PIVCard::AppName, this);
this->setEnabled(false);
connect(cmd, &ChangePinCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->setKeyRef(keyRef);
cmd->start();
}
void PIVCardWidget::setAdminKey()
{
auto cmd = new SetPIVCardApplicationAdministrationKeyCommand(mCardSerialNumber, this);
this->setEnabled(false);
connect(cmd, &SetPIVCardApplicationAdministrationKeyCommand::finished,
this, [this]() {
this->setEnabled(true);
});
cmd->start();
}
diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp
index 293c31835..7dd03f101 100644
--- a/src/view/tabwidget.cpp
+++ b/src/view/tabwidget.cpp
@@ -1,1005 +1,1005 @@
/* -*- mode: c++; c-basic-offset:4 -*-
view/tabwidget.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 <config-kleopatra.h>
#include "tabwidget.h"
#include "searchbar.h"
#include "keytreeview.h"
#include "kleopatra_debug.h"
#include <settings.h>
#include <utils/action_data.h>
#include <Libkleo/Stl_Util>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QTabWidget>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KConfig>
#include <QAction>
#include <KActionCollection>
#include <QInputDialog>
#include <QTreeView>
#include <QToolButton>
#include <QMenu>
#include <QVBoxLayout>
#include <QRegularExpression>
#include <QAbstractProxyModel>
#include <map>
using namespace Kleo;
using namespace GpgME;
namespace
{
class Page : public Kleo::KeyTreeView
{
Q_OBJECT
Page(const Page &other);
public:
Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr, const KConfigGroup &group = KConfigGroup());
Page(const KConfigGroup &group, QWidget *parent = nullptr);
~Page() override;
void setTemporary(bool temporary);
bool isTemporary() const
{
return m_isTemporary;
}
void setHierarchicalView(bool hierarchical) override;
void setStringFilter(const QString &filter) override;
void setKeyFilter(const std::shared_ptr<KeyFilter> &filter) override;
QString title() const
{
return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title;
}
void setTitle(const QString &title);
QString toolTip() const
{
return m_toolTip.isEmpty() ? title() : m_toolTip;
}
// not used void setToolTip(const QString &tip);
bool canBeClosed() const
{
return m_canBeClosed;
}
bool canBeRenamed() const
{
return m_canBeRenamed;
}
bool canChangeStringFilter() const
{
return m_canChangeStringFilter;
}
bool canChangeKeyFilter() const
{
return m_canChangeKeyFilter && !m_isTemporary;
}
bool canChangeHierarchical() const
{
return m_canChangeHierarchical;
}
void saveTo(KConfigGroup &group) const;
Page *clone() const override
{
return new Page(*this);
}
void liftAllRestrictions()
{
m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true;
}
Q_SIGNALS:
void titleChanged(const QString &title);
private:
void init();
private:
QString m_title;
QString m_toolTip;
bool m_isTemporary : 1;
bool m_canBeClosed : 1;
bool m_canBeRenamed : 1;
bool m_canChangeStringFilter : 1;
bool m_canChangeKeyFilter : 1;
bool m_canChangeHierarchical : 1;
};
} // anon namespace
Page::Page(const Page &other)
: KeyTreeView(other),
m_title(other.m_title),
m_toolTip(other.m_toolTip),
m_isTemporary(other.m_isTemporary),
m_canBeClosed(other.m_canBeClosed),
m_canBeRenamed(other.m_canBeRenamed),
m_canChangeStringFilter(other.m_canChangeStringFilter),
m_canChangeKeyFilter(other.m_canChangeKeyFilter),
m_canChangeHierarchical(other.m_canChangeHierarchical)
{
init();
}
Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent,
const KConfigGroup &group)
: KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group),
m_title(title),
m_toolTip(toolTip),
m_isTemporary(false),
m_canBeClosed(true),
m_canBeRenamed(true),
m_canChangeStringFilter(true),
m_canChangeKeyFilter(true),
m_canChangeHierarchical(true)
{
init();
}
static const char TITLE_ENTRY[] = "title";
static const char STRING_FILTER_ENTRY[] = "string-filter";
static const char KEY_FILTER_ENTRY[] = "key-filter";
static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view";
static const char COLUMN_SIZES[] = "column-sizes";
static const char SORT_COLUMN[] = "sort-column";
static const char SORT_DESCENDING[] = "sort-descending";
Page::Page(const KConfigGroup &group, QWidget *parent)
: KeyTreeView(group.readEntry(STRING_FILTER_ENTRY),
KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)),
nullptr, parent, group),
m_title(group.readEntry(TITLE_ENTRY)),
m_toolTip(),
m_isTemporary(false),
m_canBeClosed(!group.isImmutable()),
m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY)),
m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY)),
m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY)),
m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY))
{
init();
setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true));
const QList<int> settings = group.readEntry(COLUMN_SIZES, QList<int>());
std::vector<int> sizes;
sizes.reserve(settings.size());
std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes));
setColumnSizes(sizes);
setSortColumn(group.readEntry(SORT_COLUMN, 0),
group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder);
}
void Page::init()
{
}
Page::~Page() {}
void Page::saveTo(KConfigGroup &group) const
{
group.writeEntry(TITLE_ENTRY, m_title);
group.writeEntry(STRING_FILTER_ENTRY, stringFilter());
group.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString());
group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView());
QList<int> settings;
const auto sizes = columnSizes();
settings.reserve(sizes.size());
std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings));
group.writeEntry(COLUMN_SIZES, settings);
group.writeEntry(SORT_COLUMN, sortColumn());
group.writeEntry(SORT_DESCENDING, sortOrder() == Qt::DescendingOrder);
}
void Page::setStringFilter(const QString &filter)
{
if (!m_canChangeStringFilter) {
return;
}
KeyTreeView::setStringFilter(filter);
}
void Page::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (!canChangeKeyFilter()) {
return;
}
const QString oldTitle = title();
KeyTreeView::setKeyFilter(filter);
const QString newTitle = title();
if (oldTitle != newTitle) {
Q_EMIT titleChanged(newTitle);
}
}
void Page::setTitle(const QString &t)
{
if (t == m_title) {
return;
}
if (!m_canBeRenamed) {
return;
}
const QString oldTitle = title();
m_title = t;
const QString newTitle = title();
if (oldTitle != newTitle) {
Q_EMIT titleChanged(newTitle);
}
}
#if 0 // not used
void Page::setToolTip(const QString &tip)
{
if (tip == m_toolTip) {
return;
}
if (!m_canBeRenamed) {
return;
}
const QString oldTip = toolTip();
m_toolTip = tip;
const QString newTip = toolTip();
if (oldTip != newTip) {
Q_EMIT titleChanged(title());
}
}
#endif
void Page::setHierarchicalView(bool on)
{
if (!m_canChangeHierarchical) {
return;
}
KeyTreeView::setHierarchicalView(on);
}
void Page::setTemporary(bool on)
{
if (on == m_isTemporary) {
return;
}
m_isTemporary = on;
if (on) {
setKeyFilter(std::shared_ptr<KeyFilter>());
}
}
namespace
{
class Actions
{
public:
constexpr static const char *Rename = "window_rename_tab";
constexpr static const char *Duplicate = "window_duplicate_tab";
constexpr static const char *Close = "window_close_tab";
constexpr static const char *MoveLeft = "window_move_tab_left";
constexpr static const char *MoveRight = "window_move_tab_right";
constexpr static const char *Hierarchical = "window_view_hierarchical";
constexpr static const char *ExpandAll = "window_expand_all";
constexpr static const char *CollapseAll = "window_collapse_all";
explicit Actions() {}
void insert(const std::string &name, QAction *action)
{
actions.insert({name, action});
}
auto get(const std::string &name) const
{
const auto it = actions.find(name);
return (it != actions.end()) ? it->second : nullptr;
}
void setChecked(const std::string &name, bool checked) const
{
if (auto action = get(name)) {
action->setChecked(checked);
}
}
void setEnabled(const std::string &name, bool enabled) const
{
if (auto action = get(name)) {
action->setEnabled(enabled);
}
}
void setVisible(const std::string &name, bool visible) const
{
if (auto action = get(name)) {
action->setVisible(visible);
}
}
private:
std::map<std::string, QAction *> actions;
};
}
//
//
// TabWidget
//
//
class TabWidget::Private
{
friend class ::Kleo::TabWidget;
TabWidget *const q;
public:
explicit Private(TabWidget *qq);
~Private() {}
private:
void slotContextMenu(const QPoint &p);
void currentIndexChanged(int index);
void slotPageTitleChanged(const QString &title);
void slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &filter);
void slotPageStringFilterChanged(const QString &filter);
void slotPageHierarchyChanged(bool on);
#ifndef QT_NO_INPUTDIALOG
void slotRenameCurrentTab()
{
renamePage(currentPage());
}
#endif // QT_NO_INPUTDIALOG
void slotNewTab();
void slotDuplicateCurrentTab()
{
duplicatePage(currentPage());
}
void slotCloseCurrentTab()
{
closePage(currentPage());
}
void slotMoveCurrentTabLeft()
{
movePageLeft(currentPage());
}
void slotMoveCurrentTabRight()
{
movePageRight(currentPage());
}
void slotToggleHierarchicalView(bool on)
{
toggleHierarchicalView(currentPage(), on);
}
void slotExpandAll()
{
expandAll(currentPage());
}
void slotCollapseAll()
{
collapseAll(currentPage());
}
#ifndef QT_NO_INPUTDIALOG
void renamePage(Page *page);
#endif
void duplicatePage(Page *page);
void closePage(Page *page);
void movePageLeft(Page *page);
void movePageRight(Page *page);
void toggleHierarchicalView(Page *page, bool on);
void expandAll(Page *page);
void collapseAll(Page *page);
void enableDisableCurrentPageActions();
void enableDisablePageActions(const Actions &actions, const Page *page);
Page *currentPage() const
{
Q_ASSERT(!tabWidget->currentWidget() || qobject_cast<Page *>(tabWidget->currentWidget()));
return static_cast<Page *>(tabWidget->currentWidget());
}
Page *page(unsigned int idx) const
{
Q_ASSERT(!tabWidget->widget(idx) || qobject_cast<Page *>(tabWidget->widget(idx)));
return static_cast<Page *>(tabWidget->widget(idx));
}
Page *senderPage() const
{
QObject *const sender = q->sender();
Q_ASSERT(!sender || qobject_cast<Page *>(sender));
return static_cast<Page *>(sender);
}
bool isSenderCurrentPage() const
{
Page *const sp = senderPage();
return sp && sp == currentPage();
}
QTreeView *addView(Page *page, Page *columnReference);
private:
AbstractKeyListModel *flatModel = nullptr;
AbstractKeyListModel *hierarchicalModel = nullptr;
QToolButton *newTabButton = nullptr;
QToolButton *closeTabButton = nullptr;
QTabWidget *tabWidget = nullptr;
QAction *newAction = nullptr;
Actions currentPageActions;
Actions otherPageActions;
bool actionsCreated = false;
};
TabWidget::Private::Private(TabWidget *qq)
: q{qq}
{
auto layout = new QVBoxLayout{q};
layout->setContentsMargins(0, 0, 0, 0);
// create "New Tab" button before tab widget to ensure correct tab order
newTabButton = new QToolButton{q};
tabWidget = new QTabWidget{q};
KDAB_SET_OBJECT_NAME(tabWidget);
layout->addWidget(tabWidget);
tabWidget->setMovable(true);
tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
// create "Close Tab" button after tab widget to ensure correct tab order
closeTabButton = new QToolButton{q};
connect(tabWidget, &QTabWidget::currentChanged, q, [this](int index) { currentIndexChanged(index); });
connect(tabWidget->tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint & p) {
slotContextMenu(p);
});
}
void TabWidget::Private::slotContextMenu(const QPoint &p)
{
const int tabUnderPos = tabWidget->tabBar()->tabAt(p);
Page *const contextMenuPage = static_cast<Page *>(tabWidget->widget(tabUnderPos));
const Page *const current = currentPage();
const auto actions = contextMenuPage == current ? currentPageActions : otherPageActions;
enableDisablePageActions(actions, contextMenuPage);
QMenu menu;
if (auto action = actions.get(Actions::Rename)) {
menu.addAction(action);
}
menu.addSeparator();
menu.addAction(newAction);
if (auto action = actions.get(Actions::Duplicate)) {
menu.addAction(action);
}
menu.addSeparator();
if (auto action = actions.get(Actions::MoveLeft)) {
menu.addAction(action);
}
if (auto action = actions.get(Actions::MoveRight)) {
menu.addAction(action);
}
menu.addSeparator();
if (auto action = actions.get(Actions::Close)) {
menu.addAction(action);
}
const QAction *const action = menu.exec(tabWidget->tabBar()->mapToGlobal(p));
if (!action) {
return;
}
if (contextMenuPage == current || action == newAction) {
return; // performed through signal/slot connections...
}
#ifndef QT_NO_INPUTDIALOG
if (action == otherPageActions.get(Actions::Rename)) {
renamePage(contextMenuPage);
}
#endif // QT_NO_INPUTDIALOG
else if (action == otherPageActions.get(Actions::Duplicate)) {
duplicatePage(contextMenuPage);
} else if (action == otherPageActions.get(Actions::Close)) {
closePage(contextMenuPage);
} else if (action == otherPageActions.get(Actions::MoveLeft)) {
movePageLeft(contextMenuPage);
} else if (action == otherPageActions.get(Actions::MoveRight)) {
movePageRight(contextMenuPage);
}
}
void TabWidget::Private::currentIndexChanged(int index)
{
const Page *const page = this->page(index);
Q_EMIT q->currentViewChanged(page ? page->view() : nullptr);
Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr<KeyFilter>());
Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString());
enableDisableCurrentPageActions();
}
void TabWidget::Private::enableDisableCurrentPageActions()
{
const Page *const page = currentPage();
Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter());
Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter());
enableDisablePageActions(currentPageActions, page);
}
void TabWidget::Private::enableDisablePageActions(const Actions &actions, const Page *p)
{
actions.setEnabled(Actions::Rename, p && p->canBeRenamed());
actions.setEnabled(Actions::Duplicate, p);
actions.setEnabled(Actions::Close, p && p->canBeClosed() && tabWidget->count() > 1);
actions.setEnabled(Actions::MoveLeft, p && tabWidget->indexOf(const_cast<Page *>(p)) != 0);
actions.setEnabled(Actions::MoveRight, p && tabWidget->indexOf(const_cast<Page *>(p)) != tabWidget->count() - 1);
actions.setEnabled(Actions::Hierarchical, p && p->canChangeHierarchical());
actions.setChecked(Actions::Hierarchical, p && p->isHierarchicalView());
actions.setVisible(Actions::Hierarchical, Kleo::Settings{}.cmsEnabled());
actions.setEnabled(Actions::ExpandAll, p && p->isHierarchicalView());
actions.setEnabled(Actions::CollapseAll, p && p->isHierarchicalView());
}
void TabWidget::Private::slotPageTitleChanged(const QString &)
{
if (Page *const page = senderPage()) {
const int idx = tabWidget->indexOf(page);
tabWidget->setTabText(idx, page->title());
tabWidget->setTabToolTip(idx, page->toolTip());
}
}
void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &kf)
{
if (isSenderCurrentPage()) {
Q_EMIT q->keyFilterChanged(kf);
}
}
void TabWidget::Private::slotPageStringFilterChanged(const QString &filter)
{
if (isSenderCurrentPage()) {
Q_EMIT q->stringFilterChanged(filter);
}
}
void TabWidget::Private::slotPageHierarchyChanged(bool)
{
enableDisableCurrentPageActions();
}
void TabWidget::Private::slotNewTab()
{
const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", tabWidget->count()));
Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group);
addView(page, currentPage());
tabWidget->setCurrentIndex(tabWidget->count() - 1);
}
void TabWidget::Private::renamePage(Page *page)
{
if (!page) {
return;
}
bool ok;
const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok);
if (!ok) {
return;
}
page->setTitle(text);
}
void TabWidget::Private::duplicatePage(Page *page)
{
if (!page) {
return;
}
Page *const clone = page->clone();
Q_ASSERT(clone);
clone->liftAllRestrictions();
addView(clone, page);
}
void TabWidget::Private::closePage(Page *page)
{
if (!page || !page->canBeClosed() || tabWidget->count() <= 1) {
return;
}
Q_EMIT q->viewAboutToBeRemoved(page->view());
tabWidget->removeTab(tabWidget->indexOf(page));
enableDisableCurrentPageActions();
}
void TabWidget::Private::movePageLeft(Page *page)
{
if (!page) {
return;
}
const int idx = tabWidget->indexOf(page);
if (idx <= 0) {
return;
}
tabWidget->tabBar()->moveTab(idx, idx - 1);
enableDisableCurrentPageActions();
}
void TabWidget::Private::movePageRight(Page *page)
{
if (!page) {
return;
}
const int idx = tabWidget->indexOf(page);
if (idx < 0 || idx >= tabWidget->count() - 1) {
return;
}
tabWidget->tabBar()->moveTab(idx, idx + 1);
enableDisableCurrentPageActions();
}
void TabWidget::Private::toggleHierarchicalView(Page *page, bool on)
{
if (!page) {
return;
}
page->setHierarchicalView(on);
}
void TabWidget::Private::expandAll(Page *page)
{
if (!page || !page->view()) {
return;
}
page->view()->expandAll();
}
void TabWidget::Private::collapseAll(Page *page)
{
if (!page || !page->view()) {
return;
}
page->view()->collapseAll();
}
TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f)
: QWidget(p, f), d(new Private(this))
{
}
TabWidget::~TabWidget() {
saveViews(KSharedConfig::openConfig().data());
}
void TabWidget::setFlatModel(AbstractKeyListModel *model)
{
if (model == d->flatModel) {
return;
}
d->flatModel = model;
for (unsigned int i = 0, end = count(); i != end; ++i)
if (Page *const page = d->page(i)) {
page->setFlatModel(model);
}
}
AbstractKeyListModel *TabWidget::flatModel() const
{
return d->flatModel;
}
void TabWidget::setHierarchicalModel(AbstractKeyListModel *model)
{
if (model == d->hierarchicalModel) {
return;
}
d->hierarchicalModel = model;
for (unsigned int i = 0, end = count(); i != end; ++i)
if (Page *const page = d->page(i)) {
page->setHierarchicalModel(model);
}
}
AbstractKeyListModel *TabWidget::hierarchicalModel() const
{
return d->hierarchicalModel;
}
QString TabWidget::stringFilter() const
{
return d->currentPage() ? d->currentPage()->stringFilter() : QString{};
}
void TabWidget::setStringFilter(const QString &filter)
{
if (Page *const page = d->currentPage()) {
page->setStringFilter(filter);
}
}
void TabWidget::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
if (!filter) {
qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL";
return;
}
if (Page *const page = d->currentPage()) {
page->setKeyFilter(filter);
}
}
std::vector<QAbstractItemView *> TabWidget::views() const
{
std::vector<QAbstractItemView *> result;
const unsigned int N = count();
result.reserve(N);
for (unsigned int i = 0; i != N; ++i)
if (const Page *const p = d->page(i)) {
result.push_back(p->view());
}
return result;
}
QAbstractItemView *TabWidget::currentView() const
{
if (Page *const page = d->currentPage()) {
return page->view();
} else {
return nullptr;
}
}
KeyListModelInterface *TabWidget::currentModel() const
{
const QAbstractItemView *const view = currentView();
if (!view) {
return nullptr;
}
auto const proxy = qobject_cast<QAbstractProxyModel *>(view->model());
if (!proxy) {
return nullptr;
}
return dynamic_cast<KeyListModelInterface *>(proxy);
}
unsigned int TabWidget::count() const
{
return d->tabWidget->count();
}
void TabWidget::setMultiSelection(bool on)
{
for (unsigned int i = 0, end = count(); i != end; ++i)
if (const Page *const p = d->page(i))
if (QTreeView *const view = p->view()) {
view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
}
}
void TabWidget::createActions(KActionCollection *coll)
{
if (!coll) {
return;
}
const action_data actionDataNew = {
"window_new_tab", i18n("New Tab"), i18n("Open a new tab"),
- "tab-new-background", this, [this](bool) { d->slotNewTab(); }, QStringLiteral("CTRL+SHIFT+N")
+ "tab-new-background", this, [this](bool) { d->slotNewTab(); }, QStringLiteral("CTRL+SHIFT+N"),
};
d->newAction = make_action_from_data(actionDataNew, coll);
const std::vector<action_data> actionData = {
{
Actions::Rename, i18n("Rename Tab..."), i18n("Rename this tab"),
- "edit-rename", this, [this](bool) { d->slotRenameCurrentTab(); }, QStringLiteral("CTRL+SHIFT+R"), RegularQAction, Disabled
+ "edit-rename", this, [this](bool) { d->slotRenameCurrentTab(); }, QStringLiteral("CTRL+SHIFT+R"), RegularQAction, Disabled,
},
{
Actions::Duplicate, i18n("Duplicate Tab"), i18n("Duplicate this tab"),
- "tab-duplicate", this, [this](bool) { d->slotDuplicateCurrentTab(); }, QStringLiteral("CTRL+SHIFT+D")
+ "tab-duplicate", this, [this](bool) { d->slotDuplicateCurrentTab(); }, QStringLiteral("CTRL+SHIFT+D"),
},
{
Actions::Close, i18n("Close Tab"), i18n("Close this tab"),
- "tab-close", this, [this](bool) { d->slotCloseCurrentTab(); }, QStringLiteral("CTRL+SHIFT+W"), RegularQAction, Disabled
+ "tab-close", this, [this](bool) { d->slotCloseCurrentTab(); }, QStringLiteral("CTRL+SHIFT+W"), RegularQAction, Disabled,
}, // ### CTRL-W when available
{
Actions::MoveLeft, i18n("Move Tab Left"), i18n("Move this tab left"),
- nullptr, this, [this](bool) { d->slotMoveCurrentTabLeft(); }, QStringLiteral("CTRL+SHIFT+LEFT"), RegularQAction, Disabled
+ nullptr, this, [this](bool) { d->slotMoveCurrentTabLeft(); }, QStringLiteral("CTRL+SHIFT+LEFT"), RegularQAction, Disabled,
},
{
Actions::MoveRight, i18n("Move Tab Right"), i18n("Move this tab right"),
- nullptr, this, [this](bool) { d->slotMoveCurrentTabRight(); }, QStringLiteral("CTRL+SHIFT+RIGHT"), RegularQAction, Disabled
+ nullptr, this, [this](bool) { d->slotMoveCurrentTabRight(); }, QStringLiteral("CTRL+SHIFT+RIGHT"), RegularQAction, Disabled,
},
{
Actions::Hierarchical, i18n("Hierarchical Certificate List"), QString(),
- nullptr, this, [this](bool on) { d->slotToggleHierarchicalView(on); }, QString(), KFToggleAction, Disabled
+ nullptr, this, [this](bool on) { d->slotToggleHierarchicalView(on); }, QString(), KFToggleAction, Disabled,
},
{
Actions::ExpandAll, i18n("Expand All"), QString(),
- nullptr, this, [this](bool) { d->slotExpandAll(); }, QStringLiteral("CTRL+."), RegularQAction, Disabled
+ nullptr, this, [this](bool) { d->slotExpandAll(); }, QStringLiteral("CTRL+."), RegularQAction, Disabled,
},
{
Actions::CollapseAll, i18n("Collapse All"), QString(),
- nullptr, this, [this](bool) { d->slotCollapseAll(); }, QStringLiteral("CTRL+,"), RegularQAction, Disabled
+ nullptr, this, [this](bool) { d->slotCollapseAll(); }, QStringLiteral("CTRL+,"), RegularQAction, Disabled,
},
};
for (const auto &ad : actionData) {
d->currentPageActions.insert(ad.name, make_action_from_data(ad, coll));
}
for (const auto &ad : actionData) {
// create actions for the context menu of the currently not active tabs,
// but do not add those actions to the action collection
auto action = new QAction(ad.text, coll);
if (ad.icon) {
action->setIcon(QIcon::fromTheme(QLatin1String(ad.icon)));
}
action->setEnabled(ad.actionState == Enabled);
d->otherPageActions.insert(ad.name, action);
}
d->newTabButton->setDefaultAction(d->newAction);
d->tabWidget->setCornerWidget(d->newTabButton, Qt::TopLeftCorner);
if (auto action = d->currentPageActions.get(Actions::Close)) {
d->closeTabButton->setDefaultAction(action);
d->tabWidget->setCornerWidget(d->closeTabButton, Qt::TopRightCorner);
} else {
d->closeTabButton->setVisible(false);
}
d->actionsCreated = true;
}
QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text)
{
const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", d->tabWidget->count()));
Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group);
return d->addView(page, d->currentPage());
}
QAbstractItemView *TabWidget::addView(const KConfigGroup &group)
{
return d->addView(new Page(group), nullptr);
}
QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip)
{
const KConfigGroup group = KSharedConfig::openConfig()->group("KeyTreeView_default");
Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group);
page->setTemporary(true);
QAbstractItemView *v = d->addView(page, d->currentPage());
d->tabWidget->setCurrentIndex(d->tabWidget->count() - 1);
return v;
}
QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference)
{
if (!page) {
return nullptr;
}
if (!actionsCreated) {
auto coll = new KActionCollection(q);
q->createActions(coll);
}
page->setFlatModel(flatModel);
page->setHierarchicalModel(hierarchicalModel);
connect(page, &Page::titleChanged, q, [this](const QString &text) { slotPageTitleChanged(text); });
connect(page, &Page::keyFilterChanged, q, [this](const std::shared_ptr<Kleo::KeyFilter> &filter) { slotPageKeyFilterChanged(filter); });
connect(page, &Page::stringFilterChanged, q, [this](const QString &text) { slotPageStringFilterChanged(text); });
connect(page, &Page::hierarchicalChanged, q, [this](bool on) { slotPageHierarchyChanged(on); });
if (columnReference) {
page->setColumnSizes(columnReference->columnSizes());
page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder());
}
QAbstractItemView *const previous = q->currentView();
const int tabIndex = tabWidget->addTab(page, page->title());
setTabOrder(closeTabButton, page->view());
tabWidget->setTabToolTip(tabIndex, page->toolTip());
// work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted
QAbstractItemView *const current = q->currentView();
if (previous != current) {
currentIndexChanged(tabWidget->currentIndex());
}
enableDisableCurrentPageActions();
QTreeView *view = page->view();
Q_EMIT q->viewAdded(view);
return view;
}
static QStringList extractViewGroups(const KConfig *config)
{
return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList();
}
// work around deleteGroup() not deleting groups out of groupList():
static const bool KCONFIG_DELETEGROUP_BROKEN = true;
void TabWidget::loadViews(const KConfig *config)
{
if (config) {
QStringList groupList = extractViewGroups(config);
groupList.sort();
for (const QString &group : std::as_const(groupList)) {
const KConfigGroup kcg(config, group);
if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) {
addView(kcg);
}
}
}
if (!count()) {
// add default view:
addView(i18n("All Certificates"), QStringLiteral("all-certificates"));
}
}
void TabWidget::saveViews(KConfig *config) const
{
if (!config) {
return;
}
const auto extraView{extractViewGroups(config)};
for (const QString &group : extraView) {
config->deleteGroup(group);
}
unsigned int vg = 0;
for (unsigned int i = 0, end = count(); i != end; ++i) {
if (const Page *const p = d->page(i)) {
if (p->isTemporary()) {
continue;
}
KConfigGroup group(config, QString::asprintf("View #%u", vg++));
p->saveTo(group);
if (KCONFIG_DELETEGROUP_BROKEN) {
group.writeEntry("magic", 0xFA1AFE1U);
}
}
}
}
void TabWidget::connectSearchBar(SearchBar *sb)
{
connect(sb, &SearchBar::stringFilterChanged, this, &TabWidget::setStringFilter);
connect(this, &TabWidget::stringFilterChanged, sb, &SearchBar::setStringFilter);
connect(sb, &SearchBar::keyFilterChanged, this, &TabWidget::setKeyFilter);
connect(this, &TabWidget::keyFilterChanged, sb, &SearchBar::setKeyFilter);
connect(this, &TabWidget::enableChangeStringFilter, sb, &SearchBar::setChangeStringFilterEnabled);
connect(this, &TabWidget::enableChangeKeyFilter, sb, &SearchBar::setChangeKeyFilterEnabled);
}
#include "moc_tabwidget.cpp"
#include "tabwidget.moc"
diff --git a/src/view/urllabel.cpp b/src/view/urllabel.cpp
index 73860215d..9f353d52f 100644
--- a/src/view/urllabel.cpp
+++ b/src/view/urllabel.cpp
@@ -1,51 +1,51 @@
/*
view/urllabel.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021, 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 "urllabel.h"
#include <QUrl>
using namespace Kleo;
UrlLabel::UrlLabel(QWidget *parent)
: HtmlLabel{parent}
{
}
UrlLabel::~UrlLabel() = default;
void UrlLabel::setUrl(const QUrl &url, const QString &text)
{
// we prepend a zero-width-space character to work around a bug in QLabel::focusNextPrevChild(false)
// which makes it impossible to leave the label with Shift+Tab if the text starts with a link
static const QString templateString{QLatin1String{"&#8203;<a href=\"%1\">%2</a>"}};
if (url.isEmpty()) {
HtmlLabel::setHtml({});
return;
}
- setHtml(templateString.arg(
- url.url(QUrl::FullyEncoded),
+ setHtml(templateString.arg( //
+ url.url(QUrl::FullyEncoded), //
text.isEmpty() ? url.toDisplayString().toHtmlEscaped() : text.toHtmlEscaped()));
}
void UrlLabel::focusInEvent(QFocusEvent *event)
{
// immediately focus the URL when the label get focus
QLabel::focusInEvent(event);
if (!hasSelectedText()) {
QMetaObject::invokeMethod(this, [this]() {
focusNextPrevChild(true);
}, Qt::QueuedConnection);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 8, 11:10 AM (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
58/68/8527a8c47401a368adb97007a457

Event Timeline