diff --git a/src/commands/revokecertificationcommand.cpp b/src/commands/revokecertificationcommand.cpp index d2eaf8e20..102a92a65 100644 --- a/src/commands/revokecertificationcommand.cpp +++ b/src/commands/revokecertificationcommand.cpp @@ -1,239 +1,251 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/revokecertificationcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "revokecertificationcommand.h" #include "command_p.h" #include "exportopenpgpcertstoservercommand.h" #include "dialogs/revokecertificationdialog.h" #include +#include #include #include #include -#include #include #include "kleopatra_debug.h" #include #if GPGMEPP_VERSION >= 0x10E01 // 1.14.1 # define GPGME_HAS_REVSIG #endif using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; using namespace QGpgME; class RevokeCertificationCommand::Private : public Command::Private { friend class ::Kleo::Commands::RevokeCertificationCommand; RevokeCertificationCommand *q_func() const { return static_cast(q); } public: explicit Private(RevokeCertificationCommand *qq, KeyListController *c); ~Private(); void init(); private: void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const Error &err); private: void ensureDialogCreated(); void createJob(); private: + Key certificationKey; Key certificationTarget; std::vector uids; QPointer dialog; QPointer job; }; RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() { return static_cast(d.get()); } const RevokeCertificationCommand::Private *RevokeCertificationCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RevokeCertificationCommand::Private::Private(RevokeCertificationCommand *qq, KeyListController *c) : Command::Private(qq, c) { } RevokeCertificationCommand::Private::~Private() { } void RevokeCertificationCommand::Private::init() { const std::vector keys_ = keys(); if (keys_.size() != 1) { qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected exactly one key, but got" << keys_.size(); return; } if (keys_.front().protocol() != GpgME::OpenPGP) { qCWarning(KLEOPATRA_LOG) << "RevokeCertificationCommand::Private::init: Expected OpenPGP key, but got" << keys_.front().protocolAsString(); return; } certificationTarget = keys_.front(); } void RevokeCertificationCommand::Private::slotDialogAccepted() { createJob(); #ifdef GPGME_HAS_REVSIG job->startRevokeSignature(certificationTarget, dialog->selectedCertificationKey(), dialog->selectedUserIDs()); #endif } void RevokeCertificationCommand::Private::slotDialogRejected() { canceled(); } void RevokeCertificationCommand::Private::slotResult(const Error &err) { if (!err && !err.isCanceled() && dialog && dialog->sendToServer()) { ExportOpenPGPCertsToServerCommand *const cmd = new ExportOpenPGPCertsToServerCommand(certificationTarget); cmd->start(); } else if (!err) { information(i18n("Revocation successful."), i18n("Revocation Succeeded")); } else { error(i18n("

An error occurred while trying to revoke the certification of

" "%1:

\t%2

