diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp index e0d32a24c..28d813540 100644 --- a/src/dialogs/certificatedetailswidget.cpp +++ b/src/dialogs/certificatedetailswidget.cpp @@ -1,1042 +1,1042 @@ /* 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 SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #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/adduseridcommand.h" #include "commands/genrevokecommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "utils/keys.h" #include "utils/tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HIDE_ROW(row) \ ui.row->setVisible(false); \ ui.row##Lbl->setVisible(false); #define SHOW_ROW(row) \ ui.row->setVisible(true); \ ui.row##Lbl->setVisible(true); Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; class CertificateDetailsWidget::Private { public: Private(CertificateDetailsWidget *qq); void setupCommonProperties(); void setupPGPProperties(); void setupSMIMEProperties(); void revokeUserID(const GpgME::UserID &uid); void genRevokeCert(); void refreshCertificate(); void certifyClicked(); void webOfTrustClicked(); void exportClicked(); void addUserID(); void changePassphrase(); void changeExpiration(); void keysMayHaveChanged(); void showTrustChainDialog(); void showMoreDetails(); void publishCertificate(); void userIDTableContextMenuRequested(const QPoint &p); QString tofuTooltipString(const GpgME::UserID &uid) const; void smimeLinkActivated(const QString &link); void updateKey(); void setUpdatedKey(const GpgME::Key &key); void keyListDone(const GpgME::KeyListResult &, const std::vector &, const QString &, const GpgME::Error &); void copyFingerprintToClipboard(); private: CertificateDetailsWidget *const q; public: GpgME::Key key; bool updateInProgress = false; private: struct UI { QGridLayout *gridLayout_2; QHBoxLayout *hboxLayout_1; QPushButton *addUserIDBtn; QPushButton *changePassphraseBtn; QPushButton *trustChainDetailsBtn; QPushButton *genRevokeBtn; QPushButton *refreshBtn; QPushButton *certifyBtn; QGroupBox *groupBox; QGridLayout *gridLayout; QLabel *validFromLbl; QLabel *validFrom; QSpacerItem *horizontalSpacer_3; QLabel *expiresLbl; QHBoxLayout *horizontalLayout_3; QLabel *expires; QPushButton *changeExpirationBtn; QLabel *typeLbl; QLabel *type; QLabel *fingerprintLbl; QLabel *fingerprint; QPushButton *copyFingerprintBtn; QLabel *publishingLbl; QPushButton *publishing; QLabel *smimeIssuerLbl; QLabel *smimeIssuer; QLabel *compliance; QLabel *complianceLbl; QLabel *trustedIntroducerLbl; QLabel *trustedIntroducer; QHBoxLayout *horizontalLayout; QPushButton *moreDetailsBtn; QPushButton *exportBtn; QPushButton *webOfTrustBtn; QSpacerItem *horizontalSpacer; QTreeWidget *userIDTable; QLabel *label; QLabel *smimeOwnerLbl; QLabel *smimeRelatedAddresses; QLabel *smimeOwner; void setupUi(QWidget *parent) { gridLayout_2 = new QGridLayout(parent); gridLayout_2->setContentsMargins(0, 0, 0, 0); hboxLayout_1 = new QHBoxLayout(); addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent); hboxLayout_1->addWidget(addUserIDBtn); changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent); hboxLayout_1->addWidget(changePassphraseBtn); trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details..."), parent); hboxLayout_1->addWidget(trustChainDetailsBtn); genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent); genRevokeBtn->setToolTip(u"" % 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""); hboxLayout_1->addWidget(genRevokeBtn); refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent}; #ifndef QGPGME_SUPPORTS_KEY_REFRESH refreshBtn->setVisible(false); #endif hboxLayout_1->addWidget(refreshBtn); certifyBtn = new QPushButton(i18nc("@action:button", "Certify"), parent); hboxLayout_1->addWidget(certifyBtn); gridLayout_2->addLayout(hboxLayout_1, 4, 0, 1, 3); groupBox = new QGroupBox(i18n("Certificate Details"), parent); groupBox->setFlat(false); gridLayout = new QGridLayout(groupBox); int row = 0; validFromLbl = new QLabel(i18n("Valid from:"), groupBox); gridLayout->addWidget(validFromLbl, row, 0, 1, 1); validFrom = new QLabel(groupBox); validFrom->setTextInteractionFlags(Qt::TextSelectableByMouse); gridLayout->addWidget(validFrom, row, 1, 1, 1); horizontalSpacer_3 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); gridLayout->addItem(horizontalSpacer_3, row, 2, 1, 1); row++; expiresLbl = new QLabel(i18n("Expires:"), groupBox); gridLayout->addWidget(expiresLbl, row, 0, 1, 1); horizontalLayout_3 = new QHBoxLayout(); expires = new QLabel(groupBox); expires->setTextInteractionFlags(Qt::TextSelectableByMouse); horizontalLayout_3->addWidget(expires); changeExpirationBtn = new QPushButton(groupBox); changeExpirationBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); changeExpirationBtn->setIcon(QIcon::fromTheme(QStringLiteral("editor"))); changeExpirationBtn->setToolTip(i18nc("@info:tooltip", "Change the expiration date")); horizontalLayout_3->addWidget(changeExpirationBtn); gridLayout->addLayout(horizontalLayout_3, row, 1, 1, 1); row++; typeLbl = new QLabel(i18n("Type:"), groupBox); gridLayout->addWidget(typeLbl, row, 0, 1, 1); type = new QLabel(groupBox); type->setTextInteractionFlags(Qt::TextSelectableByMouse); gridLayout->addWidget(type, row, 1, 1, 1); row++; fingerprintLbl = new QLabel(i18n("Fingerprint:"), groupBox); gridLayout->addWidget(fingerprintLbl, row, 0, 1, 1); { auto hbox = new QHBoxLayout; fingerprint = new QLabel{groupBox}; fingerprint->setTextInteractionFlags(Qt::TextSelectableByMouse); hbox->addWidget(fingerprint); copyFingerprintBtn = new QPushButton{groupBox}; copyFingerprintBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); copyFingerprintBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); copyFingerprintBtn->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard")); copyFingerprintBtn->setVisible(QGuiApplication::clipboard()); hbox->addWidget(copyFingerprintBtn); hbox->addStretch(); gridLayout->addLayout(hbox, row, 1, 1, 2); } row++; publishingLbl = new QLabel(i18n("Publishing:"), groupBox); gridLayout->addWidget(publishingLbl, row, 0, 1, 1); publishing = new QPushButton(i18nc("@action:button", "Publish Certificate"), groupBox); gridLayout->addWidget(publishing, row, 1, 1, 1); row++; smimeIssuerLbl = new QLabel(i18n("Issuer:"), groupBox); gridLayout->addWidget(smimeIssuerLbl, row, 0, 1, 1); smimeIssuer = new QLabel(groupBox); smimeIssuer->setWordWrap(true); smimeIssuer->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout->addWidget(smimeIssuer, row, 1, 1, 2); row++; compliance = new QLabel(i18n("Compliance:"), groupBox); compliance->setWordWrap(true); compliance->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout->addWidget(compliance, row, 0, 1, 1); complianceLbl = new QLabel(groupBox); complianceLbl->setWordWrap(true); complianceLbl->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout->addWidget(complianceLbl, row, 1, 1, 2); row++; trustedIntroducerLbl = new QLabel(i18n("Trusted introducer for:"), groupBox); trustedIntroducerLbl->setToolTip(i18n("See certifications for details.")); trustedIntroducerLbl->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout->addWidget(trustedIntroducerLbl, row, 0, 1, 1); trustedIntroducer = new QLabel(groupBox); trustedIntroducer->setWordWrap(true); trustedIntroducer->setToolTip(i18n("See certifications for details.")); trustedIntroducer->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout->addWidget(trustedIntroducer, row, 1, 1, 2); row++; horizontalLayout = new QHBoxLayout(); moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), groupBox); horizontalLayout->addWidget(moreDetailsBtn); exportBtn = new QPushButton(i18nc("@action:button", "Export..."), groupBox); horizontalLayout->addWidget(exportBtn); webOfTrustBtn = new QPushButton(i18nc("@action:button", "Certifications..."), groupBox); horizontalLayout->addWidget(webOfTrustBtn); horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout->addItem(horizontalSpacer); gridLayout->addLayout(horizontalLayout, row, 0, 1, 3); gridLayout_2->addWidget(groupBox, 6, 0, 1, 3); userIDTable = new QTreeWidget(parent); QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem(); __qtreewidgetitem->setText(0, QString::fromUtf8("1")); userIDTable->setHeaderItem(__qtreewidgetitem); userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers); userIDTable->setSelectionMode(QAbstractItemView::SingleSelection); userIDTable->setRootIsDecorated(false); userIDTable->setUniformRowHeights(true); userIDTable->setAllColumnsShowFocus(true); gridLayout_2->addWidget(userIDTable, 3, 0, 1, 3); label = new QLabel(i18n("You can use this certificate to secure communication with the following email addresses:"), parent); label->setWordWrap(true); gridLayout_2->addWidget(label, 0, 0, 1, 3); smimeOwnerLbl = new QLabel(i18n("Owner:"), parent); gridLayout_2->addWidget(smimeOwnerLbl, 1, 0, 1, 1); smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent); QFont font; font.setBold(true); font.setWeight(75); smimeRelatedAddresses->setFont(font); gridLayout_2->addWidget(smimeRelatedAddresses, 2, 0, 1, 1); smimeOwner = new QLabel(parent); smimeOwner->setWordWrap(true); smimeOwner->setTextInteractionFlags(Qt::TextBrowserInteraction); gridLayout_2->addWidget(smimeOwner, 1, 1, 1, 2); } } ui; }; CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq) : q{qq} { ui.setupUi(q); connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); }); connect(ui.addUserIDBtn, &QPushButton::clicked, q, [this]() { addUserID(); }); connect(ui.changePassphraseBtn, &QPushButton::clicked, q, [this]() { changePassphrase(); }); connect(ui.genRevokeBtn, &QPushButton::clicked, q, [this]() { genRevokeCert(); }); connect(ui.changeExpirationBtn, &QPushButton::clicked, q, [this]() { changeExpiration(); }); connect(ui.smimeOwner, &QLabel::linkActivated, q, [this](const QString &link) { smimeLinkActivated(link); }); connect(ui.smimeIssuer, &QLabel::linkActivated, q, [this](const QString &link) { smimeLinkActivated(link); }); connect(ui.trustChainDetailsBtn, &QPushButton::pressed, q, [this]() { showTrustChainDialog(); }); connect(ui.moreDetailsBtn, &QPushButton::pressed, q, [this]() { showMoreDetails(); }); connect(ui.publishing, &QPushButton::pressed, q, [this]() { publishCertificate(); }); connect(ui.refreshBtn, &QPushButton::clicked, q, [this]() { refreshCertificate(); }); connect(ui.certifyBtn, &QPushButton::clicked, q, [this]() { certifyClicked(); }); connect(ui.webOfTrustBtn, &QPushButton::clicked, q, [this]() { webOfTrustClicked(); }); connect(ui.exportBtn, &QPushButton::clicked, q, [this]() { exportClicked(); }); connect(ui.copyFingerprintBtn, &QPushButton::clicked, q, [this]() { copyFingerprintToClipboard(); }); connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); } void CertificateDetailsWidget::Private::setupCommonProperties() { // TODO: Enable once implemented HIDE_ROW(publishing) const bool hasSecret = key.hasSecret(); const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; ui.changePassphraseBtn->setVisible(hasSecret); ui.genRevokeBtn->setVisible(isOpenPGP && hasSecret); ui.certifyBtn->setVisible(isOpenPGP && !hasSecret); ui.changeExpirationBtn->setVisible(isOpenPGP && hasSecret); ui.addUserIDBtn->setVisible(hasSecret && isOpenPGP); ui.webOfTrustBtn->setVisible(isOpenPGP); ui.hboxLayout_1->addStretch(1); ui.validFrom->setText(Kleo::Formatting::creationDateString(key)); const QString expiry = Kleo::Formatting::expirationDateString(key); ui.expires->setText(expiry.isEmpty() ? i18nc("Expires", "never") : expiry); ui.type->setText(Kleo::Formatting::type(key)); ui.fingerprint->setText(Formatting::prettyID(key.primaryFingerprint())); if (!Kleo::gnupgIsDeVsCompliant()) { HIDE_ROW(compliance) } else { ui.complianceLbl->setText(Kleo::Formatting::complianceStringForKey(key)); } 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); if (!isOpenPGP && pMail.isEmpty() && !pName.isEmpty()) { // S/MIME UserIDs are sometimes split, with one userID // containing the name another the Mail, we merge these // UID's into a single item. if (i + 1 < uids.size()) { pMail = Kleo::Formatting::prettyEMail(uids[i + 1]); // skip next uid ++i; } } if (!isOpenPGP && pMail.isEmpty() && pName.isEmpty()) { // 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. pName = QString::fromLatin1(uid.id()); } item->setData(0, Qt::DisplayRole, pMail); item->setData(0, Qt::ToolTipRole, toolTip); item->setData(1, Qt::DisplayRole, pName); item->setData(1, Qt::ToolTipRole, toolTip); QIcon trustIcon; if (updateInProgress) { trustIcon = QIcon::fromTheme(QStringLiteral("emblem-question")); item->setData(2, Qt::DisplayRole, i18n("Updating...")); } else { switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-question")); break; case GpgME::UserID::Never: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-error")); break; case GpgME::UserID::Marginal: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-warning")); break; case GpgME::UserID::Full: case GpgME::UserID::Ultimate: trustIcon = QIcon::fromTheme(QStringLiteral("emblem-success")); break; } item->setData(2, Qt::DisplayRole, Kleo::Formatting::validityShort(uid)); } item->setData(2, Qt::DecorationRole, trustIcon); 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::revokeUserID(const GpgME::UserID &userId) { const QString message = xi18nc( "@info", "Do you really want to revoke the user ID%1 ?", QString::fromUtf8(userId.id())); auto confirmButton = KStandardGuiItem::yes(); confirmButton.setText(i18nc("@action:button", "Revoke User ID")); confirmButton.setToolTip({}); const auto choice = KMessageBox::questionYesNo( q->window(), message, i18nc("@title:window", "Confirm Revocation"), confirmButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::WindowModal); if (choice != KMessageBox::Yes) { return; } auto cmd = new Commands::RevokeUserIDCommand(userId); cmd->setParentWidget(q); ui.userIDTable->setEnabled(false); connect(cmd, &Command::finished, q, [this]() { ui.userIDTable->setEnabled(true); updateKey(); }); cmd->start(); } void CertificateDetailsWidget::Private::changeExpiration() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.changeExpirationBtn->setEnabled(true); }); ui.changeExpirationBtn->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::certifyClicked() { auto cmd = new Kleo::Commands::CertifyCertificateCommand(key); QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { ui.certifyBtn->setEnabled(true); }); ui.certifyBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::webOfTrustClicked() { QScopedPointer dlg(new WebOfTrustDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::exportClicked() { QScopedPointer 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(); } 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 dlg(new TrustChainDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::publishCertificate() { qCWarning(KLEOPATRA_LOG) << "publishCertificateis not implemented."; //TODO } namespace { bool isLastValidUserID(const GpgME::UserID &userId) { if (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 !isRevokedOrExpired(u); }); return numberOfValidUserIds == 1; } bool canRevokeUserID(const GpgME::UserID &userId) { const auto key = userId.parent(); return (!userId.isNull() // && key.protocol() == GpgME::OpenPGP && canCreateCertifications(key) && !isLastValidUserID(userId)); } } void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p) { auto item = ui.userIDTable->itemAt(p); if (!item) { return; } const auto userID = item->data(0, Qt::UserRole).value(); auto menu = new QMenu(q); menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), i18n("Certify..."), q, [this, userID]() { auto cmd = new Kleo::Commands::CertifyCertificateCommand(userID); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); updateKey(); }); cmd->start(); }); { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, userID]() { revokeUserID(userID); }); action->setEnabled(canRevokeUserID(userID)); } 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); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::RevokeCertificationCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); updateKey(); }); cmd->start(); }); } #ifdef MAILAKONADI_ENABLED if (key.hasSecret() && key.protocol() == GpgME::OpenPGP) { - menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export-provider")), - i18n("Publish at mail provider ..."), + menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), + i18nc("@action:inmenu", "Publish at Mail Provider ..."), q, [this, userID]() { auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(userID); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); }); cmd->start(); }); } #endif // MAILAKONADI_ENABLED connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(ui.userIDTable->viewport()->mapToGlobal(p)); } void CertificateDetailsWidget::Private::showMoreDetails() { ui.moreDetailsBtn->setEnabled(false); if (key.protocol() == GpgME::CMS) { auto cmd = new Kleo::Commands::DumpCertificateCommand(key); connect(cmd, &Kleo::Commands::DumpCertificateCommand::finished, q, [this]() { ui.moreDetailsBtn->setEnabled(true); }); cmd->setUseDialog(true); cmd->start(); } else { QScopedPointer dlg(new SubKeysDialog(q)); dlg->setKey(key); dlg->exec(); ui.moreDetailsBtn->setEnabled(true); } } QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const { const auto tofu = uid.tofuInfo(); if (tofu.isNull()) { return QString(); } QString html = QStringLiteral(""); const auto appendRow = [&html](const QString &lbl, const QString &val) { html += QStringLiteral("" "" "" "") .arg(lbl, val); }; const auto appendHeader = [this, &html](const QString &hdr) { html += QStringLiteral("") .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("
%1:%2
%3
"); // 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("").arg(QString::fromUtf8(uid.id())); return html; } namespace { auto isGood(const GpgME::UserID::Signature &signature) { return signature.status() == GpgME::UserID::Signature::NoError && !signature.isInvalid() && 0x10 <= signature.certClass() && signature.certClass() <= 0x13; } auto accumulateTrustDomains(const std::vector &signatures) { return std::accumulate( std::begin(signatures), std::end(signatures), std::set(), [] (auto domains, const auto &signature) { if (isGood(signature) && signature.isTrustSignature()) { domains.insert(Formatting::trustSignatureDomain(signature)); } return domains; } ); } auto accumulateTrustDomains(const std::vector &userIds) { return std::accumulate( std::begin(userIds), std::end(userIds), std::set(), [] (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() { HIDE_ROW(smimeOwner) HIDE_ROW(smimeIssuer) ui.smimeRelatedAddresses->setVisible(false); ui.trustChainDetailsBtn->setVisible(false); ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu); const auto trustDomains = accumulateTrustDomains(key.userIDs()); if (trustDomains.empty()) { HIDE_ROW(trustedIntroducer) } else { SHOW_ROW(trustedIntroducer) ui.trustedIntroducer->setText(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", ")); } ui.refreshBtn->setToolTip(i18nc("@ingo:tooltip", "Update the key from external sources.")); } static QString formatDNToolTip(const Kleo::DN &dn) { QString html = QStringLiteral(""); const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) { const QString val = dn[attr]; if (!val.isEmpty()) { html += QStringLiteral( "" "" "").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("
%1:%2
"); return html; } void CertificateDetailsWidget::Private::setupSMIMEProperties() { HIDE_ROW(publishing) HIDE_ROW(trustedIntroducer) const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); const QString cn = dn[QStringLiteral("CN")]; const QString o = dn[QStringLiteral("O")]; const QString dnEmail = dn[QStringLiteral("EMAIL")]; const QString name = cn.isEmpty() ? dnEmail : cn; QString owner; if (name.isEmpty()) { owner = dn.dn(); } else if (o.isEmpty()) { owner = name; } else { owner = i18nc(" of ", "%1 of %2", name, o); } ui.smimeOwner->setText(owner); ui.smimeOwner->setTextInteractionFlags(Qt::TextBrowserInteraction); const Kleo::DN issuerDN(key.issuerName()); const QString issuerCN = issuerDN[QStringLiteral("CN")]; const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN; ui.smimeIssuer->setText(QStringLiteral("%1").arg(issuer)); ui.smimeIssuer->setToolTip(formatDNToolTip(issuerDN)); ui.smimeOwner->setToolTip(formatDNToolTip(dn)); ui.refreshBtn->setToolTip(i18nc("@ingo:tooltip", "Update the CRLs and do a full validation check of the certificate.")); } void CertificateDetailsWidget::Private::smimeLinkActivated(const QString &link) { if (link == QLatin1String("#issuerDetails")) { const auto parentKey = KeyCache::instance()->findIssuers(key, KeyCache::NoOption); if (!parentKey.size()) { return; } auto cmd = new Kleo::Commands::DetailsCommand(parentKey[0], nullptr); cmd->setParentWidget(q); cmd->start(); return; } qCWarning(KLEOPATRA_LOG) << "Unknown link activated:" << link; } 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(this)} { } CertificateDetailsWidget::~CertificateDetailsWidget() = default; void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &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,QString,GpgME::Error)), this, SLOT(keyListDone(GpgME::KeyListResult,std::vector,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/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index c7765428c..c7c4ebe7f 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,862 +1,862 @@ /* -*- 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 #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include #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 #ifdef 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/newcertificatecommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/exportpaperkeycommand.h" #include "commands/revokekeycommand.h" #include #include #include #include #include #include #include #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; #ifndef 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 slotProgress(const QString &what, int current, int total) { Q_EMIT q->progress(current, total); if (!what.isEmpty()) { Q_EMIT q->message(what); } } void slotActionTriggered(); 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 action; Command::Restrictions restrictions; Command *(*createCommand)(QAbstractItemView *, KeyListController *); }; std::vector actions; std::vector views; std::vector commands; QPointer parentWidget; QPointer tabWidget; QPointer currentView; QPointer flatModel, hierarchicalModel; }; 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 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; } static const struct { const char *signal; const char *slot; } tabs2controller[] = { { SIGNAL(viewAdded(QAbstractItemView*)), SLOT(addView(QAbstractItemView*)) }, { SIGNAL(viewAboutToBeRemoved(QAbstractItemView*)), SLOT(removeView(QAbstractItemView*)) }, { SIGNAL(currentViewChanged(QAbstractItemView*)), SLOT(slotCurrentViewChanged(QAbstractItemView*)) }, }; static const unsigned int numTabs2Controller = sizeof tabs2controller / sizeof * tabs2controller; void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); for (unsigned int i = 0; i < numTabs2Controller; ++i) { connect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } for (unsigned int i = 0; i < numTabs2Controller; ++i) { disconnect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); } 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 common_and_openpgp_action_data = { // File menu { "file_new_certificate", i18n("New Key Pair..."), QString(), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), false, true }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), false, true }, { "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"), false, true }, #ifdef MAILAKONADI_ENABLED { - "file_export_certificate_to_provider", i18n("Publish at Mailprovider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"), - "view-certificate-export-provider", nullptr, nullptr, QString(), false, true + "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(), false, true }, #endif // MAILAKONADI_ENABLED { "file_export_secret_keys", i18n("Backup Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), false, true }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), false, true }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), false, true }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), false, true }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString(), false, true }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), false, true }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, SLOT(cancelCommands()), QStringLiteral("Escape"), false, false }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), false, true }, // Certificate menu #ifdef QGPGME_SUPPORTS_KEY_REVOCATION { "certificates_revoke", i18n("Revoke Certificate..."), i18n("Revoke the selected OpenPGP certificate"), "view-certificate-revoke", nullptr, nullptr, {}, false, true }, #endif { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), false, true }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), false, true }, { "certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"), "view-certificate-revoke", nullptr, nullptr, QString(), false, true }, { "certificates_change_expiry", i18n("Change Expiry Date..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_owner_trust", i18n("Change Certification Trust..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_add_userid", i18n("Add User ID..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; static const std::vector cms_action_data = { // Certificate menu { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, }; std::vector action_data = common_and_openpgp_action_data; if (Settings{}.cmsEnabled()) { 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(coll->action(QStringLiteral("file_new_certificate"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_lookup_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_import_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_export_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_export_secret_keys"))); registerActionForCommand(coll->action(QStringLiteral("file_export_paper_key"))); registerActionForCommand(coll->action(QStringLiteral("file_export_certificates_to_server"))); #ifdef MAILAKONADI_ENABLED registerActionForCommand(coll->action(QStringLiteral("file_export_certificate_to_provider"))); #endif // MAILAKONADI_ENABLED //--- registerActionForCommand(coll->action(QStringLiteral("file_decrypt_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_folder"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_checksum_create_files"))); registerActionForCommand(coll->action(QStringLiteral("file_checksum_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("view_redisplay"))); //coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data() registerActionForCommand(coll->action(QStringLiteral("view_certificate_details"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_owner_trust"))); registerActionForCommand(coll->action(QStringLiteral("certificates_trust_root"))); registerActionForCommand(coll->action(QStringLiteral("certificates_distrust_root"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_certify_certificate"))); if (RevokeCertificationCommand::isSupported()) { registerActionForCommand(coll->action(QStringLiteral("certificates_revoke_certification"))); } //--- registerActionForCommand(coll->action(QStringLiteral("certificates_change_expiry"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_passphrase"))); registerActionForCommand(coll->action(QStringLiteral("certificates_add_userid"))); //--- #ifdef QGPGME_SUPPORTS_KEY_REVOCATION registerActionForCommand(coll->action(QStringLiteral("certificates_revoke"))); #endif registerActionForCommand(coll->action(QStringLiteral("certificates_delete"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_dump_certificate"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_x509_certificates"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_openpgp_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_import_crl"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_clear_crl_cache"))); registerActionForCommand(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, SIGNAL(triggered()), this, SLOT(slotActionTriggered())); 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::info, q, &KeyListController::message); connect(cmd, &Command::progress, q, [this](const QString &message, int current, int total) { slotProgress(message, current, total); }); } void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } DetailsCommand *const c = new DetailsCommand(view, q); if (parentWidget) { c->setParentWidget(parentWidget); } c->setIndex(idx); c->start(); } void KeyListController::Private::slotActivated(const QModelIndex &idx) { Q_UNUSED(idx) QAbstractItemView *const view = qobject_cast(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(q->sender()); if (!sm) { return; } q->enableDisableActions(sm); } void KeyListController::Private::slotContextMenu(const QPoint &p) { QAbstractItemView *const view = qobject_cast(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(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 &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 &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(sm->model()); if (!m) { return Command::NoRestriction; } const std::vector 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 (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() { if (const QObject *const s = q->sender()) { const auto it = std::find_if(actions.cbegin(), actions.cend(), [this](const action_item &item) { return item.action == q->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(s->objectName()) << "\""; else { qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(s->objectName()); } } else { qCDebug(KLEOPATRA_LOG) << "not called through a signal/slot connection (sender() == NULL)"; } } 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"