diff --git a/src/dialogs/revokerswidget.cpp b/src/dialogs/revokerswidget.cpp index ac26b3dce..3d928d93e 100644 --- a/src/dialogs/revokerswidget.cpp +++ b/src/dialogs/revokerswidget.cpp @@ -1,164 +1,164 @@ // SPDX-FileCopyrightText: 2024 g10 Code GmbH // SPDX-FileContributor: Tobias Fella <tobias.fella@gnupg.com> // SPDX-License-Identifier: GPL-2.0-or-later #include "revokerswidget.h" #include "commands/command.h" #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <Libkleo/KeyList> #include <Libkleo/TreeWidget> #include <gpgme++/key.h> #include <gpgme.h> #include <KLocalizedString> #include <KStandardAction> #include <QClipboard> #include <QGuiApplication> #include <QKeyEvent> #include <QMenu> #include <QVBoxLayout> using namespace Kleo; class RevokersWidget::Private { public: RevokersWidget *const q; enum Column { Fingerprint, Name, Email, }; Private(RevokersWidget *qq) : q(qq) , ui{qq} { connect(ui.revokersTree, &QTreeWidget::doubleClicked, q, [this]() { const auto index = ui.revokersTree->currentIndex(); if (!index.isValid()) { return; } #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0 const auto fingerprint = QString::fromLatin1(key.revocationKey(ui.revokersTree->currentIndex().row()).fingerprint()); auto cmd = Command::commandForQuery(fingerprint); - cmd->setParentWId(q->winId()); + cmd->setParentWidget(q->window()); cmd->start(); #endif }); } public: GpgME::Key key; public: struct UI { QVBoxLayout *mainLayout; TreeWidget *revokersTree; UI(QWidget *widget) { mainLayout = new QVBoxLayout{widget}; mainLayout->setContentsMargins({}); revokersTree = new TreeWidget{widget}; revokersTree->setProperty("_breeze_force_frame", true); revokersTree->setHeaderLabels({ i18nc("@title:column", "Fingerprint"), i18nc("@title:column", "Name"), i18nc("@title:column", "Email"), }); revokersTree->setAccessibleName(i18nc("@label", "Revokers")); revokersTree->setContextMenuPolicy(Qt::CustomContextMenu); revokersTree->setRootIsDecorated(false); mainLayout->addWidget(revokersTree); connect(revokersTree, &QTreeWidget::customContextMenuRequested, widget, [widget, this](const auto &pos) { auto menu = new QMenu; menu->setAttribute(Qt::WA_DeleteOnClose, true); menu->addAction(KStandardAction::copy( widget, [this]() { QGuiApplication::clipboard()->setText(revokersTree->currentIndex().data(KeyList::ClipboardRole).toString()); }, widget)); menu->popup(widget->mapToGlobal(pos)); }); } } ui; }; RevokersWidget::RevokersWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } RevokersWidget::~RevokersWidget() = default; void RevokersWidget::setKey(const GpgME::Key &key) { if (key.protocol() != GpgME::OpenPGP) { return; } d->key = key; d->ui.revokersTree->clear(); #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0 for (size_t i = 0; i < key.numRevocationKeys(); i++) { auto item = new QTreeWidgetItem; auto revoker = key.revocationKey(i); auto revokerKey = Kleo::KeyCache::instance()->findByFingerprint(revoker.fingerprint()); item->setData(Private::Fingerprint, Qt::DisplayRole, Formatting::prettyID(revoker.fingerprint())); item->setData(Private::Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(revoker.fingerprint())); item->setData(Private::Fingerprint, KeyList::ClipboardRole, QString::fromLatin1(revoker.fingerprint())); if (!revokerKey.isNull()) { item->setData(Private::Name, Qt::DisplayRole, Formatting::prettyName(revokerKey)); item->setData(Private::Name, KeyList::ClipboardRole, Formatting::prettyName(revokerKey)); item->setData(Private::Email, Qt::DisplayRole, Formatting::prettyEMail(revokerKey)); item->setData(Private::Email, KeyList::ClipboardRole, Formatting::prettyEMail(revokerKey)); } else { item->setData(Private::Name, Qt::DisplayRole, {}); item->setData(Private::Email, Qt::DisplayRole, {}); item->setData(Private::Name, Qt::AccessibleTextRole, i18nc("text for screen readers for an unknown name", "unknown name")); item->setData(Private::Email, Qt::AccessibleTextRole, i18nc("text for screen readers for an unknown email", "unknown email")); item->setData(Private::Name, KeyList::ClipboardRole, {}); item->setData(Private::Email, KeyList::ClipboardRole, {}); } d->ui.revokersTree->addTopLevelItem(item); } #endif QMetaObject::invokeMethod( this, [this]() { if (!d->ui.revokersTree->restoreColumnLayout(QStringLiteral("RevokersWidget"))) { for (int i = 0; i < d->ui.revokersTree->columnCount(); i++) { d->ui.revokersTree->resizeColumnToContents(i); } } }, Qt::QueuedConnection); } GpgME::Key RevokersWidget::key() const { return d->key; } void RevokersWidget::keyPressEvent(QKeyEvent *event) { if (event == QKeySequence::Copy) { QGuiApplication::clipboard()->setText(d->ui.revokersTree->currentIndex().data(KeyList::ClipboardRole).toString()); } } #include "moc_revokerswidget.cpp" diff --git a/src/dialogs/weboftrustwidget.cpp b/src/dialogs/weboftrustwidget.cpp index dd147c63f..03b37818c 100644 --- a/src/dialogs/weboftrustwidget.cpp +++ b/src/dialogs/weboftrustwidget.cpp @@ -1,440 +1,440 @@ /* 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 <dev@ingo-kloecker.de> SPDX-License-Identifier: GPL-2.0-or-later */ #include "weboftrustwidget.h" #include "commands/certifycertificatecommand.h" #include "commands/importcertificatefromkeyservercommand.h" #include "commands/revokecertificationcommand.h" #include "utils/tags.h" #include <Libkleo/Formatting> #include <Libkleo/KeyCache> #include <Libkleo/KeyHelpers> #include <Libkleo/TreeView> #include <Libkleo/UserIDListModel> #include <Libkleo/UserIDListProxyModel> #include <KLocalizedString> #include <KMessageBox> #include <KSeparator> #include <QCheckBox> #include <QDialogButtonBox> #include <QHeaderView> #include <QLabel> #include <QMenu> #include <QPushButton> #include <QVBoxLayout> #include <QGpgME/KeyListJob> #include <QGpgME/Protocol> #include <gpgme++/key.h> #include <gpgme++/keylistresult.h> #include "kleopatra_debug.h" using namespace Kleo; namespace { void addActionButton(QLayout *buttonBox, QAction *action) { if (!action) { return; } auto button = new QPushButton(buttonBox->parentWidget()); button->setText(action->text()); buttonBox->addWidget(button); button->setEnabled(action->isEnabled()); QObject::connect(action, &QAction::changed, button, [action, button]() { button->setEnabled(action->isEnabled()); }); QObject::connect(button, &QPushButton::clicked, action, &QAction::trigger); } } class WebOfTrustWidget::Private { friend class ::Kleo::WebOfTrustWidget; WebOfTrustWidget *const q; private: GpgME::Key key; UserIDListModel certificationsModel; UserIDListProxyModel proxyModel; QGpgME::KeyListJob *keyListJob = nullptr; TreeView *certificationsTV = nullptr; QAction *detailsAction = nullptr; QAction *certifyAction = nullptr; QAction *revokeAction = nullptr; QAction *fetchAction = nullptr; QLabel *notAvailableLabel = nullptr; QPushButton *moreButton = nullptr; QCheckBox *showOnlyOwnCheck = nullptr; public: Private(WebOfTrustWidget *qq) : q{qq} { certificationsModel.enableRemarks(Tags::tagsEnabled()); auto vLay = new QVBoxLayout(q); vLay->setContentsMargins({}); proxyModel.setSourceModel(&certificationsModel); certificationsTV = new TreeView{q}; certificationsTV->setAccessibleName(i18n("User IDs and certifications")); certificationsTV->setModel(&proxyModel); certificationsTV->setAllColumnsShowFocus(false); certificationsTV->setSelectionMode(QAbstractItemView::SingleSelection); if (!Tags::tagsEnabled()) { certificationsTV->hideColumn(static_cast<int>(UserIDListModel::Column::Tags)); } vLay->addWidget(certificationsTV); notAvailableLabel = new QLabel(i18nc("@info", "Certifications are not available before the certificate is imported.")); notAvailableLabel->setAlignment(Qt::AlignHCenter); notAvailableLabel->setVisible(false); vLay->addWidget(notAvailableLabel); 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(); }); } fetchAction = new QAction(QIcon::fromTheme(QStringLiteral("download")), i18nc("@action:button", "Fetch Missing Keys")); fetchAction->setToolTip(i18nc("@info:tooltip", "Look up and import all keys that were used to certify the user IDs of this key")); connect(fetchAction, &QAction::triggered, q, [this]() { fetchMissingKeys(); }); auto bbox = new QHBoxLayout; addActionButton(bbox, certifyAction); addActionButton(bbox, revokeAction); moreButton = new QPushButton(QIcon::fromTheme(QStringLiteral("application-menu")), {}); moreButton->setToolTip(i18nc("@info:tooltip", "Show more options")); bbox->addWidget(moreButton); connect(moreButton, &QPushButton::clicked, q, [this]() { auto menu = new QMenu(q); menu->addAction(detailsAction); menu->addAction(fetchAction); menu->popup(moreButton->mapToGlobal(QPoint())); }); showOnlyOwnCheck = new QCheckBox(i18nc("label:checkbox", "Show only my own certifications")); bbox->addWidget(showOnlyOwnCheck); connect(showOnlyOwnCheck, &QCheckBox::toggled, &proxyModel, &UserIDListProxyModel::setShowOnlyOwnCertifications); bbox->addStretch(1); vLay->addLayout(bbox); 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 proxyModel.userID(certificationsTV->currentIndex()); } GpgME::UserID::Signature selectedCertification() { return proxyModel.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->setParentWidget(q->window()); 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 keyCanBeCertified = Kleo::canBeCertified(key); const auto userID = selectedUserID(); const auto signature = selectedCertification(); detailsAction->setEnabled(!signature.isNull()); certifyAction->setEnabled(keyCanBeCertified && 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(i18nc("@info:tooltip", "Revocation of self-certifications is currently not possible.")); break; case CertificationIsRevocation: revokeAction->setToolTip( i18nc("@info:tooltip", "You cannot revoke this revocation certification. (But you can re-certify the corresponding user ID.)")); break; case CertificationIsExpired: revokeAction->setToolTip(i18nc("@info:tooltip", "You cannot revoke this expired certification.")); break; case CertificationIsInvalid: revokeAction->setToolTip(i18nc("@info:tooltip", "You cannot revoke this invalid certification.")); break; case CertificationKeyNotAvailable: revokeAction->setToolTip(i18nc("@info:tooltip", "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 userID = proxyModel.userID(certificationsTV->indexAt(p)); const auto signature = proxyModel.signature(certificationsTV->indexAt(p)); 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; } void fetchMissingKeys() { if (q->key().isNull()) { return; } const auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(q->key().userIDs()); auto cmd = new Kleo::ImportCertificateFromKeyserverCommand{QStringList{std::begin(missingSignerKeyIds), std::end(missingSignerKeyIds)}}; cmd->setParentWidget(q); fetchAction->setEnabled(false); connect(cmd, &Kleo::ImportCertificateFromKeyserverCommand::finished, q, [this]() { // Trigger an update when done q->setKey(q->key()); fetchAction->setEnabled(true); }); cmd->start(); } }; WebOfTrustWidget::WebOfTrustWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique<Private>(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; } if (isRemoteKey(key)) { d->certificationsTV->setVisible(false); d->notAvailableLabel->setVisible(true); d->moreButton->setEnabled(false); } d->key = key; d->certificationsModel.setKey(key); d->updateActions(); d->certificationsTV->expandAll(); d->certificationsTV->header()->resizeSections(QHeaderView::ResizeToContents); d->startSignatureListing(); d->certificationsTV->restoreColumnLayout(QStringLiteral("WebOfTrustWidget")); for (int i = 0; i < d->certificationsModel.columnCount(); i++) { d->certificationsTV->resizeColumnToContents(i); } d->fetchAction->setEnabled(!key.isBad()); } 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", "<para>An error occurred while loading the certifications: " "<message>%1</message></para>", Formatting::errorAsString(result.error())), i18nc("@title", "Certifications Loading Failed")); } d->keyListJob = nullptr; } #include "moc_weboftrustwidget.cpp"