diff --git a/src/dialogs/weboftrustdialog.cpp b/src/dialogs/weboftrustdialog.cpp index 304aaa676..620fa62b4 100644 --- a/src/dialogs/weboftrustdialog.cpp +++ b/src/dialogs/weboftrustdialog.cpp @@ -1,102 +1,123 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/weboftrustdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "weboftrustdialog.h" #include "weboftrustwidget.h" #include "commands/importcertificatefromkeyservercommand.h" #include +#include #include #include #include #include #include #include #include #include #include using namespace Kleo; +namespace +{ +void addActionButton(QDialogButtonBox *buttonBox, QAction *action) +{ + if (!action) { + return; + } + auto button = buttonBox->addButton(action->text(), QDialogButtonBox::ActionRole); + button->setEnabled(action->isEnabled()); + QObject::connect(action, &QAction::changed, button, [action, button]() { + button->setEnabled(action->isEnabled()); + }); + QObject::connect(button, &QPushButton::clicked, action, &QAction::trigger); +} +} + WebOfTrustDialog::WebOfTrustDialog(QWidget *parent) : QDialog(parent) { KConfigGroup dialog(KSharedConfig::openStateConfig(), "WebOfTrustDialog"); const QSize size = dialog.readEntry("Size", QSize(900, 400)); if (size.isValid()) { resize(size); } setWindowTitle(i18nc("@title:window", "Certifications")); mWidget = new WebOfTrustWidget(this); auto l = new QVBoxLayout(this); l->addWidget(mWidget); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::pressed, this, &QDialog::accept); + addActionButton(bbox, mWidget->detailsAction()); + addActionButton(bbox, mWidget->certifyAction()); + addActionButton(bbox, mWidget->revokeAction()); + mFetchKeysBtn = bbox->addButton(i18nc("@action:button", "Fetch Missing Keys"), QDialogButtonBox::ActionRole); mFetchKeysBtn->setToolTip(i18nc("@info:tooltip", "Look up and import all keys that were used to certify the user IDs of this key")); connect(mFetchKeysBtn, &QPushButton::pressed, this, &WebOfTrustDialog::fetchMissingKeys); #ifndef QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID mFetchKeysBtn->setVisible(false); #endif l->addWidget(bbox); } void WebOfTrustDialog::setKey(const GpgME::Key &key) { mWidget->setKey(key); mFetchKeysBtn->setEnabled(!key.isBad()); } GpgME::Key WebOfTrustDialog::key() const { return mWidget->key(); } WebOfTrustDialog::~WebOfTrustDialog() { KConfigGroup dialog(KSharedConfig::openStateConfig(), "WebOfTrustDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } void WebOfTrustDialog::fetchMissingKeys() { if (key().isNull()) { return; } const auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(key().userIDs()); auto cmd = new Kleo::ImportCertificateFromKeyserverCommand{QStringList{std::begin(missingSignerKeyIds), std::end(missingSignerKeyIds)}}; cmd->setParentWidget(this); mFetchKeysBtn->setEnabled(false); connect(cmd, &Kleo::ImportCertificateFromKeyserverCommand::finished, this, [this]() { // Trigger an update when done setKey(key()); mFetchKeysBtn->setEnabled(true); }); cmd->start(); } diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp index 30032ad2e..c2020d6f9 100644 --- a/src/dialogs/weboftrustwidget.cpp +++ b/src/dialogs/weboftrustwidget.cpp @@ -1,331 +1,346 @@ /* 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 "utils/keys.h" #include "utils/tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; class WebOfTrustWidget::Private { friend class ::Kleo::WebOfTrustWidget; WebOfTrustWidget *const q; private: GpgME::Key key; UserIDListModel certificationsModel; QGpgME::KeyListJob *keyListJob = nullptr; NavigatableTreeView *certificationsTV = nullptr; QAction *detailsAction = nullptr; QAction *certifyAction = nullptr; QAction *revokeAction = nullptr; public: Private(WebOfTrustWidget *qq) : q{qq} { certificationsModel.enableRemarks(Tags::tagsEnabled()); certificationsTV = new NavigatableTreeView{q}; certificationsTV->setAccessibleName(i18n("User IDs and certifications")); certificationsTV->setModel(&certificationsModel); certificationsTV->setAllColumnsShowFocus(false); certificationsTV->setSelectionMode(QAbstractItemView::SingleSelection); if (!Tags::tagsEnabled()) { certificationsTV->hideColumn(static_cast(UserIDListModel::Column::Tags)); } auto vLay = new QVBoxLayout(q); vLay->setContentsMargins(0, 0, 0, 0); vLay->addWidget(certificationsTV); detailsAction = new QAction{QIcon::fromTheme(QStringLiteral("dialog-information")), i18nc("@action", "Show Certificate Details"), q}; connect(detailsAction, &QAction::triggered, q, [this]() { showCertificateDetails(); }); certifyAction = new QAction{QIcon::fromTheme(QStringLiteral("view-certificate-sign")), i18nc("@action", "Add Certification"), q}; connect(certifyAction, &QAction::triggered, q, [this]() { addCertification(); }); if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { revokeAction = new QAction{QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action", "Revoke Certification"), q}; connect(revokeAction, &QAction::triggered, q, [this]() { revokeCertification(); }); } connect(certificationsTV, &QAbstractItemView::doubleClicked, q, [this]() { certificationDblClicked(); }); certificationsTV->setContextMenuPolicy(Qt::CustomContextMenu); connect(certificationsTV, &QWidget::customContextMenuRequested, q, [this] (const QPoint &p) { contextMenuRequested(p); }); connect(certificationsTV->selectionModel(), &QItemSelectionModel::currentRowChanged, q, [this]() { updateActions(); }); updateActions(); } GpgME::UserID selectedUserID() { return certificationsModel.userID(certificationsTV->currentIndex()); } GpgME::UserID::Signature selectedCertification() { return certificationsModel.signature(certificationsTV->currentIndex()); } void certificationDblClicked() { showCertificateDetails(); } void showCertificateDetails() { const auto signature = selectedCertification(); if (signature.isNull()) { qCDebug(KLEOPATRA_LOG) << __func__ << "- no certification selected"; return; } auto cmd = Command::commandForQuery(QString::fromUtf8(signature.signerKeyID())); cmd->setParentWId(q->winId()); cmd->start(); } void addCertification() { auto userID = selectedUserID(); if (userID.isNull()) { userID = selectedCertification().parent(); } if (userID.isNull()) { qCDebug(KLEOPATRA_LOG) << __func__ << "- no user ID or certification selected"; return; } 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(); } void revokeCertification() { Command *cmd = nullptr; if (const auto signature = selectedCertification(); !signature.isNull()) { cmd = new Kleo::Commands::RevokeCertificationCommand(signature); } else if (const auto userID = selectedUserID(); !userID.isNull()) { cmd = new Kleo::Commands::RevokeCertificationCommand(userID); } else { qCDebug(KLEOPATRA_LOG) << __func__ << "- no user ID or certification selected"; return; } 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 addActionsForUserID(QMenu *menu) { menu->addAction(certifyAction); if (revokeAction) { menu->addAction(revokeAction); } } void addActionsForSignature(QMenu *menu) { menu->addAction(detailsAction); menu->addAction(certifyAction); if (revokeAction) { menu->addAction(revokeAction); if (!revokeAction->isEnabled()) { menu->setToolTipsVisible(true); } } } void updateActions() { const auto userCanSignUserIDs = userHasCertificationKey(); const auto userID = selectedUserID(); const auto signature = selectedCertification(); detailsAction->setEnabled(!signature.isNull()); certifyAction->setEnabled(userCanSignUserIDs && (!userID.isNull() || !signature.isNull())); if (revokeAction) { revokeAction->setToolTip({}); if (!signature.isNull()) { const auto revocationFeasibility = userCanRevokeCertification(signature); revokeAction->setEnabled(revocationFeasibility == CertificationCanBeRevoked); switch (revocationFeasibility) { case CertificationCanBeRevoked: break; case CertificationNotMadeWithOwnKey: revokeAction->setToolTip(i18n("You cannot revoke this certification because it wasn't made with one of your keys (or the required secret key is missing).")); break; case CertificationIsSelfSignature: revokeAction->setToolTip(i18n("Revocation of self-certifications is currently not possible.")); break; case CertificationIsRevocation: revokeAction->setToolTip(i18n("You cannot revoke this revocation certification. (But you can re-certify the corresponding user ID.)")); break; case CertificationIsExpired: revokeAction->setToolTip(i18n("You cannot revoke this expired certification.")); break; case CertificationIsInvalid: revokeAction->setToolTip(i18n("You cannot revoke this invalid certification.")); break; case CertificationKeyNotAvailable: revokeAction->setToolTip(i18n("You cannot revoke this certification because the required secret key is not available.")); break; }; } else if (!userID.isNull()) { const bool canRevokeCertification = userCanRevokeCertifications(userID); revokeAction->setEnabled(canRevokeCertification); if (!canRevokeCertification) { revokeAction->setToolTip(i18n("You cannot revoke any of the certifications of this user ID. Select any of the certifications for details.")); } } else { revokeAction->setEnabled(false); } } } 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; } auto menu = new QMenu(q); if (!userID.isNull()) { addActionsForUserID(menu); } else if (!signature.isNull()) { addActionsForSignature(menu); } 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 (Tags::tagsEnabled()) { job->addMode(GpgME::SignatureNotations); } 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{std::make_unique(this)} { } WebOfTrustWidget::~WebOfTrustWidget() = default; +QAction *WebOfTrustWidget::detailsAction() const +{ + return d->detailsAction; +} + +QAction *WebOfTrustWidget::certifyAction() const +{ + return d->certifyAction; +} + +QAction *WebOfTrustWidget::revokeAction() const +{ + return d->revokeAction; +} + GpgME::Key WebOfTrustWidget::key() const { return d->key; } void WebOfTrustWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { qCDebug(KLEOPATRA_LOG) << "List of Certifications is only supported for OpenPGP keys"; return; } d->key = key; d->certificationsModel.setKey(key); d->updateActions(); d->certificationsTV->expandAll(); d->certificationsTV->header()->resizeSections(QHeaderView::ResizeToContents); d->startSignatureListing(); } 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; } diff --git a/src/dialogs/weboftrustwidget.h b/src/dialogs/weboftrustwidget.h index 941dee5ad..f3029f0d9 100644 --- a/src/dialogs/weboftrustwidget.h +++ b/src/dialogs/weboftrustwidget.h @@ -1,39 +1,43 @@ /* SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include namespace GpgME { class Key; class KeyListResult; } namespace Kleo { class WebOfTrustWidget : public QWidget { Q_OBJECT public: explicit WebOfTrustWidget(QWidget *parent = nullptr); ~WebOfTrustWidget() override; + QAction *detailsAction() const; + QAction *certifyAction() const; + QAction *revokeAction() const; + void setKey(const GpgME::Key &key); GpgME::Key key() const; private Q_SLOTS: void signatureListingNextKey(const GpgME::Key &key); void signatureListingDone(const GpgME::KeyListResult &result); private: class Private; const std::unique_ptr d; }; } // namespace Kleo