", Formatting::formatForComboBox(certificationTarget), QString::fromUtf8(err.asString())), i18n("Revocation Error")); } finished(); } void RevokeCertificationCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new RevokeCertificationDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, SIGNAL(accepted()), q, SLOT(slotDialogAccepted())); connect(dialog, SIGNAL(rejected()), q, SLOT(slotDialogRejected())); } void RevokeCertificationCommand::Private::createJob() { Q_ASSERT(!job); Q_ASSERT(certificationTarget.protocol() == OpenPGP); const auto backend = QGpgME::openpgp(); if (!backend) { return; } QuickJob *const j = backend->quickJob(); if (!j) { return; } connect(j, &Job::progress, q, &Command::progress); connect(j, SIGNAL(result(GpgME::Error)), q, SLOT(slotResult(GpgME::Error))); job = j; } RevokeCertificationCommand::RevokeCertificationCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID &uid) : Command(uid.parent(), new Private(this, nullptr)) { std::vector(1, uid).swap(d->uids); d->init(); } +RevokeCertificationCommand::RevokeCertificationCommand(const GpgME::UserID::Signature &signature) + : Command(signature.parent().parent(), new Private(this, nullptr)) +{ + std::vector(1, signature.parent()).swap(d->uids); + d->certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()); + d->init(); +} + RevokeCertificationCommand::~RevokeCertificationCommand() { qCDebug(KLEOPATRA_LOG) << "~RevokeCertificationCommand()"; } // static bool RevokeCertificationCommand::isSupported() { #ifdef GPGME_HAS_REVSIG return engineInfo(GpgEngine).engineVersion() >= "2.2.24"; #else return false; #endif } void RevokeCertificationCommand::doStart() { if (d->certificationTarget.isNull()) { d->finished(); return; } for (const UserID &uid : qAsConst(d->uids)) if (qstricmp(uid.parent().primaryFingerprint(), d->certificationTarget.primaryFingerprint()) != 0) { qCWarning(KLEOPATRA_LOG) << "User-ID <-> Key mismatch!"; d->finished(); return; } d->ensureDialogCreated(); Q_ASSERT(d->dialog); d->dialog->setCertificateToRevoke(d->certificationTarget); if (!d->uids.empty()) { d->dialog->setSelectedUserIDs(d->uids); } + if (!d->certificationKey.isNull()) { + d->dialog->setSelectedCertificationKey(d->certificationKey); + } d->dialog->show(); } void RevokeCertificationCommand::doCancel() { qCDebug(KLEOPATRA_LOG) << "RevokeCertificationCommand::doCancel()"; if (d->job) { d->job->slotCancel(); } } #undef d #undef q #include "moc_revokecertificationcommand.cpp" diff --git a/src/commands/revokecertificationcommand.h b/src/commands/revokecertificationcommand.h index 8832cc9f1..886fe22f6 100644 --- a/src/commands/revokecertificationcommand.h +++ b/src/commands/revokecertificationcommand.h @@ -1,57 +1,55 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/revokecertificationcommand.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ #define __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ #include -namespace GpgME -{ -class UserID; -} +#include namespace Kleo { namespace Commands { class RevokeCertificationCommand : public Command { Q_OBJECT public: explicit RevokeCertificationCommand(QAbstractItemView *view, KeyListController *parent); explicit RevokeCertificationCommand(const GpgME::UserID &uid); + explicit RevokeCertificationCommand(const GpgME::UserID::Signature &signature); ~RevokeCertificationCommand() override; /* reimp */ static Restrictions restrictions() { return OnlyOneKey | MustBeOpenPGP; } static bool isSupported(); private: void doStart() override; void doCancel() override; private: class Private; inline Private *d_func(); inline const Private *d_func() const; Q_PRIVATE_SLOT(d_func(), void slotResult(GpgME::Error)) Q_PRIVATE_SLOT(d_func(), void slotDialogAccepted()) Q_PRIVATE_SLOT(d_func(), void slotDialogRejected()) }; } // namespace Commands } // namespace Kleo #endif // __KLEOPATRA_COMMANDS_REVOKECERTIFICATIONCOMMAND_H__ diff --git a/src/dialogs/revokecertificationdialog.cpp b/src/dialogs/revokecertificationdialog.cpp index 24fbff735..a49e75535 100644 --- a/src/dialogs/revokecertificationdialog.cpp +++ b/src/dialogs/revokecertificationdialog.cpp @@ -1,138 +1,143 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokecertificationdialog.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 SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "revokecertificationdialog.h" #include "revokecertificationwidget.h" #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace GpgME; using namespace Kleo; class RevokeCertificationDialog::Private { friend class ::Kleo::RevokeCertificationDialog; RevokeCertificationDialog *const q; public: explicit Private(RevokeCertificationDialog *qq); ~Private(); private: void saveGeometry(); void restoreGeometry(const QSize &defaultSize); private: RevokeCertificationWidget *mainWidget = nullptr; }; RevokeCertificationDialog::Private::Private(RevokeCertificationDialog *qq) : q(qq) { } RevokeCertificationDialog::Private::~Private() { } void RevokeCertificationDialog::Private::saveGeometry() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "RevokeCertificationDialog"); cfgGroup.writeEntry("geometry", q->saveGeometry()); cfgGroup.sync(); } void RevokeCertificationDialog::Private::restoreGeometry(const QSize &defaultSize) { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "RevokeCertificationDialog"); const QByteArray geometry = cfgGroup.readEntry("geometry", QByteArray()); if (!geometry.isEmpty()) { q->restoreGeometry(geometry); } else { q->resize(defaultSize); } } RevokeCertificationDialog::RevokeCertificationDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f) , d(new Private(this)) { setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); auto mainLay = new QVBoxLayout(this); d->mainWidget = new RevokeCertificationWidget(this); mainLay->addWidget(d->mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(); mainLay->addWidget(buttonBox); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Revoke Certification")); connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, [this] () { d->mainWidget->saveConfig(); accept(); }); connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, [this] () { close(); }); d->restoreGeometry(QSize(640, 480)); } RevokeCertificationDialog::~RevokeCertificationDialog() { d->saveGeometry(); } void RevokeCertificationDialog::setCertificateToRevoke(const Key &key) { setWindowTitle(i18nc("@title:window arg is name, email of certificate holder", "Revoke Certification: %1", Formatting::prettyName(key))); d->mainWidget->setTarget(key); } void RevokeCertificationDialog::setSelectedUserIDs(const std::vector &uids) { d->mainWidget->setSelectUserIDs(uids); } std::vector RevokeCertificationDialog::selectedUserIDs() const { return d->mainWidget->selectedUserIDs(); } +void Kleo::RevokeCertificationDialog::setSelectedCertificationKey(const GpgME::Key &key) +{ + d->mainWidget->setCertificationKey(key); +} + Key RevokeCertificationDialog::selectedCertificationKey() const { return d->mainWidget->certificationKey(); } bool RevokeCertificationDialog::sendToServer() const { return d->mainWidget->publishSelected(); } diff --git a/src/dialogs/revokecertificationdialog.h b/src/dialogs/revokecertificationdialog.h index 1fdf1203f..bf7af90c6 100644 --- a/src/dialogs/revokecertificationdialog.h +++ b/src/dialogs/revokecertificationdialog.h @@ -1,49 +1,50 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokecertificationdialog.h 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 SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ #define __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ #include namespace GpgME { class Key; class UserID; } namespace Kleo { class RevokeCertificationDialog : public QDialog { Q_OBJECT public: explicit RevokeCertificationDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~RevokeCertificationDialog() override; void setCertificateToRevoke(const GpgME::Key &key); void setSelectedUserIDs(const std::vector &uids); std::vector selectedUserIDs() const; + void setSelectedCertificationKey(const GpgME::Key &key); GpgME::Key selectedCertificationKey() const; bool sendToServer() const; private: class Private; const std::unique_ptr d; }; } // namespace Kleo #endif /* __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONDIALOG_H__ */ diff --git a/src/dialogs/revokecertificationwidget.cpp b/src/dialogs/revokecertificationwidget.cpp index 85a1b4af3..5cba2c5f5 100644 --- a/src/dialogs/revokecertificationwidget.cpp +++ b/src/dialogs/revokecertificationwidget.cpp @@ -1,265 +1,270 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokecertificationwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "revokecertificationwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_UIDHASH #endif using namespace Kleo; namespace { class CertificationKeyFilter: public DefaultKeyFilter { public: CertificationKeyFilter(const GpgME::Key &certificationTarget); bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override; private: GpgME::Key mCertificationTarget; // the key to certify or to revoke the certification of }; CertificationKeyFilter::CertificationKeyFilter(const GpgME::Key &certificationTarget) : DefaultKeyFilter() , mCertificationTarget(certificationTarget) { setIsOpenPGP(DefaultKeyFilter::Set); setHasSecret(DefaultKeyFilter::Set); setCanCertify(DefaultKeyFilter::Set); setIsBad(DefaultKeyFilter::NotSet); } bool CertificationKeyFilter::matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const { if (!(availableMatchContexts() & contexts)) { return false; } // exclude certification target from list of certification keys if (qstrcmp(key.primaryFingerprint(), mCertificationTarget.primaryFingerprint()) == 0) { return false; } return DefaultKeyFilter::matches(key, contexts); } static bool uidsAreEqual(const GpgME::UserID &lhs, const GpgME::UserID &rhs) { // use uidhash if available #ifdef GPGME_HAS_UIDHASH if (lhs.uidhash() && rhs.uidhash()) { return strcmp(lhs.uidhash(), rhs.uidhash()) == 0; } #endif // compare actual user ID string and primary key; this is not unique, but it's all we can do if uidhash is missing return qstrcmp(lhs.id(), rhs.id()) == 0 && qstrcmp(lhs.parent().primaryFingerprint(), rhs.parent().primaryFingerprint()) == 0; } class UserIDModel : public QStandardItemModel { Q_OBJECT public: explicit UserIDModel(QObject *parent = nullptr) : QStandardItemModel(parent) { } void setKey(const GpgME::Key &key) { mKey = key; clear(); const std::vector uids = key.userIDs(); for (const auto &uid : uids) { QStandardItem *const item = new QStandardItem; item->setText(Formatting::prettyUserID(uid)); item->setCheckable(true); item->setEditable(false); item->setCheckState(Qt::Checked); appendRow(item); } } void setCheckedUserIDs(const std::vector &checkedUids) { const auto keyUids = mKey.userIDs(); Q_ASSERT(rowCount() == static_cast(keyUids.size())); for (int i = 0; i < rowCount(); ++i) { const auto &keyUid = keyUids[i]; const bool uidIsChecked = std::find_if(checkedUids.cbegin(), checkedUids.cend(), [keyUid](const GpgME::UserID &checkedUid) { return uidsAreEqual(keyUid, checkedUid); }) != checkedUids.cend(); item(i)->setCheckState(uidIsChecked ? Qt::Checked : Qt::Unchecked); } } std::vector checkedUserIDs() const { const auto keyUids = mKey.userIDs(); Q_ASSERT(rowCount() == static_cast(keyUids.size())); std::vector checkedUids; for (int i = 0; i < rowCount(); ++i) { if (item(i)->checkState() == Qt::Checked) { checkedUids.push_back(keyUids[i]); } } return checkedUids; } private: GpgME::Key mKey; }; } // unnamed namespace class RevokeCertificationWidget::Private { friend class ::Kleo::RevokeCertificationWidget; RevokeCertificationWidget *const q; QLabel *mFprLabel; KeySelectionCombo *mCertificationKeySelect; QCheckBox *mPublishCB; UserIDModel mUserIDModel; GpgME::Key mTarget; public: Private(RevokeCertificationWidget *qq) : q(qq) , mFprLabel(new QLabel) , mCertificationKeySelect(new KeySelectionCombo(/* secretOnly = */ true)) , mPublishCB(new QCheckBox) { QVBoxLayout *mainLayout = new QVBoxLayout(q); mainLayout->addWidget(mFprLabel); auto certKeyLayout = new QHBoxLayout; { auto label = new QLabel(i18n("Certification key:")); label->setToolTip(i18n("The key whose certifications shall be revoke")); certKeyLayout->addWidget(label); } certKeyLayout->addWidget(mCertificationKeySelect, 1); mainLayout->addLayout(certKeyLayout); auto splitLine = new QFrame; splitLine->setFrameShape(QFrame::HLine); splitLine->setFrameShadow(QFrame::Sunken); splitLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); mainLayout->addWidget(splitLine); auto listView = new QListView; listView->setModel(&mUserIDModel); mainLayout->addWidget(listView, 1); mPublishCB = new QCheckBox(i18n("Publish revocations on keyserver")); mainLayout->addWidget(mPublishCB); loadConfig(); } ~Private() { } void saveConfig() { KConfigGroup conf(KSharedConfig::openConfig(), "RevokeCertificationSettings"); const auto certificationKey = mCertificationKeySelect->currentKey(); if (!certificationKey.isNull()) { conf.writeEntry("LastKey", certificationKey.primaryFingerprint()); } conf.writeEntry("PublishCheckState", mPublishCB->isChecked()); } void loadConfig() { const KConfigGroup conf(KSharedConfig::openConfig(), "RevokeCertificationSettings"); mCertificationKeySelect->setDefaultKey(conf.readEntry("LastKey", QString())); mPublishCB->setChecked(conf.readEntry("PublishCheckState", false)); } }; RevokeCertificationWidget::RevokeCertificationWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } RevokeCertificationWidget::~RevokeCertificationWidget() { } void RevokeCertificationWidget::setTarget(const GpgME::Key &key) { d->mTarget = key; d->mFprLabel->setText(i18n("Fingerprint: %1", Formatting::prettyID(d->mTarget.primaryFingerprint())) + QStringLiteral("
") + i18n("Only the fingerprint clearly identifies the key and its owner.")); d->mCertificationKeySelect->setKeyFilter(std::shared_ptr(new CertificationKeyFilter(d->mTarget))); d->mUserIDModel.setKey(d->mTarget); } GpgME::Key RevokeCertificationWidget::target() const { return d->mTarget; } void RevokeCertificationWidget::setSelectUserIDs(const std::vector &uids) { d->mUserIDModel.setCheckedUserIDs(uids); } std::vector RevokeCertificationWidget::selectedUserIDs() const { return d->mUserIDModel.checkedUserIDs(); } +void RevokeCertificationWidget::setCertificationKey(const GpgME::Key &key) +{ + d->mCertificationKeySelect->setDefaultKey(QString::fromLatin1(key.primaryFingerprint())); +} + GpgME::Key RevokeCertificationWidget::certificationKey() const { return d->mCertificationKeySelect->currentKey(); } bool RevokeCertificationWidget::publishSelected() const { return d->mPublishCB->isChecked(); } void Kleo::RevokeCertificationWidget::saveConfig() const { d->saveConfig(); } // for UserIDModel #include "revokecertificationwidget.moc" diff --git a/src/dialogs/revokecertificationwidget.h b/src/dialogs/revokecertificationwidget.h index 0866bfc17..274140bfe 100644 --- a/src/dialogs/revokecertificationwidget.h +++ b/src/dialogs/revokecertificationwidget.h @@ -1,61 +1,64 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokecertificationwidget.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ #define __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ #include #include namespace GpgME { class Key; class UserID; } namespace Kleo { /** Widget for revoking OpenPGP certifications. */ class RevokeCertificationWidget : public QWidget { Q_OBJECT public: explicit RevokeCertificationWidget(QWidget *parent = nullptr); ~RevokeCertificationWidget() override; /* Set the key to revoke certifications of */ void setTarget(const GpgME::Key &key); /* Get the key to revoke certifications of */ GpgME::Key target() const; /* Select specific user ids. Default: all */ void setSelectUserIDs(const std::vector &uids); /* The user ids whose certifications shall be revoked */ std::vector selectedUserIDs() const; + /* Set the selected certification key. Default: last used key */ + void setCertificationKey(const GpgME::Key &key); + /* The selected certification key */ GpgME::Key certificationKey() const; /* Whether the revocations shall be published */ bool publishSelected() const; void saveConfig() const; private: class Private; const std::unique_ptr d; }; } // namespace Kleo #endif // __KLEOPATRA_DIALOGS_REVOKECERTIFICATIONWIDGET_H__ diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp index e63b8ad3f..6f14d27eb 100644 --- a/src/dialogs/weboftrustwidget.cpp +++ b/src/dialogs/weboftrustwidget.cpp @@ -1,219 +1,271 @@ -/* SPDX-FileCopyrightText: 2017 Intevation GmbH +/* + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2017 Intevation GmbH + SPDX-FileCopyrightText: 2020 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "weboftrustwidget.h" #include "commands/certifycertificatecommand.h" #include "commands/revokecertificationcommand.h" #include #include #include #include #include #include #include +#include #include #include "kleopatra_debug.h" #include "commands/command.h" #include "utils/remarks.h" #include #include #include using namespace Kleo; class WebOfTrustWidget::Private { friend class ::Kleo::WebOfTrustWidget; WebOfTrustWidget *const q; private: GpgME::Key key; UserIDListModel certificationsModel; QGpgME::KeyListJob *keyListJob = nullptr; QTreeView *certificationsTV = nullptr; public: Private(WebOfTrustWidget *qq) : q(qq) { certificationsModel.enableRemarks(Remarks::remarksEnabled()); certificationsTV = new QTreeView; certificationsTV->setModel(&certificationsModel); certificationsTV->setAllColumnsShowFocus(true); certificationsTV->setSelectionMode(QAbstractItemView::ExtendedSelection); auto vLay = new QVBoxLayout(q); vLay->setContentsMargins(0, 0, 0, 0); vLay->addWidget(certificationsTV); connect(certificationsTV, &QAbstractItemView::doubleClicked, q, [this] (const QModelIndex &idx) { certificationDblClicked(idx); }); certificationsTV->setContextMenuPolicy(Qt::CustomContextMenu); connect(certificationsTV, &QWidget::customContextMenuRequested, q, [this] (const QPoint &p) { contextMenuRequested(p); }); } void certificationDblClicked(const QModelIndex &idx) { if (!idx.isValid()) { return; } if (!idx.parent().isValid()) { // No parent -> root item. return; } // grab the keyid const auto query = certificationsModel.data(idx.sibling(idx.row(), 0)).toString(); // Show details widget or search auto cmd = Command::commandForQuery(query); cmd->setParentWId(q->winId()); cmd->start(); } void addActionsForUserID(QMenu *menu, const GpgME::UserID &userID) { menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), i18n("Certify..."), q, [this, userID]() { auto cmd = new Kleo::Commands::CertifyCertificateCommand(userID); cmd->setParentWidget(q); certificationsTV->setEnabled(false); connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { certificationsTV->setEnabled(true); // Trigger an update when done q->setKey(key); }); cmd->start(); }); if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18n("Revoke Certification..."), q, [this, userID]() { auto cmd = new Kleo::Commands::RevokeCertificationCommand(userID); cmd->setParentWidget(q); certificationsTV->setEnabled(false); connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished, q, [this]() { certificationsTV->setEnabled(true); // Trigger an update when done q->setKey(key); }); cmd->start(); }); } } + void addActionsForSignature(QMenu *menu, const GpgME::UserID::Signature &signature) + { + menu->addAction(QIcon::fromTheme(QStringLiteral("dialog-information")), + i18n("Show Certificate Details..."), + q, [this, signature]() { + auto cmd = Command::commandForQuery(QString::fromUtf8(signature.signerKeyID())); + cmd->setParentWId(q->winId()); + cmd->start(); + }); + if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { + auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), + i18n("Revoke Certification..."), + q, [this, signature]() { + auto cmd = new Kleo::Commands::RevokeCertificationCommand(signature); + cmd->setParentWidget(q); + certificationsTV->setEnabled(false); + connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished, + q, [this]() { + certificationsTV->setEnabled(true); + // Trigger an update when done + q->setKey(key); + }); + cmd->start(); + }); + const auto certificationKey = KeyCache::instance()->findByKeyIDOrFingerprint(signature.signerKeyID()); + const bool isSelfSignature = qstrcmp(signature.parent().parent().keyID(), signature.signerKeyID()) == 0; + action->setEnabled(!isSelfSignature && certificationKey.hasSecret() && !signature.isRevokation() && !signature.isExpired() && !signature.isInvalid()); + if (isSelfSignature) { + action->setToolTip(i18n("Revokation of self-certifications is currently not possible.")); + } else if (!certificationKey.hasSecret()) { + action->setToolTip(i18n("You cannot revoke this certification because it wasn't made with one of your keys (or the required secret key is missing).")); + } else if (signature.isRevokation()) { + action->setToolTip(i18n("You cannot revoke this revokation certification. (But you can re-certify the corresponding user ID.)")); + } else if (signature.isExpired()) { + action->setToolTip(i18n("You cannot revoke this expired certification.")); + } else if (signature.isInvalid()) { + action->setToolTip(i18n("You cannot revoke this invalid certification.")); + } + if (!action->isEnabled()) { + menu->setToolTipsVisible(true); + } + } + } + void contextMenuRequested(const QPoint &p) { const auto index = certificationsTV->indexAt(p); const auto userID = certificationsModel.userID(index); const auto signature = certificationsModel.signature(index); if (userID.isNull() && signature.isNull()) { return; } QMenu *menu = new QMenu(q); if (!userID.isNull()) { addActionsForUserID(menu, userID); } + else if (!signature.isNull()) { + addActionsForSignature(menu, signature); + } connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(certificationsTV->viewport()->mapToGlobal(p)); } void startSignatureListing() { if (keyListJob) { return; } QGpgME::KeyListJob *const job = QGpgME::openpgp()->keyListJob(/*remote*/false, /*includeSigs*/true, /*validate*/true); if (!job) { return; } if (Remarks::remarksEnabled()) { job->addMode(GpgME::SignatureNotations); } /* Old style connect here again as QGPGME newstyle connects with * default arguments don't work on windows. */ connect(job, &QGpgME::KeyListJob::result, q, &WebOfTrustWidget::signatureListingDone); connect(job, &QGpgME::KeyListJob::nextKey, q, &WebOfTrustWidget::signatureListingNextKey); job->start(QStringList(QString::fromLatin1(key.primaryFingerprint()))); keyListJob = job; } }; WebOfTrustWidget::WebOfTrustWidget(QWidget *parent) : QWidget(parent), d(new Private(this)) { } GpgME::Key WebOfTrustWidget::key() const { return d->key; } void WebOfTrustWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { qCDebug(KLEOPATRA_LOG) << "Trust chain is only supported for CMS keys"; return; } d->key = key; d->certificationsModel.setKey(key); d->certificationsTV->expandAll(); d->certificationsTV->header()->resizeSections(QHeaderView::ResizeToContents); d->startSignatureListing(); } WebOfTrustWidget::~WebOfTrustWidget() { } void WebOfTrustWidget::signatureListingNextKey(const GpgME::Key &key) { GpgME::Key merged = key; merged.mergeWith(d->key); setKey(merged); } void WebOfTrustWidget::signatureListingDone(const GpgME::KeyListResult &result) { if (result.error()) { KMessageBox::information(this, xi18nc("@info", "An error occurred while loading the certifications: " "%1", QString::fromLocal8Bit(result.error().asString())), i18nc("@title", "Certifications Loading Failed")); } d->keyListJob = nullptr; }